You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by vv...@apache.org on 2016/03/07 10:57:10 UTC
[1/2] hadoop git commit: YARN-4737. Add CSRF filter support in YARN.
Contributed by Jonathan Maron.
Repository: hadoop
Updated Branches:
refs/heads/branch-2 ed22444a6 -> e9a0ffc7f
refs/heads/trunk 4f9fe3ac0 -> e51a8c105
YARN-4737. Add CSRF filter support in YARN. Contributed by Jonathan Maron.
(cherry picked from commit 43416187c07afb35e3267f94d0a41d8d3cfb5735)
Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo
Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/e9a0ffc7
Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/e9a0ffc7
Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/e9a0ffc7
Branch: refs/heads/branch-2
Commit: e9a0ffc7f1512a181081ad0c9aadbf5f5040c124
Parents: ed22444
Author: Varun Vasudev <vv...@apache.org>
Authored: Mon Mar 7 15:16:35 2016 +0530
Committer: Varun Vasudev <vv...@apache.org>
Committed: Mon Mar 7 15:23:36 2016 +0530
----------------------------------------------------------------------
.../mapreduce/v2/jobhistory/JHAdminConfig.java | 13 ++
.../src/main/resources/mapred-default.xml | 26 +++
.../mapreduce/v2/hs/HistoryClientService.java | 1 +
.../hadoop/yarn/conf/YarnConfiguration.java | 24 ++
.../org/apache/hadoop/yarn/webapp/WebApps.java | 42 ++++
.../src/main/resources/yarn-default.xml | 78 +++++++
.../ApplicationHistoryServer.java | 19 +-
.../server/nodemanager/webapp/WebServer.java | 1 +
.../server/resourcemanager/ResourceManager.java | 1 +
.../yarn/webapp/TestRMWithCSRFFilter.java | 231 +++++++++++++++++++
10 files changed, 429 insertions(+), 7 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e9a0ffc7/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/main/java/org/apache/hadoop/mapreduce/v2/jobhistory/JHAdminConfig.java
----------------------------------------------------------------------
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 6d0d047..d08ffef 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
@@ -225,6 +225,19 @@ public class JHAdminConfig {
+ "jobname.limit";
public static final int DEFAULT_MR_HS_JOBNAME_LIMIT = 50;
+
+ /**
+ * CSRF settings.
+ */
+ public static final String MR_HISTORY_CSRF_PREFIX = MR_HISTORY_PREFIX +
+ "webapp.rest-csrf.";
+ public static final String MR_HISTORY_CSRF_ENABLED = MR_HISTORY_CSRF_PREFIX +
+ "enabled";
+ public static final String MR_HISTORY_CSRF_CUSTOM_HEADER =
+ MR_HISTORY_CSRF_PREFIX + "custom-header";
+ public static final String MR_HISTORY_METHODS_TO_IGNORE =
+ MR_HISTORY_CSRF_PREFIX + "methods-to-ignore";
+
/**
* Settings for .jhist file format.
*/
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e9a0ffc7/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/resources/mapred-default.xml
----------------------------------------------------------------------
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 71f14ea..a284293 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
@@ -1828,4 +1828,30 @@
default is -1</description>
</property>
+<property>
+ <description>
+ Enable the CSRF filter for the job history web app
+ </description>
+ <name>mapreduce.jobhistory.webapp.rest-csrf.enabled</name>
+ <value>false</value>
+</property>
+
+<property>
+ <description>
+ Optional parameter that indicates the custom header name to use for CSRF
+ protection.
+ </description>
+ <name>mapreduce.jobhistory.webapp.rest-csrf.custom-header</name>
+ <value>X-XSRF-Header</value>
+</property>
+
+<property>
+ <description>
+ Optional parameter that indicates the list of HTTP methods that do not
+ require CSRF protection
+ </description>
+ <name>mapreduce.jobhistory.webapp.rest-csrf.methods-to-ignore</name>
+ <value>GET,OPTIONS,HEAD</value>
+</property>
+
</configuration>
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e9a0ffc7/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/HistoryClientService.java
----------------------------------------------------------------------
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 3751ad9..2fbaade 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
@@ -160,6 +160,7 @@ public class HistoryClientService extends AbstractService {
JHAdminConfig.MR_WEBAPP_SPNEGO_KEYTAB_FILE_KEY)
.withHttpSpnegoPrincipalKey(
JHAdminConfig.MR_WEBAPP_SPNEGO_USER_NAME_KEY)
+ .withCSRFProtection(JHAdminConfig.MR_HISTORY_CSRF_PREFIX)
.at(NetUtils.getHostPortString(bindAddress)).start(webApp);
String connectHost = MRWebAppUtil.getJHSWebappURLWithoutScheme(conf).split(":")[0];
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e9a0ffc7/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
----------------------------------------------------------------------
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 a75e321..4573404 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
@@ -2399,6 +2399,30 @@ public class YarnConfiguration extends Configuration {
public static final String NM_SCRIPT_BASED_NODE_LABELS_PROVIDER_SCRIPT_OPTS =
NM_SCRIPT_BASED_NODE_LABELS_PROVIDER_PREFIX + "opts";
+ // RM and NM CSRF props
+ public static final String REST_CSRF = "webapp.rest-csrf.";
+ public static final String RM_CSRF_PREFIX = RM_PREFIX + REST_CSRF;
+ public static final String NM_CSRF_PREFIX = NM_PREFIX + REST_CSRF;
+ public static final String TIMELINE_CSRF_PREFIX = TIMELINE_SERVICE_PREFIX +
+ REST_CSRF;
+ public static final String RM_CSRF_ENABLED = RM_CSRF_PREFIX + "enabled";
+ public static final String NM_CSRF_ENABLED = NM_CSRF_PREFIX + "enabled";
+ public static final String TIMELINE_CSRF_ENABLED = TIMELINE_CSRF_PREFIX +
+ "enabled";
+ public static final String RM_CSRF_CUSTOM_HEADER = RM_CSRF_PREFIX +
+ "custom-header";
+ public static final String NM_CSRF_CUSTOM_HEADER = NM_CSRF_PREFIX +
+ "custom-header";
+ public static final String TIMELINE_CSRF_CUSTOM_HEADER =
+ TIMELINE_CSRF_PREFIX + "custom-header";
+ public static final String RM_CSRF_METHODS_TO_IGNORE = RM_CSRF_PREFIX +
+ "methods-to-ignore";
+ public static final String NM_CSRF_METHODS_TO_IGNORE = NM_CSRF_PREFIX +
+ "methods-to-ignore";
+ public static final String TIMELINE_CSRF_METHODS_TO_IGNORE =
+ TIMELINE_CSRF_PREFIX + "methods-to-ignore";
+
+
public YarnConfiguration() {
super();
}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e9a0ffc7/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
----------------------------------------------------------------------
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 0c6edad..6144a0d 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
@@ -39,6 +39,7 @@ import org.apache.hadoop.http.HttpConfig.Policy;
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.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
import org.slf4j.Logger;
@@ -73,6 +74,7 @@ import com.google.inject.servlet.GuiceFilter;
public class WebApps {
static final Logger LOG = LoggerFactory.getLogger(WebApps.class);
public static class Builder<T> {
+
static class ServletStruct {
public Class<? extends HttpServlet> clazz;
public String name;
@@ -91,6 +93,7 @@ public class WebApps {
boolean devMode = false;
private String spnegoPrincipalKey;
private String spnegoKeytabKey;
+ private String configPrefix;
private final HashSet<ServletStruct> servlets = new HashSet<ServletStruct>();
private final HashMap<String, Object> attributes = new HashMap<String, Object>();
@@ -161,6 +164,18 @@ public class WebApps {
return this;
}
+ /**
+ * Enable the CSRF filter.
+ * @param csrfConfigPrefix The config prefix that identifies the
+ * CSRF parameters applicable for this filter
+ * instance.
+ * @return the Builder instance
+ */
+ public Builder<T> withCSRFProtection(String csrfConfigPrefix) {
+ this.configPrefix = csrfConfigPrefix;
+ return this;
+ }
+
public Builder<T> inDevMode() {
devMode = true;
return this;
@@ -266,6 +281,19 @@ public class WebApps {
for(Map.Entry<String, Object> entry : attributes.entrySet()) {
server.setAttribute(entry.getKey(), entry.getValue());
}
+ Map<String, String> params = getCsrfConfigParameters();
+
+ if (hasCSRFEnabled(params)) {
+ LOG.info("CSRF Protection has been enabled for the {} application. "
+ + "Please ensure that there is an authentication mechanism "
+ + "enabled (kerberos, custom, etc).",
+ name);
+ String restCsrfClassName = RestCsrfPreventionFilter.class.getName();
+ HttpServer2.defineFilter(server.getWebAppContext(), restCsrfClassName,
+ restCsrfClassName, params,
+ new String[] {"/*"});
+ }
+
HttpServer2.defineFilter(server.getWebAppContext(), "guice",
GuiceFilter.class.getName(), null, new String[] { "/*" });
@@ -295,6 +323,20 @@ public class WebApps {
return webapp;
}
+ private boolean hasCSRFEnabled(Map<String, String> params) {
+ return params != null && Boolean.valueOf(params.get("enabled"));
+ }
+
+ private Map<String, String> getCsrfConfigParameters() {
+ Map<String, String> params = null;
+ if (configPrefix != null) {
+ // need to obtain parameters for CSRF filter
+ params =
+ RestCsrfPreventionFilter.getFilterParams(conf, configPrefix);
+ }
+ return params;
+ }
+
public WebApp start() {
return start(null);
}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e9a0ffc7/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
----------------------------------------------------------------------
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 cc08802..ea1afe4 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
@@ -2637,4 +2637,82 @@
<name>yarn.node-labels.fs-store.impl.class</name>
<value>org.apache.hadoop.yarn.nodelabels.FileSystemNodeLabelsStore</value>
</property>
+
+ <property>
+ <description>
+ Enable the CSRF filter for the RM web app
+ </description>
+ <name>yarn.resourcemanager.webapp.rest-csrf.enabled</name>
+ <value>false</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the custom header name to use for CSRF
+ protection.
+ </description>
+ <name>yarn.resourcemanager.webapp.rest-csrf.custom-header</name>
+ <value>X-XSRF-Header</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the list of HTTP methods that do not
+ require CSRF protection
+ </description>
+ <name>yarn.resourcemanager.webapp.rest-csrf.methods-to-ignore</name>
+ <value>GET,OPTIONS,HEAD</value>
+ </property>
+
+ <property>
+ <description>
+ Enable the CSRF filter for the NM web app
+ </description>
+ <name>yarn.nodemanager.webapp.rest-csrf.enabled</name>
+ <value>false</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the custom header name to use for CSRF
+ protection.
+ </description>
+ <name>yarn.nodemanager.webapp.rest-csrf.custom-header</name>
+ <value>X-XSRF-Header</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the list of HTTP methods that do not
+ require CSRF protection
+ </description>
+ <name>yarn.nodemanager.webapp.rest-csrf.methods-to-ignore</name>
+ <value>GET,OPTIONS,HEAD</value>
+ </property>
+
+ <property>
+ <description>
+ Enable the CSRF filter for the timeline service web app
+ </description>
+ <name>yarn.timeline-service.webapp.rest-csrf.enabled</name>
+ <value>false</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the custom header name to use for CSRF
+ protection.
+ </description>
+ <name>yarn.timeline-service.webapp.rest-csrf.custom-header</name>
+ <value>X-XSRF-Header</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the list of HTTP methods that do not
+ require CSRF protection
+ </description>
+ <name>yarn.timeline-service.webapp.rest-csrf.methods-to-ignore</name>
+ <value>GET,OPTIONS,HEAD</value>
+ </property>
</configuration>
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e9a0ffc7/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
----------------------------------------------------------------------
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 f4fe140..cedbd2e 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
@@ -297,16 +297,21 @@ public class ApplicationHistoryServer extends CompositeService {
YarnConfiguration.TIMELINE_SERVICE_BIND_HOST,
WebAppUtils.getAHSWebAppURLWithoutScheme(conf));
try {
- AHSWebApp ahsWebApp = new AHSWebApp(timelineDataManager, ahsClientService);
+ AHSWebApp ahsWebApp =
+ new AHSWebApp(timelineDataManager, ahsClientService);
webApp =
WebApps
.$for("applicationhistory", ApplicationHistoryClientService.class,
ahsClientService, "ws")
- .with(conf).withAttribute(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
- conf.get(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS)).at(bindAddress).build(ahsWebApp);
+ .with(conf)
+ .withAttribute(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
+ conf.get(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS))
+ .withCSRFProtection(YarnConfiguration.TIMELINE_CSRF_PREFIX)
+ .at(bindAddress).build(ahsWebApp);
HttpServer2 httpServer = webApp.httpServer();
- String[] names = conf.getTrimmedStrings(YarnConfiguration.TIMELINE_SERVICE_UI_NAMES);
+ String[] names = conf.getTrimmedStrings(
+ YarnConfiguration.TIMELINE_SERVICE_UI_NAMES);
WebAppContext webAppContext = httpServer.getWebAppContext();
for (String name : names) {
@@ -332,9 +337,9 @@ public class ApplicationHistoryServer extends CompositeService {
}
httpServer.start();
conf.updateConnectAddr(YarnConfiguration.TIMELINE_SERVICE_BIND_HOST,
- YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
- YarnConfiguration.DEFAULT_TIMELINE_SERVICE_WEBAPP_ADDRESS,
- this.getListenerAddress());
+ YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
+ YarnConfiguration.DEFAULT_TIMELINE_SERVICE_WEBAPP_ADDRESS,
+ this.getListenerAddress());
LOG.info("Instantiating AHSWebApp at " + getPort());
} catch (Exception e) {
String msg = "AHSWebApp failed to start.";
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e9a0ffc7/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
----------------------------------------------------------------------
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 319c10c..827e1b5 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
@@ -79,6 +79,7 @@ public class WebServer extends AbstractService {
YarnConfiguration.NM_WEBAPP_SPNEGO_USER_NAME_KEY)
.withHttpSpnegoKeytabKey(
YarnConfiguration.NM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY)
+ .withCSRFProtection(YarnConfiguration.NM_CSRF_PREFIX)
.start(this.nmWebApp);
this.port = this.webApp.httpServer().getConnectorAddress(0).getPort();
} catch (Exception e) {
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e9a0ffc7/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
----------------------------------------------------------------------
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 80b33a3..2744bb4 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
@@ -1058,6 +1058,7 @@ public class ResourceManager extends CompositeService implements Recoverable {
YarnConfiguration.RM_WEBAPP_SPNEGO_USER_NAME_KEY)
.withHttpSpnegoKeytabKey(
YarnConfiguration.RM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY)
+ .withCSRFProtection(YarnConfiguration.RM_CSRF_PREFIX)
.at(webAppAddress);
String proxyHostAndPort = WebAppUtils.getProxyHostAndPort(conf);
if(WebAppUtils.getResolvedRMWebAppURLWithoutScheme(conf).
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e9a0ffc7/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithCSRFFilter.java
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithCSRFFilter.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithCSRFFilter.java
new file mode 100644
index 0000000..2efbd2d
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithCSRFFilter.java
@@ -0,0 +1,231 @@
+/**
+ * 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.ClientResponse.Status;
+import com.sun.jersey.api.client.UniformInterfaceException;
+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.RestCsrfPreventionFilter;
+import org.apache.hadoop.service.Service.STATE;
+import org.apache.hadoop.util.VersionInfo;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.server.resourcemanager.ClusterMetrics;
+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.apache.hadoop.yarn.util.YarnVersionInfo;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Used TestRMWebServices as an example of web invocations of RM and added
+ * test for CSRF Filter.
+ */
+public class TestRMWithCSRFFilter extends JerseyTestBase {
+
+ private static MockRM rm;
+
+ private Injector 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);
+ RestCsrfPreventionFilter csrfFilter = new RestCsrfPreventionFilter();
+ Map<String,String> initParams = new HashMap<>();
+ // adding GET as protected method to make things a little easier...
+ initParams.put(RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM,
+ "OPTIONS,HEAD,TRACE");
+ filter("/*").through(csrfFilter, initParams);
+ }
+ });
+
+ public class GuiceServletConfig extends GuiceServletContextListener {
+
+ @Override
+ protected Injector getInjector() {
+ return injector;
+ }
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public TestRMWithCSRFFilter() {
+ 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 testNoCustomHeaderFromBrowser() throws Exception {
+ WebResource r = resource();
+ ClientResponse response = r.path("ws").path("v1").path("cluster")
+ .path("info").accept("application/xml")
+ .header(RestCsrfPreventionFilter.HEADER_USER_AGENT,"Mozilla/5.0")
+ .get(ClientResponse.class);
+ assertTrue("Should have been rejected", response.getStatus() ==
+ Status.BAD_REQUEST.getStatusCode());
+ }
+
+ @Test
+ public void testIncludeCustomHeaderFromBrowser() throws Exception {
+ WebResource r = resource();
+ ClientResponse response = r.path("ws").path("v1").path("cluster")
+ .path("info").accept("application/xml")
+ .header(RestCsrfPreventionFilter.HEADER_USER_AGENT,"Mozilla/5.0")
+ .header("X-XSRF-HEADER", "")
+ .get(ClientResponse.class);
+ assertTrue("Should have been accepted", response.getStatus() ==
+ Status.OK.getStatusCode());
+ assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType());
+ String xml = response.getEntity(String.class);
+ verifyClusterInfoXML(xml);
+ }
+
+ @Test
+ public void testAllowedMethod() throws Exception {
+ WebResource r = resource();
+ ClientResponse response = r.path("ws").path("v1").path("cluster")
+ .path("info").accept("application/xml")
+ .header(RestCsrfPreventionFilter.HEADER_USER_AGENT,"Mozilla/5.0")
+ .head();
+ assertTrue("Should have been allowed", response.getStatus() ==
+ Status.OK.getStatusCode());
+ }
+
+ @Test
+ public void testAllowNonBrowserInteractionWithoutHeader() throws Exception {
+ WebResource r = resource();
+ ClientResponse response = r.path("ws").path("v1").path("cluster")
+ .path("info").accept("application/xml")
+ .get(ClientResponse.class);
+ assertTrue("Should have been accepted", response.getStatus() ==
+ Status.OK.getStatusCode());
+ assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType());
+ String xml = response.getEntity(String.class);
+ verifyClusterInfoXML(xml);
+ }
+
+ public void verifyClusterInfoXML(String xml) throws Exception {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ InputSource is = new InputSource();
+ is.setCharacterStream(new StringReader(xml));
+ Document dom = db.parse(is);
+ NodeList nodes = dom.getElementsByTagName("clusterInfo");
+ assertEquals("incorrect number of elements", 1, nodes.getLength());
+
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element element = (Element) nodes.item(i);
+
+ verifyClusterGeneric(WebServicesTestUtils.getXmlLong(element, "id"),
+ WebServicesTestUtils.getXmlLong(element, "startedOn"),
+ WebServicesTestUtils.getXmlString(element, "state"),
+ WebServicesTestUtils.getXmlString(element, "haState"),
+ WebServicesTestUtils.getXmlString(
+ element, "haZooKeeperConnectionState"),
+ WebServicesTestUtils.getXmlString(element, "hadoopVersionBuiltOn"),
+ WebServicesTestUtils.getXmlString(element, "hadoopBuildVersion"),
+ WebServicesTestUtils.getXmlString(element, "hadoopVersion"),
+ WebServicesTestUtils.getXmlString(element,
+ "resourceManagerVersionBuiltOn"),
+ WebServicesTestUtils.getXmlString(element,
+ "resourceManagerBuildVersion"),
+ WebServicesTestUtils.getXmlString(element, "resourceManagerVersion"));
+ }
+ }
+
+ public void verifyClusterGeneric(long clusterid, long startedon,
+ String state, String haState,
+ String haZooKeeperConnectionState,
+ String hadoopVersionBuiltOn,
+ String hadoopBuildVersion,
+ String hadoopVersion,
+ String resourceManagerVersionBuiltOn,
+ String resourceManagerBuildVersion,
+ String resourceManagerVersion) {
+
+ assertEquals("clusterId doesn't match: ",
+ ResourceManager.getClusterTimeStamp(), clusterid);
+ assertEquals("startedOn doesn't match: ",
+ ResourceManager.getClusterTimeStamp(), startedon);
+ assertTrue("stated doesn't match: " + state,
+ state.matches(STATE.INITED.toString()));
+ assertTrue("HA state doesn't match: " + haState,
+ haState.matches("INITIALIZING"));
+
+ WebServicesTestUtils.checkStringMatch("hadoopVersionBuiltOn",
+ VersionInfo.getDate(), hadoopVersionBuiltOn);
+ WebServicesTestUtils.checkStringEqual("hadoopBuildVersion",
+ VersionInfo.getBuildVersion(), hadoopBuildVersion);
+ WebServicesTestUtils.checkStringMatch("hadoopVersion",
+ VersionInfo.getVersion(), hadoopVersion);
+
+ WebServicesTestUtils.checkStringMatch("resourceManagerVersionBuiltOn",
+ YarnVersionInfo.getDate(),
+ resourceManagerVersionBuiltOn);
+ WebServicesTestUtils.checkStringEqual("resourceManagerBuildVersion",
+ YarnVersionInfo.getBuildVersion(), resourceManagerBuildVersion);
+ WebServicesTestUtils.checkStringMatch("resourceManagerVersion",
+ YarnVersionInfo.getVersion(),
+ resourceManagerVersion);
+ }
+
+}
[2/2] hadoop git commit: YARN-4737. Add CSRF filter support in YARN.
Contributed by Jonathan Maron.
Posted by vv...@apache.org.
YARN-4737. Add CSRF filter support in YARN. Contributed by Jonathan Maron.
Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo
Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/e51a8c10
Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/e51a8c10
Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/e51a8c10
Branch: refs/heads/trunk
Commit: e51a8c10560e5db5cf01fd530af48825cb51c9ea
Parents: 4f9fe3a
Author: Varun Vasudev <vv...@apache.org>
Authored: Mon Mar 7 15:16:35 2016 +0530
Committer: Varun Vasudev <vv...@apache.org>
Committed: Mon Mar 7 15:26:44 2016 +0530
----------------------------------------------------------------------
.../mapreduce/v2/jobhistory/JHAdminConfig.java | 13 ++
.../src/main/resources/mapred-default.xml | 26 +++
.../mapreduce/v2/hs/HistoryClientService.java | 1 +
.../hadoop/yarn/conf/YarnConfiguration.java | 24 ++
.../org/apache/hadoop/yarn/webapp/WebApps.java | 42 ++++
.../src/main/resources/yarn-default.xml | 78 +++++++
.../ApplicationHistoryServer.java | 19 +-
.../server/nodemanager/webapp/WebServer.java | 1 +
.../server/resourcemanager/ResourceManager.java | 1 +
.../yarn/webapp/TestRMWithCSRFFilter.java | 231 +++++++++++++++++++
10 files changed, 429 insertions(+), 7 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e51a8c10/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/main/java/org/apache/hadoop/mapreduce/v2/jobhistory/JHAdminConfig.java
----------------------------------------------------------------------
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 1f2088a..5aa4671 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
@@ -225,6 +225,19 @@ public class JHAdminConfig {
+ "jobname.limit";
public static final int DEFAULT_MR_HS_JOBNAME_LIMIT = 50;
+
+ /**
+ * CSRF settings.
+ */
+ public static final String MR_HISTORY_CSRF_PREFIX = MR_HISTORY_PREFIX +
+ "webapp.rest-csrf.";
+ public static final String MR_HISTORY_CSRF_ENABLED = MR_HISTORY_CSRF_PREFIX +
+ "enabled";
+ public static final String MR_HISTORY_CSRF_CUSTOM_HEADER =
+ MR_HISTORY_CSRF_PREFIX + "custom-header";
+ public static final String MR_HISTORY_METHODS_TO_IGNORE =
+ MR_HISTORY_CSRF_PREFIX + "methods-to-ignore";
+
/**
* Settings for .jhist file format.
*/
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e51a8c10/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/main/resources/mapred-default.xml
----------------------------------------------------------------------
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 da25a99..b7bdcc8 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
@@ -1862,4 +1862,30 @@
default is -1</description>
</property>
+<property>
+ <description>
+ Enable the CSRF filter for the job history web app
+ </description>
+ <name>mapreduce.jobhistory.webapp.rest-csrf.enabled</name>
+ <value>false</value>
+</property>
+
+<property>
+ <description>
+ Optional parameter that indicates the custom header name to use for CSRF
+ protection.
+ </description>
+ <name>mapreduce.jobhistory.webapp.rest-csrf.custom-header</name>
+ <value>X-XSRF-Header</value>
+</property>
+
+<property>
+ <description>
+ Optional parameter that indicates the list of HTTP methods that do not
+ require CSRF protection
+ </description>
+ <name>mapreduce.jobhistory.webapp.rest-csrf.methods-to-ignore</name>
+ <value>GET,OPTIONS,HEAD</value>
+</property>
+
</configuration>
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e51a8c10/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/main/java/org/apache/hadoop/mapreduce/v2/hs/HistoryClientService.java
----------------------------------------------------------------------
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 3751ad9..2fbaade 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
@@ -160,6 +160,7 @@ public class HistoryClientService extends AbstractService {
JHAdminConfig.MR_WEBAPP_SPNEGO_KEYTAB_FILE_KEY)
.withHttpSpnegoPrincipalKey(
JHAdminConfig.MR_WEBAPP_SPNEGO_USER_NAME_KEY)
+ .withCSRFProtection(JHAdminConfig.MR_HISTORY_CSRF_PREFIX)
.at(NetUtils.getHostPortString(bindAddress)).start(webApp);
String connectHost = MRWebAppUtil.getJHSWebappURLWithoutScheme(conf).split(":")[0];
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e51a8c10/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
----------------------------------------------------------------------
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 cef6932..61d1d72 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
@@ -2399,6 +2399,30 @@ public class YarnConfiguration extends Configuration {
public static final String NM_SCRIPT_BASED_NODE_LABELS_PROVIDER_SCRIPT_OPTS =
NM_SCRIPT_BASED_NODE_LABELS_PROVIDER_PREFIX + "opts";
+ // RM and NM CSRF props
+ public static final String REST_CSRF = "webapp.rest-csrf.";
+ public static final String RM_CSRF_PREFIX = RM_PREFIX + REST_CSRF;
+ public static final String NM_CSRF_PREFIX = NM_PREFIX + REST_CSRF;
+ public static final String TIMELINE_CSRF_PREFIX = TIMELINE_SERVICE_PREFIX +
+ REST_CSRF;
+ public static final String RM_CSRF_ENABLED = RM_CSRF_PREFIX + "enabled";
+ public static final String NM_CSRF_ENABLED = NM_CSRF_PREFIX + "enabled";
+ public static final String TIMELINE_CSRF_ENABLED = TIMELINE_CSRF_PREFIX +
+ "enabled";
+ public static final String RM_CSRF_CUSTOM_HEADER = RM_CSRF_PREFIX +
+ "custom-header";
+ public static final String NM_CSRF_CUSTOM_HEADER = NM_CSRF_PREFIX +
+ "custom-header";
+ public static final String TIMELINE_CSRF_CUSTOM_HEADER =
+ TIMELINE_CSRF_PREFIX + "custom-header";
+ public static final String RM_CSRF_METHODS_TO_IGNORE = RM_CSRF_PREFIX +
+ "methods-to-ignore";
+ public static final String NM_CSRF_METHODS_TO_IGNORE = NM_CSRF_PREFIX +
+ "methods-to-ignore";
+ public static final String TIMELINE_CSRF_METHODS_TO_IGNORE =
+ TIMELINE_CSRF_PREFIX + "methods-to-ignore";
+
+
public YarnConfiguration() {
super();
}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e51a8c10/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
----------------------------------------------------------------------
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 0c6edad..6144a0d 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
@@ -39,6 +39,7 @@ import org.apache.hadoop.http.HttpConfig.Policy;
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.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
import org.slf4j.Logger;
@@ -73,6 +74,7 @@ import com.google.inject.servlet.GuiceFilter;
public class WebApps {
static final Logger LOG = LoggerFactory.getLogger(WebApps.class);
public static class Builder<T> {
+
static class ServletStruct {
public Class<? extends HttpServlet> clazz;
public String name;
@@ -91,6 +93,7 @@ public class WebApps {
boolean devMode = false;
private String spnegoPrincipalKey;
private String spnegoKeytabKey;
+ private String configPrefix;
private final HashSet<ServletStruct> servlets = new HashSet<ServletStruct>();
private final HashMap<String, Object> attributes = new HashMap<String, Object>();
@@ -161,6 +164,18 @@ public class WebApps {
return this;
}
+ /**
+ * Enable the CSRF filter.
+ * @param csrfConfigPrefix The config prefix that identifies the
+ * CSRF parameters applicable for this filter
+ * instance.
+ * @return the Builder instance
+ */
+ public Builder<T> withCSRFProtection(String csrfConfigPrefix) {
+ this.configPrefix = csrfConfigPrefix;
+ return this;
+ }
+
public Builder<T> inDevMode() {
devMode = true;
return this;
@@ -266,6 +281,19 @@ public class WebApps {
for(Map.Entry<String, Object> entry : attributes.entrySet()) {
server.setAttribute(entry.getKey(), entry.getValue());
}
+ Map<String, String> params = getCsrfConfigParameters();
+
+ if (hasCSRFEnabled(params)) {
+ LOG.info("CSRF Protection has been enabled for the {} application. "
+ + "Please ensure that there is an authentication mechanism "
+ + "enabled (kerberos, custom, etc).",
+ name);
+ String restCsrfClassName = RestCsrfPreventionFilter.class.getName();
+ HttpServer2.defineFilter(server.getWebAppContext(), restCsrfClassName,
+ restCsrfClassName, params,
+ new String[] {"/*"});
+ }
+
HttpServer2.defineFilter(server.getWebAppContext(), "guice",
GuiceFilter.class.getName(), null, new String[] { "/*" });
@@ -295,6 +323,20 @@ public class WebApps {
return webapp;
}
+ private boolean hasCSRFEnabled(Map<String, String> params) {
+ return params != null && Boolean.valueOf(params.get("enabled"));
+ }
+
+ private Map<String, String> getCsrfConfigParameters() {
+ Map<String, String> params = null;
+ if (configPrefix != null) {
+ // need to obtain parameters for CSRF filter
+ params =
+ RestCsrfPreventionFilter.getFilterParams(conf, configPrefix);
+ }
+ return params;
+ }
+
public WebApp start() {
return start(null);
}
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e51a8c10/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
----------------------------------------------------------------------
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 cc08802..ea1afe4 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
@@ -2637,4 +2637,82 @@
<name>yarn.node-labels.fs-store.impl.class</name>
<value>org.apache.hadoop.yarn.nodelabels.FileSystemNodeLabelsStore</value>
</property>
+
+ <property>
+ <description>
+ Enable the CSRF filter for the RM web app
+ </description>
+ <name>yarn.resourcemanager.webapp.rest-csrf.enabled</name>
+ <value>false</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the custom header name to use for CSRF
+ protection.
+ </description>
+ <name>yarn.resourcemanager.webapp.rest-csrf.custom-header</name>
+ <value>X-XSRF-Header</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the list of HTTP methods that do not
+ require CSRF protection
+ </description>
+ <name>yarn.resourcemanager.webapp.rest-csrf.methods-to-ignore</name>
+ <value>GET,OPTIONS,HEAD</value>
+ </property>
+
+ <property>
+ <description>
+ Enable the CSRF filter for the NM web app
+ </description>
+ <name>yarn.nodemanager.webapp.rest-csrf.enabled</name>
+ <value>false</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the custom header name to use for CSRF
+ protection.
+ </description>
+ <name>yarn.nodemanager.webapp.rest-csrf.custom-header</name>
+ <value>X-XSRF-Header</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the list of HTTP methods that do not
+ require CSRF protection
+ </description>
+ <name>yarn.nodemanager.webapp.rest-csrf.methods-to-ignore</name>
+ <value>GET,OPTIONS,HEAD</value>
+ </property>
+
+ <property>
+ <description>
+ Enable the CSRF filter for the timeline service web app
+ </description>
+ <name>yarn.timeline-service.webapp.rest-csrf.enabled</name>
+ <value>false</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the custom header name to use for CSRF
+ protection.
+ </description>
+ <name>yarn.timeline-service.webapp.rest-csrf.custom-header</name>
+ <value>X-XSRF-Header</value>
+ </property>
+
+ <property>
+ <description>
+ Optional parameter that indicates the list of HTTP methods that do not
+ require CSRF protection
+ </description>
+ <name>yarn.timeline-service.webapp.rest-csrf.methods-to-ignore</name>
+ <value>GET,OPTIONS,HEAD</value>
+ </property>
</configuration>
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e51a8c10/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
----------------------------------------------------------------------
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 f4fe140..cedbd2e 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
@@ -297,16 +297,21 @@ public class ApplicationHistoryServer extends CompositeService {
YarnConfiguration.TIMELINE_SERVICE_BIND_HOST,
WebAppUtils.getAHSWebAppURLWithoutScheme(conf));
try {
- AHSWebApp ahsWebApp = new AHSWebApp(timelineDataManager, ahsClientService);
+ AHSWebApp ahsWebApp =
+ new AHSWebApp(timelineDataManager, ahsClientService);
webApp =
WebApps
.$for("applicationhistory", ApplicationHistoryClientService.class,
ahsClientService, "ws")
- .with(conf).withAttribute(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
- conf.get(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS)).at(bindAddress).build(ahsWebApp);
+ .with(conf)
+ .withAttribute(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
+ conf.get(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS))
+ .withCSRFProtection(YarnConfiguration.TIMELINE_CSRF_PREFIX)
+ .at(bindAddress).build(ahsWebApp);
HttpServer2 httpServer = webApp.httpServer();
- String[] names = conf.getTrimmedStrings(YarnConfiguration.TIMELINE_SERVICE_UI_NAMES);
+ String[] names = conf.getTrimmedStrings(
+ YarnConfiguration.TIMELINE_SERVICE_UI_NAMES);
WebAppContext webAppContext = httpServer.getWebAppContext();
for (String name : names) {
@@ -332,9 +337,9 @@ public class ApplicationHistoryServer extends CompositeService {
}
httpServer.start();
conf.updateConnectAddr(YarnConfiguration.TIMELINE_SERVICE_BIND_HOST,
- YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
- YarnConfiguration.DEFAULT_TIMELINE_SERVICE_WEBAPP_ADDRESS,
- this.getListenerAddress());
+ YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
+ YarnConfiguration.DEFAULT_TIMELINE_SERVICE_WEBAPP_ADDRESS,
+ this.getListenerAddress());
LOG.info("Instantiating AHSWebApp at " + getPort());
} catch (Exception e) {
String msg = "AHSWebApp failed to start.";
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e51a8c10/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
----------------------------------------------------------------------
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 319c10c..827e1b5 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
@@ -79,6 +79,7 @@ public class WebServer extends AbstractService {
YarnConfiguration.NM_WEBAPP_SPNEGO_USER_NAME_KEY)
.withHttpSpnegoKeytabKey(
YarnConfiguration.NM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY)
+ .withCSRFProtection(YarnConfiguration.NM_CSRF_PREFIX)
.start(this.nmWebApp);
this.port = this.webApp.httpServer().getConnectorAddress(0).getPort();
} catch (Exception e) {
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e51a8c10/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
----------------------------------------------------------------------
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 80b33a3..2744bb4 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
@@ -1058,6 +1058,7 @@ public class ResourceManager extends CompositeService implements Recoverable {
YarnConfiguration.RM_WEBAPP_SPNEGO_USER_NAME_KEY)
.withHttpSpnegoKeytabKey(
YarnConfiguration.RM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY)
+ .withCSRFProtection(YarnConfiguration.RM_CSRF_PREFIX)
.at(webAppAddress);
String proxyHostAndPort = WebAppUtils.getProxyHostAndPort(conf);
if(WebAppUtils.getResolvedRMWebAppURLWithoutScheme(conf).
http://git-wip-us.apache.org/repos/asf/hadoop/blob/e51a8c10/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithCSRFFilter.java
----------------------------------------------------------------------
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithCSRFFilter.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithCSRFFilter.java
new file mode 100644
index 0000000..2efbd2d
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/webapp/TestRMWithCSRFFilter.java
@@ -0,0 +1,231 @@
+/**
+ * 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.ClientResponse.Status;
+import com.sun.jersey.api.client.UniformInterfaceException;
+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.RestCsrfPreventionFilter;
+import org.apache.hadoop.service.Service.STATE;
+import org.apache.hadoop.util.VersionInfo;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.server.resourcemanager.ClusterMetrics;
+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.apache.hadoop.yarn.util.YarnVersionInfo;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Used TestRMWebServices as an example of web invocations of RM and added
+ * test for CSRF Filter.
+ */
+public class TestRMWithCSRFFilter extends JerseyTestBase {
+
+ private static MockRM rm;
+
+ private Injector 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);
+ RestCsrfPreventionFilter csrfFilter = new RestCsrfPreventionFilter();
+ Map<String,String> initParams = new HashMap<>();
+ // adding GET as protected method to make things a little easier...
+ initParams.put(RestCsrfPreventionFilter.CUSTOM_METHODS_TO_IGNORE_PARAM,
+ "OPTIONS,HEAD,TRACE");
+ filter("/*").through(csrfFilter, initParams);
+ }
+ });
+
+ public class GuiceServletConfig extends GuiceServletContextListener {
+
+ @Override
+ protected Injector getInjector() {
+ return injector;
+ }
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ }
+
+ public TestRMWithCSRFFilter() {
+ 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 testNoCustomHeaderFromBrowser() throws Exception {
+ WebResource r = resource();
+ ClientResponse response = r.path("ws").path("v1").path("cluster")
+ .path("info").accept("application/xml")
+ .header(RestCsrfPreventionFilter.HEADER_USER_AGENT,"Mozilla/5.0")
+ .get(ClientResponse.class);
+ assertTrue("Should have been rejected", response.getStatus() ==
+ Status.BAD_REQUEST.getStatusCode());
+ }
+
+ @Test
+ public void testIncludeCustomHeaderFromBrowser() throws Exception {
+ WebResource r = resource();
+ ClientResponse response = r.path("ws").path("v1").path("cluster")
+ .path("info").accept("application/xml")
+ .header(RestCsrfPreventionFilter.HEADER_USER_AGENT,"Mozilla/5.0")
+ .header("X-XSRF-HEADER", "")
+ .get(ClientResponse.class);
+ assertTrue("Should have been accepted", response.getStatus() ==
+ Status.OK.getStatusCode());
+ assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType());
+ String xml = response.getEntity(String.class);
+ verifyClusterInfoXML(xml);
+ }
+
+ @Test
+ public void testAllowedMethod() throws Exception {
+ WebResource r = resource();
+ ClientResponse response = r.path("ws").path("v1").path("cluster")
+ .path("info").accept("application/xml")
+ .header(RestCsrfPreventionFilter.HEADER_USER_AGENT,"Mozilla/5.0")
+ .head();
+ assertTrue("Should have been allowed", response.getStatus() ==
+ Status.OK.getStatusCode());
+ }
+
+ @Test
+ public void testAllowNonBrowserInteractionWithoutHeader() throws Exception {
+ WebResource r = resource();
+ ClientResponse response = r.path("ws").path("v1").path("cluster")
+ .path("info").accept("application/xml")
+ .get(ClientResponse.class);
+ assertTrue("Should have been accepted", response.getStatus() ==
+ Status.OK.getStatusCode());
+ assertEquals(MediaType.APPLICATION_XML_TYPE, response.getType());
+ String xml = response.getEntity(String.class);
+ verifyClusterInfoXML(xml);
+ }
+
+ public void verifyClusterInfoXML(String xml) throws Exception {
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ DocumentBuilder db = dbf.newDocumentBuilder();
+ InputSource is = new InputSource();
+ is.setCharacterStream(new StringReader(xml));
+ Document dom = db.parse(is);
+ NodeList nodes = dom.getElementsByTagName("clusterInfo");
+ assertEquals("incorrect number of elements", 1, nodes.getLength());
+
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element element = (Element) nodes.item(i);
+
+ verifyClusterGeneric(WebServicesTestUtils.getXmlLong(element, "id"),
+ WebServicesTestUtils.getXmlLong(element, "startedOn"),
+ WebServicesTestUtils.getXmlString(element, "state"),
+ WebServicesTestUtils.getXmlString(element, "haState"),
+ WebServicesTestUtils.getXmlString(
+ element, "haZooKeeperConnectionState"),
+ WebServicesTestUtils.getXmlString(element, "hadoopVersionBuiltOn"),
+ WebServicesTestUtils.getXmlString(element, "hadoopBuildVersion"),
+ WebServicesTestUtils.getXmlString(element, "hadoopVersion"),
+ WebServicesTestUtils.getXmlString(element,
+ "resourceManagerVersionBuiltOn"),
+ WebServicesTestUtils.getXmlString(element,
+ "resourceManagerBuildVersion"),
+ WebServicesTestUtils.getXmlString(element, "resourceManagerVersion"));
+ }
+ }
+
+ public void verifyClusterGeneric(long clusterid, long startedon,
+ String state, String haState,
+ String haZooKeeperConnectionState,
+ String hadoopVersionBuiltOn,
+ String hadoopBuildVersion,
+ String hadoopVersion,
+ String resourceManagerVersionBuiltOn,
+ String resourceManagerBuildVersion,
+ String resourceManagerVersion) {
+
+ assertEquals("clusterId doesn't match: ",
+ ResourceManager.getClusterTimeStamp(), clusterid);
+ assertEquals("startedOn doesn't match: ",
+ ResourceManager.getClusterTimeStamp(), startedon);
+ assertTrue("stated doesn't match: " + state,
+ state.matches(STATE.INITED.toString()));
+ assertTrue("HA state doesn't match: " + haState,
+ haState.matches("INITIALIZING"));
+
+ WebServicesTestUtils.checkStringMatch("hadoopVersionBuiltOn",
+ VersionInfo.getDate(), hadoopVersionBuiltOn);
+ WebServicesTestUtils.checkStringEqual("hadoopBuildVersion",
+ VersionInfo.getBuildVersion(), hadoopBuildVersion);
+ WebServicesTestUtils.checkStringMatch("hadoopVersion",
+ VersionInfo.getVersion(), hadoopVersion);
+
+ WebServicesTestUtils.checkStringMatch("resourceManagerVersionBuiltOn",
+ YarnVersionInfo.getDate(),
+ resourceManagerVersionBuiltOn);
+ WebServicesTestUtils.checkStringEqual("resourceManagerBuildVersion",
+ YarnVersionInfo.getBuildVersion(), resourceManagerBuildVersion);
+ WebServicesTestUtils.checkStringMatch("resourceManagerVersion",
+ YarnVersionInfo.getVersion(),
+ resourceManagerVersion);
+ }
+
+}