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 1f2088a8393..5aa467174e9 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. */ 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 da25a994d11..b7bdcc87c18 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 + + + Enable the CSRF filter for the job history web app + + mapreduce.jobhistory.webapp.rest-csrf.enabled + false + + + + + Optional parameter that indicates the custom header name to use for CSRF + protection. + + mapreduce.jobhistory.webapp.rest-csrf.custom-header + X-XSRF-Header + + + + + Optional parameter that indicates the list of HTTP methods that do not + require CSRF protection + + mapreduce.jobhistory.webapp.rest-csrf.methods-to-ignore + GET,OPTIONS,HEAD + + 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 3751ad9296b..2fbaade6b2d 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]; 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 cef69328e3a..61d1d72f568 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(); } 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 0c6edad2a89..6144a0def82 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 { + static class ServletStruct { public Class 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 servlets = new HashSet(); private final HashMap attributes = new HashMap(); @@ -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 withCSRFProtection(String csrfConfigPrefix) { + this.configPrefix = csrfConfigPrefix; + return this; + } + public Builder inDevMode() { devMode = true; return this; @@ -266,6 +281,19 @@ public class WebApps { for(Map.Entry entry : attributes.entrySet()) { server.setAttribute(entry.getKey(), entry.getValue()); } + Map 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 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; + } + public WebApp start() { return start(null); } 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 cc08802fffc..ea1afe48ae0 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 @@ yarn.node-labels.fs-store.impl.class org.apache.hadoop.yarn.nodelabels.FileSystemNodeLabelsStore + + + + Enable the CSRF filter for the RM web app + + yarn.resourcemanager.webapp.rest-csrf.enabled + false + + + + + Optional parameter that indicates the custom header name to use for CSRF + protection. + + yarn.resourcemanager.webapp.rest-csrf.custom-header + X-XSRF-Header + + + + + Optional parameter that indicates the list of HTTP methods that do not + require CSRF protection + + yarn.resourcemanager.webapp.rest-csrf.methods-to-ignore + GET,OPTIONS,HEAD + + + + + Enable the CSRF filter for the NM web app + + yarn.nodemanager.webapp.rest-csrf.enabled + false + + + + + Optional parameter that indicates the custom header name to use for CSRF + protection. + + yarn.nodemanager.webapp.rest-csrf.custom-header + X-XSRF-Header + + + + + Optional parameter that indicates the list of HTTP methods that do not + require CSRF protection + + yarn.nodemanager.webapp.rest-csrf.methods-to-ignore + GET,OPTIONS,HEAD + + + + + Enable the CSRF filter for the timeline service web app + + yarn.timeline-service.webapp.rest-csrf.enabled + false + + + + + Optional parameter that indicates the custom header name to use for CSRF + protection. + + yarn.timeline-service.webapp.rest-csrf.custom-header + X-XSRF-Header + + + + + Optional parameter that indicates the list of HTTP methods that do not + require CSRF protection + + yarn.timeline-service.webapp.rest-csrf.methods-to-ignore + GET,OPTIONS,HEAD + 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 f4fe140d2bf..cedbd2eb1ae 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."; 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 319c10cd724..827e1b5a4c1 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) { 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 80b33a38a54..2744bb455ff 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). 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 00000000000..2efbd2d16fc --- /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 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); + } + +}