diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/main/java/org/apache/hadoop/mapreduce/v2/jobhistory/JHAdminConfig.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/main/java/org/apache/hadoop/mapreduce/v2/jobhistory/JHAdminConfig.java
index 5aa4671..1c79671 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/main/java/org/apache/hadoop/mapreduce/v2/jobhistory/JHAdminConfig.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/main/java/org/apache/hadoop/mapreduce/v2/jobhistory/JHAdminConfig.java
@@ -239,6 +239,14 @@
MR_HISTORY_CSRF_PREFIX + "methods-to-ignore";
/**
+ * XFS settings.
+ */
+ public static final String MR_HISTORY_XFS_PREFIX = MR_HISTORY_PREFIX +
+ "webapp.xfs-filter.";
+ public static final String MR_HISTORY_XFS_OPTIONS = MR_HISTORY_XFS_PREFIX +
+ "xframe-options";
+
+ /**
* Settings for .jhist file format.
*/
public static final String MR_HS_JHIST_FORMAT =
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/resources/mapred-default.xml b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/resources/mapred-default.xml
index b7bdcc8..bc15958 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/resources/mapred-default.xml
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/resources/mapred-default.xml
@@ -1888,4 +1888,12 @@
GET,OPTIONS,HEAD
+
+
+ Value of the xframe-options
+
+ mapreduce.jobhistory.webapp.xfs-filter.xframe-options
+ SAMEORIGIN
+
+
diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/HistoryClientService.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/HistoryClientService.java
index 2fbaade..487794c 100644
--- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/HistoryClientService.java
+++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/HistoryClientService.java
@@ -161,6 +161,7 @@ protected void initializeWebApp(Configuration conf) {
.withHttpSpnegoPrincipalKey(
JHAdminConfig.MR_WEBAPP_SPNEGO_USER_NAME_KEY)
.withCSRFProtection(JHAdminConfig.MR_HISTORY_CSRF_PREFIX)
+ .withXFSProtection(JHAdminConfig.MR_HISTORY_XFS_PREFIX)
.at(NetUtils.getHostPortString(bindAddress)).start(webApp);
String connectHost = MRWebAppUtil.getJHSWebappURLWithoutScheme(conf).split(":")[0];
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
index cfe2897..49d24de 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
@@ -2565,6 +2565,19 @@ public static boolean areNodeLabelsEnabled(
public static final String TIMELINE_CSRF_METHODS_TO_IGNORE =
TIMELINE_CSRF_PREFIX + "methods-to-ignore";
+ // RM and NM XFS props
+ public static final String XFS = "webapp.xfs-filter.";
+ public static final String YARN_XFS_ENABLED = "yarn." + XFS + "enabled" ;
+ public static final String RM_XFS_PREFIX = RM_PREFIX + XFS;
+ public static final String NM_XFS_PREFIX = NM_PREFIX + XFS;
+ public static final String TIMELINE_XFS_PREFIX = TIMELINE_SERVICE_PREFIX +
+ XFS;
+ public static final String RM_XFS_OPTIONS = RM_XFS_PREFIX +
+ "xframe-options";
+ public static final String NM_XFS_OPTIONS = NM_XFS_PREFIX +
+ "xframe-options";
+ public static final String TIMELINE_XFS_OPTIONS =
+ TIMELINE_XFS_PREFIX + "xframe-options";
public YarnConfiguration() {
super();
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 d86e180..53cb3ee 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
@@ -40,6 +40,7 @@
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.conf.YarnConfiguration;
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
import org.slf4j.Logger;
@@ -93,7 +94,8 @@
boolean devMode = false;
private String spnegoPrincipalKey;
private String spnegoKeytabKey;
- private String configPrefix;
+ private String csrfConfigPrefix;
+ private String xfsConfigPrefix;
private final HashSet servlets = new HashSet();
private final HashMap attributes = new HashMap();
@@ -166,13 +168,25 @@
/**
* Enable the CSRF filter.
- * @param csrfConfigPrefix The config prefix that identifies the
+ * @param prefix The config prefix that identifies the
* CSRF parameters applicable for this filter
* instance.
* @return the Builder instance
*/
- public Builder withCSRFProtection(String csrfConfigPrefix) {
- this.configPrefix = csrfConfigPrefix;
+ public Builder withCSRFProtection(String prefix) {
+ this.csrfConfigPrefix = prefix;
+ return this;
+ }
+
+ /**
+ * Enable the XFS filter.
+ * @param prefix The config prefix that identifies the
+ * XFS parameters applicable for this filter
+ * instance.
+ * @return the Builder instance
+ */
+ public Builder withXFSProtection(String prefix) {
+ this.xfsConfigPrefix = prefix;
return this;
}
@@ -281,7 +295,7 @@ public void setup() {
for(Map.Entry entry : attributes.entrySet()) {
server.setAttribute(entry.getKey(), entry.getValue());
}
- Map params = getCsrfConfigParameters();
+ Map params = getConfigParameters(csrfConfigPrefix);
if (hasCSRFEnabled(params)) {
LOG.info("CSRF Protection has been enabled for the {} application. "
@@ -294,6 +308,15 @@ public void setup() {
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[] { "/*" });
@@ -327,14 +350,18 @@ private boolean hasCSRFEnabled(Map params) {
return params != null && Boolean.valueOf(params.get("enabled"));
}
- private Map getCsrfConfigParameters() {
- Map params = null;
- if (configPrefix != null) {
- // need to obtain parameters for CSRF filter
- params =
- RestCsrfPreventionFilter.getFilterParams(conf, configPrefix);
- }
- return params;
+ /**
+ * XFS filter is enabled by default. If the enabled flag is not explicitly
+ * specified and set to "false", this method returns true.
+ * @return true if XFS is enabled, false otherwise.
+ */
+ private boolean hasXFSEnabled() {
+ return conf.getBoolean(YarnConfiguration.YARN_XFS_ENABLED, true);
+ }
+
+ private Map getConfigParameters(String configPrefix) {
+ return configPrefix != null ? conf.getPropsWithPrefix(configPrefix) :
+ null;
}
public WebApp start() {
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
index a38d0d8..dfc3d29 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
@@ -2755,6 +2755,54 @@
+ Enable the XFS filter for YARN
+
+ yarn.webapp.xfs-filter.enabled
+ true
+
+
+
+
+ Property specifying the xframe options value.
+
+ yarn.resourcemanager.webapp.xfs-filter.xframe-options
+ SAMEORIGIN
+
+
+
+
+ Enable the XFS filter for the NM web app
+
+ yarn.nodemanager.webapp.xfs-filter.xframe-options-enabled
+ true
+
+
+
+
+ Property specifying the xframe options value.
+
+ yarn.nodemanager.webapp.xfs-filter.xframe-options
+ SAMEORIGIN
+
+
+
+
+ Enable the XFS filter for the timeline service web app
+
+ yarn.timeline-service.webapp.xfs-filter.xframe-options-enabled
+ true
+
+
+
+
+ Property specifying the xframe options value.
+
+ yarn.timeline-service.webapp.xfs-filter.xframe-options
+ SAMEORIGIN
+
+
+
+
The least amount of time(msec.) an inactive (decommissioned or shutdown) node can
stay in the nodes list of the resourcemanager after being declared untracked.
A node is marked untracked if and only if it is absent from both include and
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
index d241077..fd63787 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
@@ -308,6 +308,7 @@ private void startWebApp() {
.withAttribute(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
conf.get(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS))
.withCSRFProtection(YarnConfiguration.TIMELINE_CSRF_PREFIX)
+ .withXFSProtection(YarnConfiguration.TIMELINE_XFS_PREFIX)
.at(bindAddress).build(ahsWebApp);
HttpServer2 httpServer = webApp.httpServer();
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
index 827e1b5..bb444db 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
@@ -78,8 +78,9 @@ protected void serviceStart() throws Exception {
.withHttpSpnegoPrincipalKey(
YarnConfiguration.NM_WEBAPP_SPNEGO_USER_NAME_KEY)
.withHttpSpnegoKeytabKey(
- YarnConfiguration.NM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY)
- .withCSRFProtection(YarnConfiguration.NM_CSRF_PREFIX)
+ YarnConfiguration.NM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY)
+ .withCSRFProtection(YarnConfiguration.NM_CSRF_PREFIX)
+ .withXFSProtection(YarnConfiguration.NM_XFS_PREFIX)
.start(this.nmWebApp);
this.port = this.webApp.httpServer().getConnectorAddress(0).getPort();
} catch (Exception e) {
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
index f9d3325..3634107 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
@@ -968,6 +968,7 @@ protected void startWepApp() {
.withHttpSpnegoKeytabKey(
YarnConfiguration.RM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY)
.withCSRFProtection(YarnConfiguration.RM_CSRF_PREFIX)
+ .withXFSProtection(YarnConfiguration.RM_XFS_PREFIX)
.at(webAppAddress);
String proxyHostAndPort = WebAppUtils.getProxyHostAndPort(conf);
if(WebAppUtils.getResolvedRMWebAppURLWithoutScheme(conf).
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
new file mode 100644
index 0000000..a73a0ed
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithXFSFilter.java
@@ -0,0 +1,158 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.yarn.webapp;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
+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.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.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Used TestRMWebServices as an example of web invocations of RM and added
+ * test for XFS Filter.
+ */
+public class TestRMWithXFSFilter extends JerseyTestBase {
+
+ private static MockRM rm;
+
+ private Injector injector;
+
+ /**
+ *
+ */
+ public class GuiceServletConfig extends GuiceServletContextListener {
+
+ @Override
+ protected Injector getInjector() {
+ return injector;
+ }
+ }
+
+ @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());
+ }
+
+ @Test
+ public void testDefaultBehavior() throws Exception {
+ createInjector();
+
+ WebResource r = resource();
+ ClientResponse response = r.path("ws").path("v1").path("cluster")
+ .path("info").accept("application/xml")
+ .get(ClientResponse.class);
+ assertTrue("Should have received DENY x-frame options header",
+ response.getHeaders().get(XFrameOptionsFilter.X_FRAME_OPTIONS).get(0)
+ .equals("DENY"));
+ }
+
+ protected void createInjector(String headerValue) {
+ createInjector(headerValue, false);
+ }
+
+
+ protected void createInjector() {
+ createInjector(null, false);
+ }
+
+ protected void createInjector(final String headerValue,
+ final boolean explicitlyDisabled) {
+ injector = 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);
+ }
+ });
+ }
+
+ @Test
+ public void testSameOrigin() throws Exception {
+ createInjector("SAMEORIGIN");
+
+ WebResource r = resource();
+ ClientResponse response = r.path("ws").path("v1").path("cluster")
+ .path("info").accept("application/xml")
+ .get(ClientResponse.class);
+ assertTrue("Should have received SAMEORIGIN x-frame options header",
+ response.getHeaders().get(XFrameOptionsFilter.X_FRAME_OPTIONS).get(0)
+ .equals("SAMEORIGIN"));
+ }
+
+ @Test
+ public void testExplicitlyDisabled() throws Exception {
+ createInjector(null, true);
+
+ 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);
+ }
+
+}