HADOOP-15457. Add Security-Related HTTP Response Header in WEBUIs. (kanwaljeets via rkanter)
This commit is contained in:
parent
bc6d9d4c79
commit
aa23d49fc8
|
@ -34,6 +34,8 @@ import java.util.HashMap;
|
||||||
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 @@ public final class HttpServer2 implements FilterContainer {
|
||||||
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 final class HttpServer2 implements FilterContainer {
|
||||||
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 final class HttpServer2 implements FilterContainer {
|
||||||
@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 final class HttpServer2 implements FilterContainer {
|
||||||
} 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,8 +1617,19 @@ public final class HttpServer2 implements FilterContainer {
|
||||||
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.
|
||||||
|
@ -1651,4 +1666,30 @@ public final class HttpServer2 implements FilterContainer {
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -701,4 +701,65 @@ public class TestHttpServer extends HttpServerFunctionalTest {
|
||||||
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue