From 9e32437c8ac9aacd935d9df23d7f935d6dee303a Mon Sep 17 00:00:00 2001 From: Andor Molnar Date: Fri, 15 Nov 2019 16:37:08 +0100 Subject: [PATCH 01/10] HBASE-23303. Added new HTTP filter to include missing security headers --- .../apache/hadoop/hbase/rest/RESTServer.java | 19 ++++-- .../rest/filter/SecurityHeadersFilter.java | 58 +++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java index 95e401a88db0..2c10cfb5fd4d 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java @@ -19,14 +19,12 @@ package org.apache.hadoop.hbase.rest; import java.lang.management.ManagementFactory; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.EnumSet; +import java.util.*; import java.util.concurrent.ArrayBlockingQueue; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import org.apache.commons.lang3.ArrayUtils; +import org.apache.hadoop.hbase.rest.filter.SecurityHeadersFilter; import org.apache.yetus.audience.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; @@ -137,6 +135,18 @@ void addCSRFFilter(ServletContextHandler ctxHandler, Configuration conf) { } } + private void addSecurityHeadersFilter(ServletContextHandler ctxHandler) { + FilterHolder holder = new FilterHolder(); + holder.setName("security"); + holder.setClassName(SecurityHeadersFilter.class.getName()); + Map params = new HashMap<>(); + params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", + "DENY")); + holder.setInitParameters(params); + ctxHandler.addFilter(SecurityHeadersFilter.class, PATH_SPEC_ANY, + EnumSet.allOf(DispatcherType.class)); + } + // login the server principal (if using secure Hadoop) private static Pair> loginServerPrincipal( UserProvider userProvider, Configuration conf) throws Exception { @@ -349,6 +359,7 @@ public synchronized void run() throws Exception { ctxHandler.addFilter(filter, PATH_SPEC_ANY, EnumSet.of(DispatcherType.REQUEST)); } addCSRFFilter(ctxHandler, conf); + addSecurityHeadersFilter(ctxHandler); HttpServerUtil.constrainHttpMethods(ctxHandler, servlet.getConfiguration() .getBoolean(REST_HTTP_ALLOW_OPTIONS_METHOD, REST_HTTP_ALLOW_OPTIONS_METHOD_DEFAULT)); diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java new file mode 100644 index 000000000000..4ff8ce3e1d31 --- /dev/null +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java @@ -0,0 +1,58 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.hbase.rest.filter; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SecurityHeadersFilter implements Filter { + private static final Logger LOG = + LoggerFactory.getLogger(SecurityHeadersFilter.class); + private FilterConfig filterConfig; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + this.filterConfig = filterConfig; + LOG.info("Added security headers filter"); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.addHeader("X-Content-Type-Options", "nosniff"); + httpResponse.addHeader("X-XSS-Protection", "1; mode=block"); + httpResponse.addHeader("X-Frame-Options", + filterConfig.getInitParameter("xframeoptions")); + chain.doFilter(request, response); + } + + @Override + public void destroy() { + } +} From 30800544e89230a0b5310878c961bd333e358fa2 Mon Sep 17 00:00:00 2001 From: Andor Molnar Date: Fri, 15 Nov 2019 17:26:56 +0100 Subject: [PATCH 02/10] HBASE-23303. Added configurable X-Frame-Options header --- .../main/java/org/apache/hadoop/hbase/rest/RESTServer.java | 7 +++---- .../hadoop/hbase/rest/filter/SecurityHeadersFilter.java | 3 +++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java index 2c10cfb5fd4d..2a315c221005 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java @@ -135,7 +135,7 @@ void addCSRFFilter(ServletContextHandler ctxHandler, Configuration conf) { } } - private void addSecurityHeadersFilter(ServletContextHandler ctxHandler) { + private void addSecurityHeadersFilter(ServletContextHandler ctxHandler, Configuration conf) { FilterHolder holder = new FilterHolder(); holder.setName("security"); holder.setClassName(SecurityHeadersFilter.class.getName()); @@ -143,8 +143,7 @@ private void addSecurityHeadersFilter(ServletContextHandler ctxHandler) { params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", "DENY")); holder.setInitParameters(params); - ctxHandler.addFilter(SecurityHeadersFilter.class, PATH_SPEC_ANY, - EnumSet.allOf(DispatcherType.class)); + ctxHandler.addFilter(holder, PATH_SPEC_ANY, EnumSet.allOf(DispatcherType.class)); } // login the server principal (if using secure Hadoop) @@ -359,7 +358,7 @@ public synchronized void run() throws Exception { ctxHandler.addFilter(filter, PATH_SPEC_ANY, EnumSet.of(DispatcherType.REQUEST)); } addCSRFFilter(ctxHandler, conf); - addSecurityHeadersFilter(ctxHandler); + addSecurityHeadersFilter(ctxHandler, conf); HttpServerUtil.constrainHttpMethods(ctxHandler, servlet.getConfiguration() .getBoolean(REST_HTTP_ALLOW_OPTIONS_METHOD, REST_HTTP_ALLOW_OPTIONS_METHOD_DEFAULT)); diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java index 4ff8ce3e1d31..6ba51aeabd94 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java @@ -27,9 +27,12 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) public class SecurityHeadersFilter implements Filter { private static final Logger LOG = LoggerFactory.getLogger(SecurityHeadersFilter.class); From b28af7425570947f9b1b0234f85894d2e3f8b860 Mon Sep 17 00:00:00 2001 From: Andor Molnar Date: Mon, 18 Nov 2019 12:26:16 +0100 Subject: [PATCH 03/10] HBASE-23303. Use the same filter for both http and rest server for consistency --- .../http/ClickjackingPreventionFilter.java | 54 ------------------- .../apache/hadoop/hbase/http/HttpServer.java | 4 +- .../hbase/http}/SecurityHeadersFilter.java | 2 +- .../apache/hadoop/hbase/rest/RESTServer.java | 2 +- 4 files changed, 4 insertions(+), 58 deletions(-) delete mode 100644 hbase-http/src/main/java/org/apache/hadoop/hbase/http/ClickjackingPreventionFilter.java rename {hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter => hbase-http/src/main/java/org/apache/hadoop/hbase/http}/SecurityHeadersFilter.java (98%) diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ClickjackingPreventionFilter.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ClickjackingPreventionFilter.java deleted file mode 100644 index 7ce13012a404..000000000000 --- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ClickjackingPreventionFilter.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.hadoop.hbase.http; - -import java.io.IOException; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; - -import org.apache.hadoop.hbase.HBaseInterfaceAudience; - -import org.apache.yetus.audience.InterfaceAudience; - -@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) -public class ClickjackingPreventionFilter implements Filter { - private FilterConfig filterConfig; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - this.filterConfig = filterConfig; - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) - throws IOException, ServletException { - HttpServletResponse httpRes = (HttpServletResponse) res; - httpRes.addHeader("X-Frame-Options", filterConfig.getInitParameter("xframeoptions")); - chain.doFilter(req, res); - } - - @Override - public void destroy() { - } -} diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java index e96e057b439f..42ae08405f7c 100644 --- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java +++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java @@ -598,8 +598,8 @@ private void initializeWebServer(String name, String hostName, addGlobalFilter("safety", QuotingInputFilter.class.getName(), null); Map params = new HashMap<>(); params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", "DENY")); - addGlobalFilter("clickjackingprevention", - ClickjackingPreventionFilter.class.getName(), params); + addGlobalFilter("securityheaders", + SecurityHeadersFilter.class.getName(), params); final FilterInitializer[] initializers = getFilterInitializers(conf); if (initializers != null) { conf = new Configuration(conf); diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java similarity index 98% rename from hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java rename to hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java index 6ba51aeabd94..bbbd1309c476 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/filter/SecurityHeadersFilter.java +++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java @@ -16,7 +16,7 @@ * limitations under the License. */ -package org.apache.hadoop.hbase.rest.filter; +package org.apache.hadoop.hbase.http; import java.io.IOException; import javax.servlet.Filter; diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java index 2a315c221005..fb997ccabe0f 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java @@ -24,7 +24,7 @@ import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import org.apache.commons.lang3.ArrayUtils; -import org.apache.hadoop.hbase.rest.filter.SecurityHeadersFilter; +import org.apache.hadoop.hbase.http.SecurityHeadersFilter; import org.apache.yetus.audience.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; From 519afd1f19a2cb4f3d0e1976fe92a71dcfa7925e Mon Sep 17 00:00:00 2001 From: Andor Molnar Date: Mon, 18 Nov 2019 13:21:11 +0100 Subject: [PATCH 04/10] HBASE-23303. Added HSTS security header --- .../main/java/org/apache/hadoop/hbase/http/HttpServer.java | 5 ++++- .../org/apache/hadoop/hbase/http/SecurityHeadersFilter.java | 2 ++ .../main/java/org/apache/hadoop/hbase/rest/RESTServer.java | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java index 42ae08405f7c..972a76d15935 100644 --- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java +++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java @@ -597,7 +597,10 @@ private void initializeWebServer(String name, String hostName, addGlobalFilter("safety", QuotingInputFilter.class.getName(), null); Map params = new HashMap<>(); - params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", "DENY")); + params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", + "DENY")); + params.put("hsts", conf.get("hbase.http.filter.hsts.value", + "max-age=31536000")); addGlobalFilter("securityheaders", SecurityHeadersFilter.class.getName(), params); final FilterInitializer[] initializers = getFilterInitializers(conf); diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java index bbbd1309c476..8eb6102429dd 100644 --- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java +++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java @@ -52,6 +52,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha httpResponse.addHeader("X-XSS-Protection", "1; mode=block"); httpResponse.addHeader("X-Frame-Options", filterConfig.getInitParameter("xframeoptions")); + httpResponse.addHeader("Strict-Transport-Security", + filterConfig.getInitParameter("hsts")); chain.doFilter(request, response); } diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java index fb997ccabe0f..2f1623cdf8bd 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java @@ -142,6 +142,8 @@ private void addSecurityHeadersFilter(ServletContextHandler ctxHandler, Configur Map params = new HashMap<>(); params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", "DENY")); + params.put("hsts", conf.get("hbase.http.filter.hsts.value", + "max-age=31536000")); holder.setInitParameters(params); ctxHandler.addFilter(holder, PATH_SPEC_ANY, EnumSet.allOf(DispatcherType.class)); } From dfc3fdd48d90d82e1e323c52d6d26eec606d8ea7 Mon Sep 17 00:00:00 2001 From: Andor Molnar Date: Mon, 18 Nov 2019 13:36:34 +0100 Subject: [PATCH 05/10] HBASE-23303. Checkstyle fixes --- .../main/java/org/apache/hadoop/hbase/rest/RESTServer.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java index 2f1623cdf8bd..8550a479b938 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java @@ -19,7 +19,11 @@ package org.apache.hadoop.hbase.rest; import java.lang.management.ManagementFactory; -import java.util.*; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; From ef700ce533ea19e2dab3c86bff01d27a0096d197 Mon Sep 17 00:00:00 2001 From: Andor Molnar Date: Fri, 22 Nov 2019 14:03:40 +0100 Subject: [PATCH 06/10] HBASE-23303. Added new (last) security header: Content-Security-Policy --- .../apache/hadoop/hbase/http/HttpServer.java | 8 ++--- .../hbase/http/SecurityHeadersFilter.java | 32 +++++++++++++++---- .../apache/hadoop/hbase/rest/RESTServer.java | 7 +--- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java index 972a76d15935..845203bd4aa0 100644 --- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java +++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java @@ -596,13 +596,11 @@ private void initializeWebServer(String name, String hostName, addDefaultApps(contexts, appDir, conf); addGlobalFilter("safety", QuotingInputFilter.class.getName(), null); - Map params = new HashMap<>(); - params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", - "DENY")); - params.put("hsts", conf.get("hbase.http.filter.hsts.value", - "max-age=31536000")); + + Map params = SecurityHeadersFilter.getDefaultParameters(conf); addGlobalFilter("securityheaders", SecurityHeadersFilter.class.getName(), params); + final FilterInitializer[] initializers = getFilterInitializers(conf); if (initializers != null) { conf = new Configuration(conf); diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java index 8eb6102429dd..97ba73ccc603 100644 --- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java +++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java @@ -18,7 +18,12 @@ package org.apache.hadoop.hbase.http; -import java.io.IOException; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -26,16 +31,18 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; - -import org.apache.hadoop.hbase.HBaseInterfaceAudience; -import org.apache.yetus.audience.InterfaceAudience; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) public class SecurityHeadersFilter implements Filter { private static final Logger LOG = LoggerFactory.getLogger(SecurityHeadersFilter.class); + private static final String DEFAULT_XFRAMEOPTIONS = "DENY"; + private static final String DEFAULT_HSTS = "max-age=63072000;includeSubDomains;preload"; + private static final String DEFAULT_CSP = + "default-src https: data: 'unsafe-inline' 'unsafe-eval'"; private FilterConfig filterConfig; @Override @@ -54,10 +61,23 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha filterConfig.getInitParameter("xframeoptions")); httpResponse.addHeader("Strict-Transport-Security", filterConfig.getInitParameter("hsts")); + httpResponse.addHeader("Content-Security-Policy", + filterConfig.getInitParameter("csp")); chain.doFilter(request, response); } @Override public void destroy() { } + + public static Map getDefaultParameters(Configuration conf) { + Map params = new HashMap<>(); + params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", + DEFAULT_XFRAMEOPTIONS)); + params.put("hsts", conf.get("hbase.http.filter.hsts.value", + DEFAULT_HSTS)); + params.put("csp", conf.get("hbase.http.filter.csp.value", + DEFAULT_CSP)); + return params; + } } diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java index 8550a479b938..87588134bd63 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java @@ -21,7 +21,6 @@ import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.EnumSet; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; @@ -143,11 +142,7 @@ private void addSecurityHeadersFilter(ServletContextHandler ctxHandler, Configur FilterHolder holder = new FilterHolder(); holder.setName("security"); holder.setClassName(SecurityHeadersFilter.class.getName()); - Map params = new HashMap<>(); - params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", - "DENY")); - params.put("hsts", conf.get("hbase.http.filter.hsts.value", - "max-age=31536000")); + Map params = SecurityHeadersFilter.getDefaultParameters(conf); holder.setInitParameters(params); ctxHandler.addFilter(holder, PATH_SPEC_ANY, EnumSet.allOf(DispatcherType.class)); } From fab1bf988d7cc7152eb13241d136e3cdd7d44a6a Mon Sep 17 00:00:00 2001 From: Andor Molnar Date: Wed, 4 Dec 2019 18:11:16 +0100 Subject: [PATCH 07/10] HBASE-23303. Reverted changes to ClickjackingPreventionFilter, created separate filter for new security headers, cleared default values to be HTTP-friendly by default --- .../http/ClickjackingPreventionFilter.java | 65 +++++++++++++++++++ .../apache/hadoop/hbase/http/HttpServer.java | 8 ++- .../hbase/http/SecurityHeadersFilter.java | 40 ++++++------ .../apache/hadoop/hbase/rest/RESTServer.java | 45 +++++++------ 4 files changed, 115 insertions(+), 43 deletions(-) create mode 100644 hbase-http/src/main/java/org/apache/hadoop/hbase/http/ClickjackingPreventionFilter.java diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ClickjackingPreventionFilter.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ClickjackingPreventionFilter.java new file mode 100644 index 000000000000..0f0c7150c417 --- /dev/null +++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/ClickjackingPreventionFilter.java @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.http; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; + +import org.apache.yetus.audience.InterfaceAudience; + +@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) +public class ClickjackingPreventionFilter implements Filter { + private FilterConfig filterConfig; + private static final String DEFAULT_XFRAMEOPTIONS = "DENY"; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + this.filterConfig = filterConfig; + } + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + HttpServletResponse httpRes = (HttpServletResponse) res; + httpRes.addHeader("X-Frame-Options", filterConfig.getInitParameter("xframeoptions")); + chain.doFilter(req, res); + } + + @Override + public void destroy() { + } + + public static Map getDefaultParameters(Configuration conf) { + Map params = new HashMap<>(); + params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", + DEFAULT_XFRAMEOPTIONS)); + return params; + } +} diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java index 845203bd4aa0..661af4a49b47 100644 --- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java +++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java @@ -597,9 +597,13 @@ private void initializeWebServer(String name, String hostName, addGlobalFilter("safety", QuotingInputFilter.class.getName(), null); - Map params = SecurityHeadersFilter.getDefaultParameters(conf); + addGlobalFilter("clickjackingprevention", + ClickjackingPreventionFilter.class.getName(), + ClickjackingPreventionFilter.getDefaultParameters(conf)); + addGlobalFilter("securityheaders", - SecurityHeadersFilter.class.getName(), params); + SecurityHeadersFilter.class.getName(), + SecurityHeadersFilter.getDefaultParameters(conf)); final FilterInitializer[] initializers = getFilterInitializers(conf); if (initializers != null) { diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java index 97ba73ccc603..b83fef167196 100644 --- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java +++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/SecurityHeadersFilter.java @@ -18,12 +18,9 @@ package org.apache.hadoop.hbase.http; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.hbase.HBaseInterfaceAudience; -import org.apache.yetus.audience.InterfaceAudience; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -31,18 +28,19 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.yetus.audience.InterfaceAudience; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG) public class SecurityHeadersFilter implements Filter { private static final Logger LOG = LoggerFactory.getLogger(SecurityHeadersFilter.class); - private static final String DEFAULT_XFRAMEOPTIONS = "DENY"; - private static final String DEFAULT_HSTS = "max-age=63072000;includeSubDomains;preload"; - private static final String DEFAULT_CSP = - "default-src https: data: 'unsafe-inline' 'unsafe-eval'"; + private static final String DEFAULT_HSTS = ""; + private static final String DEFAULT_CSP = ""; private FilterConfig filterConfig; @Override @@ -57,12 +55,14 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.addHeader("X-Content-Type-Options", "nosniff"); httpResponse.addHeader("X-XSS-Protection", "1; mode=block"); - httpResponse.addHeader("X-Frame-Options", - filterConfig.getInitParameter("xframeoptions")); - httpResponse.addHeader("Strict-Transport-Security", - filterConfig.getInitParameter("hsts")); - httpResponse.addHeader("Content-Security-Policy", - filterConfig.getInitParameter("csp")); + String hsts = filterConfig.getInitParameter("hsts"); + if (StringUtils.isNotBlank(hsts)) { + httpResponse.addHeader("Strict-Transport-Security", hsts); + } + String csp = filterConfig.getInitParameter("csp"); + if (StringUtils.isNotBlank(csp)) { + httpResponse.addHeader("Content-Security-Policy", csp); + } chain.doFilter(request, response); } @@ -72,8 +72,6 @@ public void destroy() { public static Map getDefaultParameters(Configuration conf) { Map params = new HashMap<>(); - params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", - DEFAULT_XFRAMEOPTIONS)); params.put("hsts", conf.get("hbase.http.filter.hsts.value", DEFAULT_HSTS)); params.put("csp", conf.get("hbase.http.filter.csp.value", diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java index 87588134bd63..b1a1b2b48a3b 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java @@ -24,54 +24,50 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ArrayBlockingQueue; - -import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; +import javax.servlet.DispatcherType; import org.apache.commons.lang3.ArrayUtils; -import org.apache.hadoop.hbase.http.SecurityHeadersFilter; -import org.apache.yetus.audience.InterfaceAudience; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.HBaseInterfaceAudience; +import org.apache.hadoop.hbase.http.ClickjackingPreventionFilter; +import org.apache.hadoop.hbase.http.HttpServerUtil; import org.apache.hadoop.hbase.http.InfoServer; +import org.apache.hadoop.hbase.http.SecurityHeadersFilter; import org.apache.hadoop.hbase.log.HBaseMarkers; import org.apache.hadoop.hbase.rest.filter.AuthFilter; import org.apache.hadoop.hbase.rest.filter.GzipFilter; import org.apache.hadoop.hbase.rest.filter.RestCsrfPreventionFilter; import org.apache.hadoop.hbase.security.UserProvider; import org.apache.hadoop.hbase.util.DNS; -import org.apache.hadoop.hbase.http.HttpServerUtil; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.hbase.util.ReflectionUtils; import org.apache.hadoop.hbase.util.Strings; import org.apache.hadoop.hbase.util.VersionInfo; - import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; import org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter; import org.apache.hbase.thirdparty.org.apache.commons.cli.Options; import org.apache.hbase.thirdparty.org.apache.commons.cli.ParseException; import org.apache.hbase.thirdparty.org.apache.commons.cli.PosixParser; - +import org.apache.yetus.audience.InterfaceAudience; import org.eclipse.jetty.http.HttpVersion; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.HttpConnectionFactory; -import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.server.HttpConfiguration; -import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.SecureRequestCustomizer; -import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.eclipse.jetty.jmx.MBeanContainer; -import org.eclipse.jetty.servlet.FilterHolder; - import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.servlet.ServletContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import javax.servlet.DispatcherType; +import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; /** * Main class for launching REST gateway as a servlet hosted by Jetty. @@ -138,12 +134,20 @@ void addCSRFFilter(ServletContextHandler ctxHandler, Configuration conf) { } } + private void addClickjackingPreventionFilter(ServletContextHandler ctxHandler, + Configuration conf) { + FilterHolder holder = new FilterHolder(); + holder.setName("clickjackingprevention"); + holder.setClassName(ClickjackingPreventionFilter.class.getName()); + holder.setInitParameters(ClickjackingPreventionFilter.getDefaultParameters(conf)); + ctxHandler.addFilter(holder, PATH_SPEC_ANY, EnumSet.allOf(DispatcherType.class)); + } + private void addSecurityHeadersFilter(ServletContextHandler ctxHandler, Configuration conf) { FilterHolder holder = new FilterHolder(); - holder.setName("security"); + holder.setName("securityheaders"); holder.setClassName(SecurityHeadersFilter.class.getName()); - Map params = SecurityHeadersFilter.getDefaultParameters(conf); - holder.setInitParameters(params); + holder.setInitParameters(SecurityHeadersFilter.getDefaultParameters(conf)); ctxHandler.addFilter(holder, PATH_SPEC_ANY, EnumSet.allOf(DispatcherType.class)); } @@ -359,6 +363,7 @@ public synchronized void run() throws Exception { ctxHandler.addFilter(filter, PATH_SPEC_ANY, EnumSet.of(DispatcherType.REQUEST)); } addCSRFFilter(ctxHandler, conf); + addClickjackingPreventionFilter(ctxHandler, conf); addSecurityHeadersFilter(ctxHandler, conf); HttpServerUtil.constrainHttpMethods(ctxHandler, servlet.getConfiguration() .getBoolean(REST_HTTP_ALLOW_OPTIONS_METHOD, REST_HTTP_ALLOW_OPTIONS_METHOD_DEFAULT)); From 158d60753b6617ab36dc649d8c25758b5ef51c5b Mon Sep 17 00:00:00 2001 From: Andor Molnar Date: Thu, 5 Dec 2019 13:34:42 +0100 Subject: [PATCH 08/10] HBASE-23303. Checkstyle fix --- .../org/apache/hadoop/hbase/rest/RESTServer.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java index b1a1b2b48a3b..42dcf7e2235e 100644 --- a/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java +++ b/hbase-rest/src/main/java/org/apache/hadoop/hbase/rest/RESTServer.java @@ -18,6 +18,7 @@ package org.apache.hadoop.hbase.rest; +import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.EnumSet; @@ -43,12 +44,6 @@ import org.apache.hadoop.hbase.util.ReflectionUtils; import org.apache.hadoop.hbase.util.Strings; import org.apache.hadoop.hbase.util.VersionInfo; -import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; -import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; -import org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter; -import org.apache.hbase.thirdparty.org.apache.commons.cli.Options; -import org.apache.hbase.thirdparty.org.apache.commons.cli.ParseException; -import org.apache.hbase.thirdparty.org.apache.commons.cli.PosixParser; import org.apache.yetus.audience.InterfaceAudience; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.jmx.MBeanContainer; @@ -67,7 +62,12 @@ import org.glassfish.jersey.servlet.ServletContainer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; +import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; +import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine; +import org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter; +import org.apache.hbase.thirdparty.org.apache.commons.cli.Options; +import org.apache.hbase.thirdparty.org.apache.commons.cli.ParseException; +import org.apache.hbase.thirdparty.org.apache.commons.cli.PosixParser; /** * Main class for launching REST gateway as a servlet hosted by Jetty. From 19e9cb11b828644a7dbbd70dd59d545e1f21e444 Mon Sep 17 00:00:00 2001 From: Andor Molnar Date: Thu, 5 Dec 2019 15:10:31 +0100 Subject: [PATCH 09/10] HBASE-23303. Added unit tests --- .../hbase/rest/TestSecurityHeadersFilter.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecurityHeadersFilter.java diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecurityHeadersFilter.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecurityHeadersFilter.java new file mode 100644 index 000000000000..eca0b655f456 --- /dev/null +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecurityHeadersFilter.java @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.rest; + +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; +import org.apache.hadoop.hbase.HBaseClassTestRule; +import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.rest.client.Client; +import org.apache.hadoop.hbase.rest.client.Cluster; +import org.apache.hadoop.hbase.rest.client.Response; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.testclassification.RestTests; +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category({RestTests.class, MediumTests.class}) +public class TestSecurityHeadersFilter { + + @ClassRule + public static final HBaseClassTestRule CLASS_RULE = + HBaseClassTestRule.forClass(TestSecurityHeadersFilter.class); + + private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); + private static final HBaseRESTTestingUtility REST_TEST_UTIL = + new HBaseRESTTestingUtility(); + private static Client client; + + @After + public void tearDown() throws Exception { + REST_TEST_UTIL.shutdownServletContainer(); + TEST_UTIL.shutdownMiniCluster(); + } + + @Test + public void testDefaultValues() throws Exception { + TEST_UTIL.startMiniCluster(); + REST_TEST_UTIL.startServletContainer(TEST_UTIL.getConfiguration()); + client = new Client(new Cluster().add("localhost", + REST_TEST_UTIL.getServletPort())); + + String path = "/version/cluster"; + Response response = client.get(path); + assertThat(response.getCode(), equalTo(200)); + + assertThat("Header 'X-Content-Type-Options' is missing from Rest response", + response.getHeader("X-Content-Type-Options"), is(not((String)null))); + assertThat("Header 'X-Content-Type-Options' has invalid default value", + response.getHeader("X-Content-Type-Options"), equalTo("nosniff")); + + assertThat("Header 'X-XSS-Protection' is missing from Rest response", + response.getHeader("X-XSS-Protection"), is(not((String)null))); + assertThat("Header 'X-XSS-Protection' has invalid default value", + response.getHeader("X-XSS-Protection"), equalTo("1; mode=block")); + + assertThat("Header 'Strict-Transport-Security' should be missing from Rest response," + + "but it's present", + response.getHeader("Strict-Transport-Security"), is((String)null)); + assertThat("Header 'Content-Security-Policy' should be missing from Rest response," + + "but it's present", + response.getHeader("Content-Security-Policy"), is((String)null)); + } + + @Test + public void testHstsAndCspSettings() throws Exception { + TEST_UTIL.getConfiguration().set("hbase.http.filter.hsts.value", + "max-age=63072000;includeSubDomains;preload"); + TEST_UTIL.getConfiguration().set("hbase.http.filter.csp.value", + "default-src https: data: 'unsafe-inline' 'unsafe-eval'"); + TEST_UTIL.startMiniCluster(); + REST_TEST_UTIL.startServletContainer(TEST_UTIL.getConfiguration()); + client = new Client(new Cluster().add("localhost", + REST_TEST_UTIL.getServletPort())); + + String path = "/version/cluster"; + Response response = client.get(path); + assertThat(response.getCode(), equalTo(200)); + + assertThat("Header 'Strict-Transport-Security' is missing from Rest response", + response.getHeader("Strict-Transport-Security"), is(not((String)null))); + assertThat("Header 'Strict-Transport-Security' has invalid default value", + response.getHeader("Strict-Transport-Security"), + equalTo("max-age=63072000;includeSubDomains;preload")); + + assertThat("Header 'Content-Security-Policy' is missing from Rest response", + response.getHeader("Content-Security-Policy"), is(not((String)null))); + assertThat("Header 'Content-Security-Policy' has invalid default value", + response.getHeader("Content-Security-Policy"), + equalTo("default-src https: data: 'unsafe-inline' 'unsafe-eval'")); + } +} From 47ec91ba61d39a4dcc5d923dc678a57235cbaa21 Mon Sep 17 00:00:00 2001 From: Andor Molnar Date: Fri, 6 Dec 2019 14:34:18 +0100 Subject: [PATCH 10/10] HBASE-23303. Added unit tests to hbase-http project --- .../hbase/http/TestSecurityHeadersFilter.java | 106 ++++++++++++++++++ .../hbase/rest/TestSecurityHeadersFilter.java | 4 +- 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestSecurityHeadersFilter.java diff --git a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestSecurityHeadersFilter.java b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestSecurityHeadersFilter.java new file mode 100644 index 000000000000..41a1235baaf4 --- /dev/null +++ b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestSecurityHeadersFilter.java @@ -0,0 +1,106 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.http; + +import static org.apache.hadoop.hbase.http.HttpServerFunctionalTest.createTestServer; +import static org.apache.hadoop.hbase.http.HttpServerFunctionalTest.getServerURL; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertThat; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseClassTestRule; +import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.hamcrest.core.Is; +import org.hamcrest.core.IsEqual; +import org.junit.After; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +@Category({HttpServerFunctionalTest.class, MediumTests.class}) +public class TestSecurityHeadersFilter { + private static URL baseUrl; + private HttpServer http; + + @ClassRule + public static final HBaseClassTestRule CLASS_RULE = + HBaseClassTestRule.forClass(TestSecurityHeadersFilter.class); + + @After + public void tearDown() throws Exception { + http.stop(); + } + + @Test + public void testDefaultValues() throws Exception { + http = createTestServer(); + http.start(); + baseUrl = getServerURL(http); + + URL url = new URL(baseUrl, "/"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + assertThat(conn.getResponseCode(), equalTo(HttpURLConnection.HTTP_OK)); + + assertThat("Header 'X-Content-Type-Options' is missing", + conn.getHeaderField("X-Content-Type-Options"), is(not((String)null))); + assertThat(conn.getHeaderField("X-Content-Type-Options"), equalTo("nosniff")); + assertThat("Header 'X-XSS-Protection' is missing", + conn.getHeaderField("X-XSS-Protection"), is(not((String)null))); + assertThat("Header 'X-XSS-Protection' has invalid value", + conn.getHeaderField("X-XSS-Protection"), equalTo("1; mode=block")); + + assertThat("Header 'Strict-Transport-Security' should be missing from response," + + "but it's present", + conn.getHeaderField("Strict-Transport-Security"), is((String)null)); + assertThat("Header 'Content-Security-Policy' should be missing from response," + + "but it's present", + conn.getHeaderField("Content-Security-Policy"), is((String)null)); + } + + @Test + public void testHstsAndCspSettings() throws IOException { + Configuration conf = new Configuration(); + conf.set("hbase.http.filter.hsts.value", + "max-age=63072000;includeSubDomains;preload"); + conf.set("hbase.http.filter.csp.value", + "default-src https: data: 'unsafe-inline' 'unsafe-eval'"); + http = createTestServer(conf); + http.start(); + baseUrl = getServerURL(http); + + URL url = new URL(baseUrl, "/"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + assertThat(conn.getResponseCode(), equalTo(HttpURLConnection.HTTP_OK)); + + assertThat("Header 'Strict-Transport-Security' is missing from Rest response", + conn.getHeaderField("Strict-Transport-Security"), Is.is(not((String)null))); + assertThat("Header 'Strict-Transport-Security' has invalid value", + conn.getHeaderField("Strict-Transport-Security"), + IsEqual.equalTo("max-age=63072000;includeSubDomains;preload")); + + assertThat("Header 'Content-Security-Policy' is missing from Rest response", + conn.getHeaderField("Content-Security-Policy"), Is.is(not((String)null))); + assertThat("Header 'Content-Security-Policy' has invalid value", + conn.getHeaderField("Content-Security-Policy"), + IsEqual.equalTo("default-src https: data: 'unsafe-inline' 'unsafe-eval'")); + } +} diff --git a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecurityHeadersFilter.java b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecurityHeadersFilter.java index eca0b655f456..bf0c69502d52 100644 --- a/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecurityHeadersFilter.java +++ b/hbase-rest/src/test/java/org/apache/hadoop/hbase/rest/TestSecurityHeadersFilter.java @@ -97,13 +97,13 @@ public void testHstsAndCspSettings() throws Exception { assertThat("Header 'Strict-Transport-Security' is missing from Rest response", response.getHeader("Strict-Transport-Security"), is(not((String)null))); - assertThat("Header 'Strict-Transport-Security' has invalid default value", + assertThat("Header 'Strict-Transport-Security' has invalid value", response.getHeader("Strict-Transport-Security"), equalTo("max-age=63072000;includeSubDomains;preload")); assertThat("Header 'Content-Security-Policy' is missing from Rest response", response.getHeader("Content-Security-Policy"), is(not((String)null))); - assertThat("Header 'Content-Security-Policy' has invalid default value", + assertThat("Header 'Content-Security-Policy' has invalid value", response.getHeader("Content-Security-Policy"), equalTo("default-src https: data: 'unsafe-inline' 'unsafe-eval'")); }