HBASE-23303 Add default security headers if SSL is enabled (#4128)
Signed-off-by: Balazs Meszaros <meszibalu@apache.org>
This commit is contained in:
parent
4bea1e8c37
commit
87f8d9ac4e
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue