CLOUDSTACK-7063, CLOUDSTACK-7064: Add security headers on HTTP response

- Adds X-XSS-Protection header
- Adds X-Content-Type-Options header
- Fixes to use json content type defined from global settings
- Uses secure cookie if enabled in global settings

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
(cherry picked from commit b6b3494782d8bc1033941b802380ba1d5ebd464c)
Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Rohit Yadav 2015-02-28 18:12:37 +05:30
parent 843c0f891b
commit 20bcb4b673
4 changed files with 66 additions and 20 deletions

View File

@ -181,7 +181,8 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
private static final Logger s_accessLogger = Logger.getLogger("apiserver." + ApiServer.class.getName());
public static boolean encodeApiResponse = false;
public static String jsonContentType = "text/javascript";
public static boolean s_enableSecureCookie = false;
public static String s_jsonContentType = HttpUtils.JSON_CONTENT_TYPE;
/**
* Non-printable ASCII characters - numbers 0 to 31 and 127 decimal
@ -362,9 +363,13 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
}
setEncodeApiResponse(Boolean.valueOf(_configDao.getValue(Config.EncodeApiResponse.key())));
final String jsonType = _configDao.getValue(Config.JavaScriptDefaultContentType.key());
final String jsonType = _configDao.getValue(Config.JSONDefaultContentType.key());
if (jsonType != null) {
jsonContentType = jsonType;
s_jsonContentType = jsonType;
}
final Boolean enableSecureSessionCookie = Boolean.valueOf(_configDao.getValue(Config.EnableSecureSessionCookie.key()));
if (enableSecureSessionCookie != null) {
s_enableSecureCookie = enableSecureSessionCookie;
}
if (apiPort != null) {
@ -1136,7 +1141,7 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
final BasicHttpEntity body = new BasicHttpEntity();
if (HttpUtils.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) {
// JSON response
body.setContentType(jsonContentType);
body.setContentType(getJSONContentType());
if (responseText == null) {
body.setContent(new ByteArrayInputStream("{ \"error\" : { \"description\" : \"Internal Server Error\" } }".getBytes(HttpUtils.UTF_8)));
}
@ -1367,7 +1372,11 @@ public class ApiServer extends ManagerBase implements HttpRequestHandler, ApiSer
ApiServer.encodeApiResponse = encodeApiResponse;
}
public static String getJsonContentType() {
return jsonContentType;
public static boolean isSecureSessionCookieEnabled() {
return s_enableSecureCookie;
}
public static String getJSONContentType() {
return s_jsonContentType;
}
}

View File

@ -155,12 +155,20 @@ public class ApiServlet extends HttpServlet {
try {
if (HttpUtils.RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) {
resp.setContentType(HttpUtils.JSON_CONTENT_TYPE);
resp.setContentType(ApiServer.getJSONContentType());
} else if (HttpUtils.RESPONSE_TYPE_XML.equalsIgnoreCase(responseType)){
resp.setContentType(HttpUtils.XML_CONTENT_TYPE);
}
HttpSession session = req.getSession(false);
if (ApiServer.isSecureSessionCookieEnabled()) {
resp.setHeader("SET-COOKIE", "JSESSIONID=" + session.getId() + ";Secure;Path=/client");
if (s_logger.isDebugEnabled()) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Session cookie is marked secure!");
}
}
}
final Object[] responseTypeParam = params.get(ApiConstants.RESPONSE);
if (responseTypeParam != null) {
responseType = (String)responseTypeParam[0];
@ -213,7 +221,7 @@ public class ApiServlet extends HttpServlet {
}
}
}
HttpUtils.writeHttpResponse(resp, responseString, httpResponseCode, responseType);
HttpUtils.writeHttpResponse(resp, responseString, httpResponseCode, responseType, ApiServer.getJSONContentType());
return;
}
}
@ -240,7 +248,7 @@ public class ApiServlet extends HttpServlet {
auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials");
final String serializedResponse =
_apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials", params, responseType);
HttpUtils.writeHttpResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType);
HttpUtils.writeHttpResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType, ApiServer.getJSONContentType());
return;
}
@ -251,7 +259,7 @@ public class ApiServlet extends HttpServlet {
s_logger.info("missing command, ignoring request...");
auditTrailSb.append(" " + HttpServletResponse.SC_BAD_REQUEST + " " + "no command specified");
final String serializedResponse = _apiServer.getSerializedApiError(HttpServletResponse.SC_BAD_REQUEST, "no command specified", params, responseType);
HttpUtils.writeHttpResponse(resp, serializedResponse, HttpServletResponse.SC_BAD_REQUEST, responseType);
HttpUtils.writeHttpResponse(resp, serializedResponse, HttpServletResponse.SC_BAD_REQUEST, responseType, ApiServer.getJSONContentType());
return;
}
final User user = _entityMgr.findById(User.class, userId);
@ -267,7 +275,7 @@ public class ApiServlet extends HttpServlet {
auditTrailSb.append(" " + HttpServletResponse.SC_UNAUTHORIZED + " " + "unable to verify user credentials");
final String serializedResponse =
_apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials", params, responseType);
HttpUtils.writeHttpResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType);
HttpUtils.writeHttpResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType, ApiServer.getJSONContentType());
return;
}
} else {
@ -281,7 +289,7 @@ public class ApiServlet extends HttpServlet {
// Add the HTTP method (GET/POST/PUT/DELETE) as well into the params map.
params.put("httpmethod", new String[] {req.getMethod()});
final String response = _apiServer.handleRequest(params, responseType, auditTrailSb);
HttpUtils.writeHttpResponse(resp, response != null ? response : "", HttpServletResponse.SC_OK, responseType);
HttpUtils.writeHttpResponse(resp, response != null ? response : "", HttpServletResponse.SC_OK, responseType, ApiServer.getJSONContentType());
} else {
if (session != null) {
try {
@ -294,13 +302,13 @@ public class ApiServlet extends HttpServlet {
final String serializedResponse =
_apiServer.getSerializedApiError(HttpServletResponse.SC_UNAUTHORIZED, "unable to verify user credentials and/or request signature", params,
responseType);
HttpUtils.writeHttpResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType);
HttpUtils.writeHttpResponse(resp, serializedResponse, HttpServletResponse.SC_UNAUTHORIZED, responseType, ApiServer.getJSONContentType());
}
} catch (final ServerApiException se) {
final String serializedResponseText = _apiServer.getSerializedApiError(se, params, responseType);
resp.setHeader("X-Description", se.getDescription());
HttpUtils.writeHttpResponse(resp, serializedResponseText, se.getErrorCode().getHttpCode(), responseType);
HttpUtils.writeHttpResponse(resp, serializedResponseText, se.getErrorCode().getHttpCode(), responseType, ApiServer.getJSONContentType());
auditTrailSb.append(" " + se.getErrorCode() + " " + se.getDescription());
} catch (final Exception ex) {
s_logger.error("unknown exception writing api response", ex);

View File

@ -1592,13 +1592,22 @@ public enum Config {
"Percentage (as a value between 0 and 1) of connected agents after which agent load balancing will start happening",
null),
JavaScriptDefaultContentType(
JSONDefaultContentType(
"Advanced",
ManagementServer.class,
String.class,
"json.content.type",
"text/javascript",
"Http response content type for .js files (default is text/javascript)",
"application/json; charset=UTF-8",
"Http response content type for JSON",
null),
EnableSecureSessionCookie(
"Advanced",
ManagementServer.class,
Boolean.class,
"enable.secure.session.cookie",
"false",
"Session cookie's secure flag is enabled if true. Use this only when using HTTPS",
null),
DefaultMaxDomainUserVms("Domain Defaults", ManagementServer.class, Long.class, "max.domain.user.vms", "40", "The default maximum number of user VMs that can be deployed for a domain", null),

View File

@ -31,20 +31,40 @@ public class HttpUtils {
public static final String UTF_8 = "UTF-8";
public static final String RESPONSE_TYPE_JSON = "json";
public static final String RESPONSE_TYPE_XML = "xml";
public static final String JSON_CONTENT_TYPE = "text/javascript; charset=UTF-8";
public static final String JSON_CONTENT_TYPE = "application/json; charset=UTF-8";
public static final String XML_CONTENT_TYPE = "text/xml; charset=UTF-8";
public static void addSecurityHeaders(final HttpServletResponse resp) {
if (resp.containsHeader("X-Content-Type-Options")) {
resp.setHeader("X-Content-Type-Options", "nosniff");
}
else {
resp.addHeader("X-Content-Type-Options", "nosniff");
}
if (resp.containsHeader("X-XSS-Protection")) {
resp.setHeader("X-XSS-Protection", "1;mode=block");
}
else {
resp.addHeader("X-XSS-Protection", "1;mode=block");
}
}
public static void writeHttpResponse(final HttpServletResponse resp, final String response,
final Integer responseCode, final String responseType) {
final Integer responseCode, final String responseType, final String jsonContentType) {
try {
if (RESPONSE_TYPE_JSON.equalsIgnoreCase(responseType)) {
resp.setContentType(JSON_CONTENT_TYPE);
if (jsonContentType != null && !jsonContentType.isEmpty()) {
resp.setContentType(jsonContentType);
} else {
resp.setContentType(JSON_CONTENT_TYPE);
}
} else if (RESPONSE_TYPE_XML.equalsIgnoreCase(responseType)){
resp.setContentType(XML_CONTENT_TYPE);
}
if (responseCode != null) {
resp.setStatus(responseCode);
}
addSecurityHeaders(resp);
resp.getWriter().print(response);
} catch (final IOException ioex) {
if (s_logger.isTraceEnabled()) {