From 05b6a1a06aff41322dd08ee8a0b4df04fd84d6da Mon Sep 17 00:00:00 2001 From: Benjamin Teke Date: Sat, 24 Jul 2021 05:44:21 +0200 Subject: [PATCH] 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 --- .../apache/hadoop/yarn/webapp/WebApps.java | 31 ++-- .../yarn/webapp/TestRMWithXFSFilter.java | 159 +++++++----------- 2 files changed, 78 insertions(+), 112 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java index 2f02fd7a076..a088f4bf39f 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java @@ -41,7 +41,6 @@ import org.apache.hadoop.http.HttpServer2; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.authorize.AccessControlList; 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.conf.YarnConfiguration; import org.apache.hadoop.yarn.webapp.util.WebAppUtils; @@ -325,6 +324,19 @@ public class WebApps { YarnConfiguration.YARN_ADMIN_ACL, YarnConfiguration.DEFAULT_YARN_ADMIN_ACL))) .setPathSpec(pathList.toArray(new String[0])); + + // Set the X-FRAME-OPTIONS header, use the HttpServer2 default if + // the header value is not specified + Map 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. IntegerRanges ranges = null; if (portRangeConfigKey != null) { @@ -395,15 +407,6 @@ public class WebApps { 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", GuiceFilter.class.getName(), null, new String[] { "/*" }); @@ -489,14 +492,6 @@ public class WebApps { HttpServer2.defineFilter(ui2Context, restCsrfClassName, 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() { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithXFSFilter.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithXFSFilter.java index 2f7ecde2fb7..2054754760d 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithXFSFilter.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithXFSFilter.java @@ -18,126 +18,97 @@ 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.security.http.XFrameOptionsFilter; +import org.apache.hadoop.http.HttpServer2; import org.apache.hadoop.yarn.conf.YarnConfiguration; 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.fifo.FifoScheduler; -import org.apache.hadoop.yarn.server.resourcemanager.webapp.JAXBContextResolver; -import org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWebServices; -import org.junit.Before; +import org.junit.After; +import org.junit.Assert; import org.junit.Test; -import java.util.HashMap; -import java.util.Map; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; /** * Used TestRMWebServices as an example of web invocations of RM and added * test for XFS Filter. */ -public class TestRMWithXFSFilter extends JerseyTestBase { - +public class TestRMWithXFSFilter { private static MockRM rm; - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - } - - public TestRMWithXFSFilter() { - super(new WebAppDescriptor.Builder( - "org.apache.hadoop.yarn.server.resourcemanager.webapp") - .contextListenerClass(GuiceServletConfig.class) - .filterClass(com.google.inject.servlet.GuiceFilter.class) - .contextPath("jersey-guice-filter").servletPath("/").build()); + private void createMockRm(final Boolean xfsEnabled, + final String xfsHeaderValue) { + Configuration conf = new Configuration(); + conf.setClass(YarnConfiguration.RM_SCHEDULER, FifoScheduler.class, + ResourceScheduler.class); + conf.setBoolean("mockrm.webapp.enabled", true); + if (xfsEnabled != null) { + conf.setBoolean(YarnConfiguration.YARN_XFS_ENABLED, xfsEnabled); + } + if (xfsHeaderValue != null) { + conf.setStrings(YarnConfiguration.RM_XFS_OPTIONS, xfsHeaderValue); + } + rm = new MockRM(conf); + rm.start(); } @Test - public void testDefaultBehavior() throws Exception { - createInjector(); + public void testXFrameOptionsDefaultBehaviour() throws Exception { + createMockRm(null, null); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("info").accept("application/xml") - .get(ClientResponse.class); - assertEquals("Should have received DENY x-frame options header", - "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 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); - } - })); + URL url = new URL("http://localhost:8088/ws/v1/cluster/info"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS"); + Assert.assertTrue(xfoHeader.endsWith(HttpServer2.XFrameOption + .SAMEORIGIN.toString())); } @Test - public void testSameOrigin() throws Exception { - createInjector("SAMEORIGIN"); + public void testXFrameOptionsExplicitlyEnabled() throws Exception { + createMockRm(true, HttpServer2.XFrameOption + .SAMEORIGIN.toString()); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("info").accept("application/xml") - .get(ClientResponse.class); - assertEquals("Should have received SAMEORIGIN x-frame options header", - "SAMEORIGIN", - response.getHeaders().get(XFrameOptionsFilter.X_FRAME_OPTIONS).get(0)); + URL url = new URL("http://localhost:8088/ws/v1/cluster/info"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS"); + Assert.assertTrue(xfoHeader.endsWith(HttpServer2.XFrameOption + .SAMEORIGIN.toString())); } @Test - public void testExplicitlyDisabled() throws Exception { - createInjector(null, true); + public void testXFrameOptionsEnabledDefaultApps() throws Exception { + createMockRm(true, HttpServer2.XFrameOption + .SAMEORIGIN.toString()); - WebResource r = resource(); - ClientResponse response = r.path("ws").path("v1").path("cluster") - .path("info").accept("application/xml") - .get(ClientResponse.class); - assertFalse("Should have not received x-frame options header", - response.getHeaders().get(XFrameOptionsFilter.X_FRAME_OPTIONS) == null); + URL url = new URL("http://localhost:8088/logs"); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS"); + Assert.assertTrue(xfoHeader.endsWith(HttpServer2.XFrameOption + .SAMEORIGIN.toString())); } + @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(); + } }