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.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 @@ private void initializeWebServer(String name, String hostName,
|
||||
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 void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
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 String getServerName() {
|
||||
@Override
|
||||
public void init(FilterConfig config) throws ServletException {
|
||||
this.config = config;
|
||||
initHttpHeaderMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1593,11 +1601,7 @@ public void doFilter(ServletRequest request,
|
||||
} 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 @@ private String inferMimeType(ServletRequest request) {
|
||||
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 @@ private static XFrameOption getEnum(String value) {
|
||||
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 void testBacklogSize() throws Exception
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user