Repository: hadoop Updated Branches: refs/heads/trunk bc6d9d4c7 -> aa23d49fc
HADOOP-15457. Add Security-Related HTTP Response Header in WEBUIs. (kanwaljeets via rkanter) Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/aa23d49f Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/aa23d49f Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/aa23d49f Branch: refs/heads/trunk Commit: aa23d49fc8b9c2537529dbdc13512000e2ab295a Parents: bc6d9d4 Author: Robert Kanter <rkan...@apache.org> Authored: Wed May 23 10:23:17 2018 -0700 Committer: Robert Kanter <rkan...@apache.org> Committed: Wed May 23 10:24:09 2018 -0700 ---------------------------------------------------------------------- .../org/apache/hadoop/http/HttpServer2.java | 79 +++++++++++++++----- .../org/apache/hadoop/http/TestHttpServer.java | 61 +++++++++++++++ 2 files changed, 121 insertions(+), 19 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hadoop/blob/aa23d49f/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java index 47ca841..c273c78 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java @@ -34,6 +34,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -172,10 +174,16 @@ public final class HttpServer2 implements FilterContainer { private final SignerSecretProvider secretProvider; private XFrameOption xFrameOption; private boolean xFrameOptionIsEnabled; - private static final String X_FRAME_VALUE = "xFrameOption"; - private static final String X_FRAME_ENABLED = "X_FRAME_ENABLED"; - - + public static final String HTTP_HEADER_PREFIX = "hadoop.http.header."; + private static final String HTTP_HEADER_REGEX = + "hadoop\\.http\\.header\\.([a-zA-Z\\-_]+)"; + static final String X_XSS_PROTECTION = + "X-XSS-Protection:1; mode=block"; + static final String X_CONTENT_TYPE_OPTIONS = + "X-Content-Type-Options:nosniff"; + private static final String X_FRAME_OPTIONS = "X-FRAME-OPTIONS"; + private static final Pattern PATTERN_HTTP_HEADER_REGEX = + Pattern.compile(HTTP_HEADER_REGEX); /** * Class to construct instances of HTTP server with specific options. */ @@ -574,10 +582,7 @@ public final class HttpServer2 implements FilterContainer { addDefaultApps(contexts, appDir, conf); webServer.setHandler(handlers); - Map<String, String> xFrameParams = new HashMap<>(); - xFrameParams.put(X_FRAME_ENABLED, - String.valueOf(this.xFrameOptionIsEnabled)); - xFrameParams.put(X_FRAME_VALUE, this.xFrameOption.toString()); + Map<String, String> xFrameParams = setHeaders(conf); addGlobalFilter("safety", QuotingInputFilter.class.getName(), xFrameParams); final FilterInitializer[] initializers = getFilterInitializers(conf); if (initializers != null) { @@ -1475,9 +1480,11 @@ public final class HttpServer2 implements FilterContainer { public static class QuotingInputFilter implements Filter { private FilterConfig config; + private Map<String, String> headerMap; public static class RequestQuoter extends HttpServletRequestWrapper { private final HttpServletRequest rawRequest; + public RequestQuoter(HttpServletRequest rawRequest) { super(rawRequest); this.rawRequest = rawRequest; @@ -1566,6 +1573,7 @@ public final class HttpServer2 implements FilterContainer { @Override public void init(FilterConfig config) throws ServletException { this.config = config; + initHttpHeaderMap(); } @Override @@ -1593,11 +1601,7 @@ public final class HttpServer2 implements FilterContainer { } else if (mime.startsWith("application/xml")) { httpResponse.setContentType("text/xml; charset=utf-8"); } - - if(Boolean.valueOf(this.config.getInitParameter(X_FRAME_ENABLED))) { - httpResponse.addHeader("X-FRAME-OPTIONS", - this.config.getInitParameter(X_FRAME_VALUE)); - } + headerMap.forEach((k, v) -> httpResponse.addHeader(k, v)); chain.doFilter(quoted, httpResponse); } @@ -1613,14 +1617,25 @@ public final class HttpServer2 implements FilterContainer { return (mime == null) ? null : mime; } + private void initHttpHeaderMap() { + Enumeration<String> params = this.config.getInitParameterNames(); + headerMap = new HashMap<>(); + while (params.hasMoreElements()) { + String key = params.nextElement(); + Matcher m = PATTERN_HTTP_HEADER_REGEX.matcher(key); + if (m.matches()) { + String headerKey = m.group(1); + headerMap.put(headerKey, config.getInitParameter(key)); + } + } + } } - - /** - * The X-FRAME-OPTIONS header in HTTP response to mitigate clickjacking - * attack. - */ + /** + * The X-FRAME-OPTIONS header in HTTP response to mitigate clickjacking + * attack. + */ public enum XFrameOption { - DENY("DENY") , SAMEORIGIN ("SAMEORIGIN"), ALLOWFROM ("ALLOW-FROM"); + DENY("DENY"), SAMEORIGIN("SAMEORIGIN"), ALLOWFROM("ALLOW-FROM"); XFrameOption(String name) { this.name = name; @@ -1651,4 +1666,30 @@ public final class HttpServer2 implements FilterContainer { throw new IllegalArgumentException("Unexpected value in xFrameOption."); } } + + + private Map<String, String> setHeaders(Configuration conf) { + Map<String, String> xFrameParams = new HashMap<>(); + Map<String, String> headerConfigMap = + conf.getValByRegex(HTTP_HEADER_REGEX); + + xFrameParams.putAll(getDefaultHeaders()); + if(this.xFrameOptionIsEnabled) { + xFrameParams.put(HTTP_HEADER_PREFIX+X_FRAME_OPTIONS, + this.xFrameOption.toString()); + } + xFrameParams.putAll(headerConfigMap); + return xFrameParams; + } + + private Map<String, String> getDefaultHeaders() { + Map<String, String> headers = new HashMap<>(); + String[] splitVal = X_CONTENT_TYPE_OPTIONS.split(":"); + headers.put(HTTP_HEADER_PREFIX + splitVal[0], + splitVal[1]); + splitVal = X_XSS_PROTECTION.split(":"); + headers.put(HTTP_HEADER_PREFIX + splitVal[0], + splitVal[1]); + return headers; + } } http://git-wip-us.apache.org/repos/asf/hadoop/blob/aa23d49f/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServer.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServer.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServer.java index 6c1512e..26b1137 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServer.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServer.java @@ -701,4 +701,65 @@ public class TestHttpServer extends HttpServerFunctionalTest { ServerConnector listener = (ServerConnector)listeners.get(0); assertEquals(backlogSize, listener.getAcceptQueueSize()); } + + @Test + public void testHttpResponseDefaultHeaders() throws Exception { + Configuration conf = new Configuration(); + HttpServer2 httpServer = createTestServer(conf); + try { + HttpURLConnection conn = getHttpURLConnection(httpServer); + assertEquals(HttpServer2.X_XSS_PROTECTION.split(":")[1], + conn.getHeaderField( + HttpServer2.X_XSS_PROTECTION.split(":")[0])); + assertEquals(HttpServer2.X_CONTENT_TYPE_OPTIONS.split(":")[1], + conn.getHeaderField( + HttpServer2.X_CONTENT_TYPE_OPTIONS.split(":")[0])); + } finally { + httpServer.stop(); + } + } + + @Test + public void testHttpResponseOverrideDefaultHeaders() throws Exception { + Configuration conf = new Configuration(); + conf.set(HttpServer2.HTTP_HEADER_PREFIX+ + HttpServer2.X_XSS_PROTECTION.split(":")[0], "customXssValue"); + HttpServer2 httpServer = createTestServer(conf); + try { + HttpURLConnection conn = getHttpURLConnection(httpServer); + assertEquals("customXssValue", + conn.getHeaderField( + HttpServer2.X_XSS_PROTECTION.split(":")[0]) + ); + assertEquals(HttpServer2.X_CONTENT_TYPE_OPTIONS.split(":")[1], + conn.getHeaderField( + HttpServer2.X_CONTENT_TYPE_OPTIONS.split(":")[0]) + ); + } finally { + httpServer.stop(); + } + } + + @Test + public void testHttpResponseCustomHeaders() throws Exception { + Configuration conf = new Configuration(); + String key = "customKey"; + String value = "customValue"; + conf.set(HttpServer2.HTTP_HEADER_PREFIX+key, value); + HttpServer2 httpServer = createTestServer(conf); + try { + HttpURLConnection conn = getHttpURLConnection(httpServer); + assertEquals(HttpServer2.X_XSS_PROTECTION.split(":")[1], + conn.getHeaderField( + HttpServer2.X_XSS_PROTECTION.split(":")[0])); + assertEquals(HttpServer2.X_CONTENT_TYPE_OPTIONS.split(":")[1], + conn.getHeaderField( + HttpServer2.X_CONTENT_TYPE_OPTIONS.split(":")[0])); + assertEquals(value, conn.getHeaderField( + key)); + } finally { + httpServer.stop(); + } + } + } --------------------------------------------------------------------- To unsubscribe, e-mail: common-commits-unsubscr...@hadoop.apache.org For additional commands, e-mail: common-commits-h...@hadoop.apache.org