HADOOP-15457. Add Security-Related HTTP Response Header in WEBUIs. (kanwaljeets via rkanter)

This commit is contained in:
Robert Kanter 2018-05-23 10:23:17 -07:00
parent bc6d9d4c79
commit aa23d49fc8
2 changed files with 121 additions and 19 deletions

View File

@ -34,6 +34,8 @@
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter; import javax.servlet.Filter;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
@ -172,10 +174,16 @@ public final class HttpServer2 implements FilterContainer {
private final SignerSecretProvider secretProvider; private final SignerSecretProvider secretProvider;
private XFrameOption xFrameOption; private XFrameOption xFrameOption;
private boolean xFrameOptionIsEnabled; private boolean xFrameOptionIsEnabled;
private static final String X_FRAME_VALUE = "xFrameOption"; public static final String HTTP_HEADER_PREFIX = "hadoop.http.header.";
private static final String X_FRAME_ENABLED = "X_FRAME_ENABLED"; 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. * Class to construct instances of HTTP server with specific options.
*/ */
@ -574,10 +582,7 @@ private void initializeWebServer(String name, String hostName,
addDefaultApps(contexts, appDir, conf); addDefaultApps(contexts, appDir, conf);
webServer.setHandler(handlers); webServer.setHandler(handlers);
Map<String, String> xFrameParams = new HashMap<>(); Map<String, String> xFrameParams = setHeaders(conf);
xFrameParams.put(X_FRAME_ENABLED,
String.valueOf(this.xFrameOptionIsEnabled));
xFrameParams.put(X_FRAME_VALUE, this.xFrameOption.toString());
addGlobalFilter("safety", QuotingInputFilter.class.getName(), xFrameParams); addGlobalFilter("safety", QuotingInputFilter.class.getName(), xFrameParams);
final FilterInitializer[] initializers = getFilterInitializers(conf); final FilterInitializer[] initializers = getFilterInitializers(conf);
if (initializers != null) { if (initializers != null) {
@ -1475,9 +1480,11 @@ public void doGet(HttpServletRequest request, HttpServletResponse response)
public static class QuotingInputFilter implements Filter { public static class QuotingInputFilter implements Filter {
private FilterConfig config; private FilterConfig config;
private Map<String, String> headerMap;
public static class RequestQuoter extends HttpServletRequestWrapper { public static class RequestQuoter extends HttpServletRequestWrapper {
private final HttpServletRequest rawRequest; private final HttpServletRequest rawRequest;
public RequestQuoter(HttpServletRequest rawRequest) { public RequestQuoter(HttpServletRequest rawRequest) {
super(rawRequest); super(rawRequest);
this.rawRequest = rawRequest; this.rawRequest = rawRequest;
@ -1566,6 +1573,7 @@ public String getServerName() {
@Override @Override
public void init(FilterConfig config) throws ServletException { public void init(FilterConfig config) throws ServletException {
this.config = config; this.config = config;
initHttpHeaderMap();
} }
@Override @Override
@ -1593,11 +1601,7 @@ public void doFilter(ServletRequest request,
} else if (mime.startsWith("application/xml")) { } else if (mime.startsWith("application/xml")) {
httpResponse.setContentType("text/xml; charset=utf-8"); httpResponse.setContentType("text/xml; charset=utf-8");
} }
headerMap.forEach((k, v) -> httpResponse.addHeader(k, v));
if(Boolean.valueOf(this.config.getInitParameter(X_FRAME_ENABLED))) {
httpResponse.addHeader("X-FRAME-OPTIONS",
this.config.getInitParameter(X_FRAME_VALUE));
}
chain.doFilter(quoted, httpResponse); chain.doFilter(quoted, httpResponse);
} }
@ -1613,14 +1617,25 @@ private String inferMimeType(ServletRequest request) {
return (mime == null) ? null : mime; 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
* The X-FRAME-OPTIONS header in HTTP response to mitigate clickjacking * attack.
* attack. */
*/
public enum XFrameOption { public enum XFrameOption {
DENY("DENY") , SAMEORIGIN ("SAMEORIGIN"), ALLOWFROM ("ALLOW-FROM"); DENY("DENY"), SAMEORIGIN("SAMEORIGIN"), ALLOWFROM("ALLOW-FROM");
XFrameOption(String name) { XFrameOption(String name) {
this.name = name; this.name = name;
@ -1651,4 +1666,30 @@ private static XFrameOption getEnum(String value) {
throw new IllegalArgumentException("Unexpected value in xFrameOption."); 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;
}
} }

View File

@ -701,4 +701,65 @@ public void testBacklogSize() throws Exception
ServerConnector listener = (ServerConnector)listeners.get(0); ServerConnector listener = (ServerConnector)listeners.get(0);
assertEquals(backlogSize, listener.getAcceptQueueSize()); 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();
}
}
} }