YARN-10833. Set the X-FRAME-OPTIONS header for the default contexts. (#3203)

* YARN-10833. Set the X-FRAME-OPTIONS header for the default contexts.

* fixup: YARN-10833. Set the X-FRAME-OPTIONS header for the default contexts.

Co-authored-by: Benjamin Teke <bteke@cloudera.com>
This commit is contained in:
Benjamin Teke 2021-07-24 05:44:21 +02:00 committed by GitHub
parent 63dfd84947
commit 05b6a1a06a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 78 additions and 112 deletions

View File

@ -41,7 +41,6 @@ import org.apache.hadoop.http.HttpServer2;
import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authorize.AccessControlList; import org.apache.hadoop.security.authorize.AccessControlList;
import org.apache.hadoop.security.http.RestCsrfPreventionFilter; import org.apache.hadoop.security.http.RestCsrfPreventionFilter;
import org.apache.hadoop.security.http.XFrameOptionsFilter;
import org.apache.hadoop.yarn.api.ApplicationClientProtocol; import org.apache.hadoop.yarn.api.ApplicationClientProtocol;
import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.webapp.util.WebAppUtils; import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
@ -325,6 +324,19 @@ public class WebApps {
YarnConfiguration.YARN_ADMIN_ACL, YarnConfiguration.YARN_ADMIN_ACL,
YarnConfiguration.DEFAULT_YARN_ADMIN_ACL))) YarnConfiguration.DEFAULT_YARN_ADMIN_ACL)))
.setPathSpec(pathList.toArray(new String[0])); .setPathSpec(pathList.toArray(new String[0]));
// Set the X-FRAME-OPTIONS header, use the HttpServer2 default if
// the header value is not specified
Map<String, String> xfsParameters =
getConfigParameters(xfsConfigPrefix);
if (xfsParameters != null) {
String xFrameOptions = xfsParameters.get("xframe-options");
if (xFrameOptions != null) {
builder.configureXFrame(hasXFSEnabled())
.setXFrameOption(xFrameOptions);
}
}
// Get port ranges from config. // Get port ranges from config.
IntegerRanges ranges = null; IntegerRanges ranges = null;
if (portRangeConfigKey != null) { if (portRangeConfigKey != null) {
@ -395,15 +407,6 @@ public class WebApps {
new String[] {"/*"}); new String[] {"/*"});
} }
params = getConfigParameters(xfsConfigPrefix);
if (hasXFSEnabled()) {
String xfsClassName = XFrameOptionsFilter.class.getName();
HttpServer2.defineFilter(server.getWebAppContext(), xfsClassName,
xfsClassName, params,
new String[] {"/*"});
}
HttpServer2.defineFilter(server.getWebAppContext(), "guice", HttpServer2.defineFilter(server.getWebAppContext(), "guice",
GuiceFilter.class.getName(), null, new String[] { "/*" }); GuiceFilter.class.getName(), null, new String[] { "/*" });
@ -489,14 +492,6 @@ public class WebApps {
HttpServer2.defineFilter(ui2Context, restCsrfClassName, HttpServer2.defineFilter(ui2Context, restCsrfClassName,
restCsrfClassName, params, new String[]{"/*"}); restCsrfClassName, params, new String[]{"/*"});
} }
params = getConfigParameters(xfsConfigPrefix);
if (hasXFSEnabled()) {
String xfsClassName = XFrameOptionsFilter.class.getName();
HttpServer2.defineFilter(ui2Context, xfsClassName, xfsClassName, params,
new String[]{"/*"});
}
} }
private String inferHostClass() { private String inferHostClass() {

View File

@ -18,126 +18,97 @@
package org.apache.hadoop.yarn.webapp; package org.apache.hadoop.yarn.webapp;
import com.google.inject.Guice;
import com.google.inject.servlet.ServletModule;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import com.sun.jersey.test.framework.WebAppDescriptor;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.security.http.XFrameOptionsFilter; import org.apache.hadoop.http.HttpServer2;
import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.server.resourcemanager.MockRM; import org.apache.hadoop.yarn.server.resourcemanager.MockRM;
import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler;
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.JAXBContextResolver; import org.junit.After;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWebServices; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.util.HashMap; import java.io.IOException;
import java.util.Map; import java.net.HttpURLConnection;
import java.net.URL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/** /**
* Used TestRMWebServices as an example of web invocations of RM and added * Used TestRMWebServices as an example of web invocations of RM and added
* test for XFS Filter. * test for XFS Filter.
*/ */
public class TestRMWithXFSFilter extends JerseyTestBase { public class TestRMWithXFSFilter {
private static MockRM rm; private static MockRM rm;
@Before private void createMockRm(final Boolean xfsEnabled,
@Override final String xfsHeaderValue) {
public void setUp() throws Exception { Configuration conf = new Configuration();
super.setUp(); conf.setClass(YarnConfiguration.RM_SCHEDULER, FifoScheduler.class,
} ResourceScheduler.class);
conf.setBoolean("mockrm.webapp.enabled", true);
public TestRMWithXFSFilter() { if (xfsEnabled != null) {
super(new WebAppDescriptor.Builder( conf.setBoolean(YarnConfiguration.YARN_XFS_ENABLED, xfsEnabled);
"org.apache.hadoop.yarn.server.resourcemanager.webapp") }
.contextListenerClass(GuiceServletConfig.class) if (xfsHeaderValue != null) {
.filterClass(com.google.inject.servlet.GuiceFilter.class) conf.setStrings(YarnConfiguration.RM_XFS_OPTIONS, xfsHeaderValue);
.contextPath("jersey-guice-filter").servletPath("/").build()); }
rm = new MockRM(conf);
rm.start();
} }
@Test @Test
public void testDefaultBehavior() throws Exception { public void testXFrameOptionsDefaultBehaviour() throws Exception {
createInjector(); createMockRm(null, null);
WebResource r = resource(); URL url = new URL("http://localhost:8088/ws/v1/cluster/info");
ClientResponse response = r.path("ws").path("v1").path("cluster") HttpURLConnection conn = (HttpURLConnection) url.openConnection();
.path("info").accept("application/xml") String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS");
.get(ClientResponse.class); Assert.assertTrue(xfoHeader.endsWith(HttpServer2.XFrameOption
assertEquals("Should have received DENY x-frame options header", .SAMEORIGIN.toString()));
"DENY",
response.getHeaders().get(XFrameOptionsFilter.X_FRAME_OPTIONS).get(0));
}
protected void createInjector(String headerValue) {
createInjector(headerValue, false);
}
protected void createInjector() {
createInjector(null, false);
}
protected void createInjector(final String headerValue,
final boolean explicitlyDisabled) {
GuiceServletConfig.setInjector(Guice.createInjector(new ServletModule() {
@Override
protected void configureServlets() {
bind(JAXBContextResolver.class);
bind(RMWebServices.class);
bind(GenericExceptionHandler.class);
Configuration conf = new Configuration();
conf.setClass(YarnConfiguration.RM_SCHEDULER, FifoScheduler.class,
ResourceScheduler.class);
rm = new MockRM(conf);
bind(ResourceManager.class).toInstance(rm);
serve("/*").with(GuiceContainer.class);
XFrameOptionsFilter xfsFilter = new XFrameOptionsFilter();
Map<String, String> initParams = new HashMap<>();
if (headerValue != null) {
initParams.put(XFrameOptionsFilter.CUSTOM_HEADER_PARAM, headerValue);
}
if (explicitlyDisabled) {
initParams.put(
"xframe-options-enabled", "false");
}
filter("/*").through(xfsFilter, initParams);
}
}));
} }
@Test @Test
public void testSameOrigin() throws Exception { public void testXFrameOptionsExplicitlyEnabled() throws Exception {
createInjector("SAMEORIGIN"); createMockRm(true, HttpServer2.XFrameOption
.SAMEORIGIN.toString());
WebResource r = resource(); URL url = new URL("http://localhost:8088/ws/v1/cluster/info");
ClientResponse response = r.path("ws").path("v1").path("cluster") HttpURLConnection conn = (HttpURLConnection) url.openConnection();
.path("info").accept("application/xml") String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS");
.get(ClientResponse.class); Assert.assertTrue(xfoHeader.endsWith(HttpServer2.XFrameOption
assertEquals("Should have received SAMEORIGIN x-frame options header", .SAMEORIGIN.toString()));
"SAMEORIGIN",
response.getHeaders().get(XFrameOptionsFilter.X_FRAME_OPTIONS).get(0));
} }
@Test @Test
public void testExplicitlyDisabled() throws Exception { public void testXFrameOptionsEnabledDefaultApps() throws Exception {
createInjector(null, true); createMockRm(true, HttpServer2.XFrameOption
.SAMEORIGIN.toString());
WebResource r = resource(); URL url = new URL("http://localhost:8088/logs");
ClientResponse response = r.path("ws").path("v1").path("cluster") HttpURLConnection conn = (HttpURLConnection) url.openConnection();
.path("info").accept("application/xml") String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS");
.get(ClientResponse.class); Assert.assertTrue(xfoHeader.endsWith(HttpServer2.XFrameOption
assertFalse("Should have not received x-frame options header", .SAMEORIGIN.toString()));
response.getHeaders().get(XFrameOptionsFilter.X_FRAME_OPTIONS) == null);
} }
@Test
public void testXFrameOptionsDisabled() throws Exception {
createMockRm(false, null);
URL url = new URL("http://localhost:8088/ws/v1/cluster/info");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS");
Assert.assertNull("Unexpected X-FRAME-OPTION in header", xfoHeader);
}
@Test
public void testXFrameOptionsIllegalOption() {
IllegalArgumentException e = Assert.assertThrows(
IllegalArgumentException.class,
() -> createMockRm(true, "otherValue"));
}
@After
public void tearDown() throws IOException {
rm.close();
}
} }