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.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;
}
}

View File

@ -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();
}
}
}