From b6b3494782d8bc1033941b802380ba1d5ebd464c Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Sat, 28 Feb 2015 18:12:37 +0530 Subject: [PATCH] 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 --- server/src/com/cloud/api/ApiServer.java | 21 ++++++++++----- server/src/com/cloud/api/ApiServlet.java | 24 +++++++++++------ .../src/com/cloud/configuration/Config.java | 15 ++++++++--- utils/src/com/cloud/utils/HttpUtils.java | 26 ++++++++++++++++--- 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/server/src/com/cloud/api/ApiServer.java b/server/src/com/cloud/api/ApiServer.java index 59f931638e5..6dcf48ad0af 100755 --- a/server/src/com/cloud/api/ApiServer.java +++ b/server/src/com/cloud/api/ApiServer.java @@ -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; } } diff --git a/server/src/com/cloud/api/ApiServlet.java b/server/src/com/cloud/api/ApiServlet.java index 3d2e843db30..edfc2465cc2 100644 --- a/server/src/com/cloud/api/ApiServlet.java +++ b/server/src/com/cloud/api/ApiServlet.java @@ -148,12 +148,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]; @@ -206,7 +214,7 @@ public class ApiServlet extends HttpServlet { } } } - HttpUtils.writeHttpResponse(resp, responseString, httpResponseCode, responseType); + HttpUtils.writeHttpResponse(resp, responseString, httpResponseCode, responseType, ApiServer.getJSONContentType()); return; } } @@ -233,7 +241,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; } @@ -244,7 +252,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); @@ -260,7 +268,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 { @@ -274,7 +282,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 { @@ -287,13 +295,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); diff --git a/server/src/com/cloud/configuration/Config.java b/server/src/com/cloud/configuration/Config.java index 93ee1ca8e50..a6089fc6a76 100755 --- a/server/src/com/cloud/configuration/Config.java +++ b/server/src/com/cloud/configuration/Config.java @@ -1591,13 +1591,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), DefaultMaxProjectUserVms( diff --git a/utils/src/com/cloud/utils/HttpUtils.java b/utils/src/com/cloud/utils/HttpUtils.java index 0ed2afaa46d..58768dcabf7 100644 --- a/utils/src/com/cloud/utils/HttpUtils.java +++ b/utils/src/com/cloud/utils/HttpUtils.java @@ -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()) {