HBASE-23303 Add default security headers if SSL is enabled (#4128)

Signed-off-by: Balazs Meszaros <meszibalu@apache.org>
This commit is contained in:
Andor Molnár 2022-03-02 15:25:57 +01:00 committed by GitHub
parent 4bea1e8c37
commit 87f8d9ac4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 34 additions and 13 deletions

View File

@ -626,9 +626,11 @@ public class HttpServer implements FilterContainer {
ClickjackingPreventionFilter.class.getName(), ClickjackingPreventionFilter.class.getName(),
ClickjackingPreventionFilter.getDefaultParameters(conf)); ClickjackingPreventionFilter.getDefaultParameters(conf));
HttpConfig httpConfig = new HttpConfig(conf);
addGlobalFilter("securityheaders", addGlobalFilter("securityheaders",
SecurityHeadersFilter.class.getName(), SecurityHeadersFilter.class.getName(),
SecurityHeadersFilter.getDefaultParameters(conf)); SecurityHeadersFilter.getDefaultParameters(conf, httpConfig.isSecure()));
// But security needs to be enabled prior to adding the other servlets // But security needs to be enabled prior to adding the other servlets
if (authenticationEnabled) { if (authenticationEnabled) {

View File

@ -39,8 +39,8 @@ import org.slf4j.LoggerFactory;
public class SecurityHeadersFilter implements Filter { public class SecurityHeadersFilter implements Filter {
private static final Logger LOG = private static final Logger LOG =
LoggerFactory.getLogger(SecurityHeadersFilter.class); LoggerFactory.getLogger(SecurityHeadersFilter.class);
private static final String DEFAULT_HSTS = ""; private static final String DEFAULT_HSTS = "max-age=63072000;includeSubDomains;preload";
private static final String DEFAULT_CSP = ""; private static final String DEFAULT_CSP = "default-src https: data: 'unsafe-inline' 'unsafe-eval'";
private FilterConfig filterConfig; private FilterConfig filterConfig;
@Override @Override
@ -70,12 +70,10 @@ public class SecurityHeadersFilter implements Filter {
public void destroy() { public void destroy() {
} }
public static Map<String, String> getDefaultParameters(Configuration conf) { public static Map<String, String> getDefaultParameters(Configuration conf, boolean isSecure) {
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
params.put("hsts", conf.get("hbase.http.filter.hsts.value", params.put("hsts", conf.get("hbase.http.filter.hsts.value", isSecure ? DEFAULT_HSTS : ""));
DEFAULT_HSTS)); params.put("csp", conf.get("hbase.http.filter.csp.value", isSecure ? DEFAULT_CSP : ""));
params.put("csp", conf.get("hbase.http.filter.csp.value",
DEFAULT_CSP));
return params; return params;
} }
} }

View File

@ -19,9 +19,11 @@ package org.apache.hadoop.hbase.http;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.security.GeneralSecurityException;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileUtil; import org.apache.hadoop.fs.FileUtil;
@ -73,6 +75,7 @@ public class TestSSLHttpServer extends HttpServerFunctionalTest {
serverConf = HTU.getConfiguration(); serverConf = HTU.getConfiguration();
serverConf.setInt(HttpServer.HTTP_MAX_THREADS, TestHttpServer.MAX_THREADS); serverConf.setInt(HttpServer.HTTP_MAX_THREADS, TestHttpServer.MAX_THREADS);
serverConf.setBoolean(ServerConfigurationKeys.HBASE_SSL_ENABLED_KEY, true);
keystoresDir = new File(HTU.getDataTestDir("keystore").toString()); keystoresDir = new File(HTU.getDataTestDir("keystore").toString());
keystoresDir.mkdirs(); keystoresDir.mkdirs();
@ -122,6 +125,17 @@ public class TestSSLHttpServer extends HttpServerFunctionalTest {
"/echo?a=b&c<=d&e=>"))); "/echo?a=b&c<=d&e=>")));
} }
@Test
public void testSecurityHeaders() throws IOException, GeneralSecurityException {
HttpsURLConnection conn = (HttpsURLConnection) baseUrl.openConnection();
conn.setSSLSocketFactory(clientSslFactory.createSSLSocketFactory());
assertEquals(HttpsURLConnection.HTTP_OK, conn.getResponseCode());
assertEquals("max-age=63072000;includeSubDomains;preload",
conn.getHeaderField("Strict-Transport-Security"));
assertEquals("default-src https: data: 'unsafe-inline' 'unsafe-eval'",
conn.getHeaderField("Content-Security-Policy"));
}
private static String readOut(URL url) throws Exception { private static String readOut(URL url) throws Exception {
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setSSLSocketFactory(clientSslFactory.createSSLSocketFactory()); conn.setSSLSocketFactory(clientSslFactory.createSSLSocketFactory());

View File

@ -149,11 +149,12 @@ public class RESTServer implements Constants {
ctxHandler.addFilter(holder, PATH_SPEC_ANY, EnumSet.allOf(DispatcherType.class)); ctxHandler.addFilter(holder, PATH_SPEC_ANY, EnumSet.allOf(DispatcherType.class));
} }
private void addSecurityHeadersFilter(ServletContextHandler ctxHandler, Configuration conf) { private void addSecurityHeadersFilter(ServletContextHandler ctxHandler,
Configuration conf, boolean isSecure) {
FilterHolder holder = new FilterHolder(); FilterHolder holder = new FilterHolder();
holder.setName("securityheaders"); holder.setName("securityheaders");
holder.setClassName(SecurityHeadersFilter.class.getName()); holder.setClassName(SecurityHeadersFilter.class.getName());
holder.setInitParameters(SecurityHeadersFilter.getDefaultParameters(conf)); holder.setInitParameters(SecurityHeadersFilter.getDefaultParameters(conf, isSecure));
ctxHandler.addFilter(holder, PATH_SPEC_ANY, EnumSet.allOf(DispatcherType.class)); ctxHandler.addFilter(holder, PATH_SPEC_ANY, EnumSet.allOf(DispatcherType.class));
} }
@ -305,7 +306,9 @@ public class RESTServer implements Constants {
httpConfig.setSendDateHeader(false); httpConfig.setSendDateHeader(false);
ServerConnector serverConnector; ServerConnector serverConnector;
boolean isSecure = false;
if (conf.getBoolean(REST_SSL_ENABLED, false)) { if (conf.getBoolean(REST_SSL_ENABLED, false)) {
isSecure = true;
HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
httpsConfig.addCustomizer(new SecureRequestCustomizer()); httpsConfig.addCustomizer(new SecureRequestCustomizer());
@ -393,7 +396,7 @@ public class RESTServer implements Constants {
} }
addCSRFFilter(ctxHandler, conf); addCSRFFilter(ctxHandler, conf);
addClickjackingPreventionFilter(ctxHandler, conf); addClickjackingPreventionFilter(ctxHandler, conf);
addSecurityHeadersFilter(ctxHandler, conf); addSecurityHeadersFilter(ctxHandler, conf, isSecure);
HttpServerUtil.constrainHttpMethods(ctxHandler, servlet.getConfiguration() HttpServerUtil.constrainHttpMethods(ctxHandler, servlet.getConfiguration()
.getBoolean(REST_HTTP_ALLOW_OPTIONS_METHOD, REST_HTTP_ALLOW_OPTIONS_METHOD_DEFAULT)); .getBoolean(REST_HTTP_ALLOW_OPTIONS_METHOD, REST_HTTP_ALLOW_OPTIONS_METHOD_DEFAULT));

View File

@ -18,8 +18,6 @@
package org.apache.hadoop.hbase.rest; package org.apache.hadoop.hbase.rest;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.File; import java.io.File;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
@ -107,6 +105,12 @@ public class TestRESTServerSSL {
Response response = sslClient.get("/version", Constants.MIMETYPE_TEXT); Response response = sslClient.get("/version", Constants.MIMETYPE_TEXT);
assertEquals(200, response.getCode()); assertEquals(200, response.getCode());
// Default security headers
assertEquals("max-age=63072000;includeSubDomains;preload",
response.getHeader("Strict-Transport-Security"));
assertEquals("default-src https: data: 'unsafe-inline' 'unsafe-eval'",
response.getHeader("Content-Security-Policy"));
} }
@Test(expected = org.apache.http.client.ClientProtocolException.class) @Test(expected = org.apache.http.client.ClientProtocolException.class)