浏览代码

YARN-4009. CORS support for ResourceManager REST API. ( Varun Vasudev via jeagles)

Jonathan Eagles 9 年之前
父节点
当前提交
f8adeb712d
共有 16 个文件被更改,包括 282 次插入80 次删除
  1. 74 0
      hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HttpCrossOriginFilterInitializer.java
  2. 2 2
      hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/CrossOriginFilter.java
  3. 36 0
      hadoop-common-project/hadoop-common/src/main/resources/core-default.xml
  4. 14 0
      hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md
  5. 7 6
      hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestHttpCrossOriginFilterInitializer.java
  6. 49 43
      hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestCrossOriginFilter.java
  7. 2 0
      hadoop-yarn-project/CHANGES.txt
  8. 10 0
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
  9. 16 0
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
  10. 11 3
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
  11. 20 20
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java
  12. 5 0
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml
  13. 9 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java
  14. 10 4
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
  15. 9 1
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md
  16. 8 0
      hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md

+ 74 - 0
hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HttpCrossOriginFilterInitializer.java

@@ -0,0 +1,74 @@
+/**
+ * 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.security;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.http.FilterContainer;
+import org.apache.hadoop.http.FilterInitializer;
+import org.apache.hadoop.security.http.CrossOriginFilter;
+
+public class HttpCrossOriginFilterInitializer extends FilterInitializer {
+
+  public static final String PREFIX = "hadoop.http.cross-origin.";
+  public static final String ENABLED_SUFFIX = "enabled";
+
+  private static final Log LOG =
+      LogFactory.getLog(HttpCrossOriginFilterInitializer.class);
+
+  @Override
+  public void initFilter(FilterContainer container, Configuration conf) {
+
+    String key = getEnabledConfigKey();
+    boolean enabled = conf.getBoolean(key, false);
+    if (enabled) {
+      container.addGlobalFilter("Cross Origin Filter",
+          CrossOriginFilter.class.getName(),
+          getFilterParameters(conf, getPrefix()));
+    } else {
+      LOG.info("CORS filter not enabled. Please set " + key
+          + " to 'true' to enable it");
+    }
+  }
+
+  protected static Map<String, String> getFilterParameters(Configuration conf,
+      String prefix) {
+    Map<String, String> filterParams = new HashMap<String, String>();
+    for (Map.Entry<String, String> entry : conf.getValByRegex(prefix)
+        .entrySet()) {
+      String name = entry.getKey();
+      String value = entry.getValue();
+      name = name.substring(prefix.length());
+      filterParams.put(name, value);
+    }
+    return filterParams;
+  }
+
+  protected String getPrefix() {
+    return HttpCrossOriginFilterInitializer.PREFIX;
+  }
+
+  protected String getEnabledConfigKey() {
+    return getPrefix() + HttpCrossOriginFilterInitializer.ENABLED_SUFFIX;
+  }
+}

+ 2 - 2
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java → hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/CrossOriginFilter.java

@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.hadoop.yarn.server.timeline.webapp;
+package org.apache.hadoop.security.http;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -246,7 +246,7 @@ public class CrossOriginFilter implements Filter {
     if (accessControlRequestHeaders == null) {
       return true;
     }
-    String headers[] = accessControlRequestHeaders.trim().split("\\s*,\\s*");
+    String[] headers = accessControlRequestHeaders.trim().split("\\s*,\\s*");
     return allowedHeaders.containsAll(Arrays.asList(headers));
   }
 

+ 36 - 0
hadoop-common-project/hadoop-common/src/main/resources/core-default.xml

@@ -1404,6 +1404,42 @@ for ldap providers in the same way as above does.
   </description>
 </property>
 
+<!-- HTTP CORS support -->
+<property>
+  <description>Enable/disable the cross-origin (CORS) filter.</description>
+  <name>hadoop.http.cross-origin.enabled</name>
+  <value>false</value>
+</property>
+
+<property>
+  <description>Comma separated list of origins that are allowed for web
+    services needing cross-origin (CORS) support. Wildcards (*) and patterns
+    allowed</description>
+  <name>hadoop.http.cross-origin.allowed-origins</name>
+  <value>*</value>
+</property>
+
+<property>
+  <description>Comma separated list of methods that are allowed for web
+    services needing cross-origin (CORS) support.</description>
+  <name>hadoop.http.cross-origin.allowed-methods</name>
+  <value>GET,POST,HEAD</value>
+</property>
+
+<property>
+  <description>Comma separated list of headers that are allowed for web
+    services needing cross-origin (CORS) support.</description>
+  <name>hadoop.http.cross-origin.allowed-headers</name>
+  <value>X-Requested-With,Content-Type,Accept,Origin</value>
+</property>
+
+<property>
+  <description>The number of seconds a pre-flighted request can be cached
+    for web services needing cross-origin (CORS) support.</description>
+  <name>hadoop.http.cross-origin.max-age</name>
+  <value>1800</value>
+</property>
+
 <property>
   <name>dfs.ha.fencing.methods</name>
   <value></value>

+ 14 - 0
hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md

@@ -60,3 +60,17 @@ IMPORTANT: when using IP addresses, browsers ignore cookies with domain settings
 `hadoop.http.authentication.kerberos.principal`: Indicates the Kerberos principal to be used for HTTP endpoint when using 'kerberos' authentication. The principal short name must be `HTTP` per Kerberos HTTP SPNEGO specification. The default value is `HTTP/_HOST@$LOCALHOST`, where `_HOST` -if present- is replaced with bind address of the HTTP server.
 
 `hadoop.http.authentication.kerberos.keytab`: Location of the keytab file with the credentials for the Kerberos principal used for the HTTP endpoint. The default value is `$user.home/hadoop.keytab`.i
+
+CORS
+----
+To enable cross-origin support (CORS), please set the following configuration parameters:
+
+Add org.apache.hadoop.security.HttpCrossOriginFilterInitializer to hadoop.http.filter.initializers in core-site.xml. You will also need to set the following properties in core-site.xml -
+
+| Property | Default Value | Description |
+|:---- |:---- |:---- |:---- |
+| hadoop.http.cross-origin.enabled | false | Enables cross origin support for all web-services |
+| hadoop.http.cross-origin.allowed-origins | \* | Comma separated list of origins that are allowed, wildcards (\*) and patterns allowed |
+| hadoop.http.cross-origin.allowed-methods | GET,POST,HEAD | Comma separated list of methods that are allowed |
+| hadoop.http.cross-origin.allowed-headers | X-Requested-With,Content-Type,Accept,Origin | Comma separated list of headers that are allowed |
+| hadoop.http.cross-origin.max-age | 1800 | Number of seconds a pre-flighted request can be cached |

+ 7 - 6
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java → hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestHttpCrossOriginFilterInitializer.java

@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.hadoop.yarn.server.timeline.webapp;
+package org.apache.hadoop.security;
 
 import java.util.Map;
 
@@ -25,21 +25,22 @@ import org.apache.hadoop.conf.Configuration;
 import org.junit.Assert;
 import org.junit.Test;
 
-public class TestCrossOriginFilterInitializer {
+public class TestHttpCrossOriginFilterInitializer {
 
   @Test
   public void testGetFilterParameters() {
 
     // Initialize configuration object
     Configuration conf = new Configuration();
-    conf.set(CrossOriginFilterInitializer.PREFIX + "rootparam", "rootvalue");
-    conf.set(CrossOriginFilterInitializer.PREFIX + "nested.param",
+    conf.set(HttpCrossOriginFilterInitializer.PREFIX + "rootparam",
+        "rootvalue");
+    conf.set(HttpCrossOriginFilterInitializer.PREFIX + "nested.param",
         "nestedvalue");
     conf.set("outofscopeparam", "outofscopevalue");
 
     // call function under test
-    Map<String, String> filterParameters =
-        CrossOriginFilterInitializer.getFilterParameters(conf);
+    Map<String, String> filterParameters = HttpCrossOriginFilterInitializer
+        .getFilterParameters(conf, HttpCrossOriginFilterInitializer.PREFIX);
 
     // retrieve values
     String rootvalue = filterParameters.get("rootparam");

+ 49 - 43
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java → hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestCrossOriginFilter.java

@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package org.apache.hadoop.yarn.server.timeline.webapp;
+package org.apache.hadoop.security.http;
 
 import java.io.IOException;
 import java.util.Collections;
@@ -31,13 +31,13 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.hadoop.security.http.CrossOriginFilter;
 import org.junit.Assert;
 import org.junit.Test;
+import org.mockito.Mockito;
 
-import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 public class TestCrossOriginFilter {
 
@@ -50,20 +50,20 @@ public class TestCrossOriginFilter {
     FilterConfig filterConfig = new FilterConfigTest(conf);
 
     // Origin is not specified for same origin requests
-    HttpServletRequest mockReq = mock(HttpServletRequest.class);
-    when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn(null);
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn(null);
 
     // Objects to verify interactions based on request
-    HttpServletResponse mockRes = mock(HttpServletResponse.class);
-    FilterChain mockChain = mock(FilterChain.class);
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
 
     // Object under test
     CrossOriginFilter filter = new CrossOriginFilter();
     filter.init(filterConfig);
     filter.doFilter(mockReq, mockRes, mockChain);
 
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
   }
 
   @Test
@@ -91,11 +91,12 @@ public class TestCrossOriginFilter {
     String encodedResponseSplitOrigin =
       CrossOriginFilter.encodeHeader(httpResponseSplitOrigin);
     Assert.assertEquals("Http response split origin should be protected against",
-        validOrigin, encodedResponseSplitOrigin); 
+        validOrigin, encodedResponseSplitOrigin);
 
     // Test Origin List
     String validOriginList = "http://foo.example.com:12345 http://bar.example.com:12345";
-    String encodedValidOriginList = CrossOriginFilter.encodeHeader(validOriginList);
+    String encodedValidOriginList = CrossOriginFilter
+        .encodeHeader(validOriginList);
     Assert.assertEquals("Valid origin list encoding should match exactly",
         validOriginList, encodedValidOriginList);
   }
@@ -135,20 +136,20 @@ public class TestCrossOriginFilter {
     FilterConfig filterConfig = new FilterConfigTest(conf);
 
     // Origin is not specified for same origin requests
-    HttpServletRequest mockReq = mock(HttpServletRequest.class);
-    when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.org");
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.org");
 
     // Objects to verify interactions based on request
-    HttpServletResponse mockRes = mock(HttpServletResponse.class);
-    FilterChain mockChain = mock(FilterChain.class);
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
 
     // Object under test
     CrossOriginFilter filter = new CrossOriginFilter();
     filter.init(filterConfig);
     filter.doFilter(mockReq, mockRes, mockChain);
 
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
   }
 
   @Test
@@ -160,22 +161,23 @@ public class TestCrossOriginFilter {
     FilterConfig filterConfig = new FilterConfigTest(conf);
 
     // Origin is not specified for same origin requests
-    HttpServletRequest mockReq = mock(HttpServletRequest.class);
-    when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
         .thenReturn("DISALLOWED_METHOD");
 
     // Objects to verify interactions based on request
-    HttpServletResponse mockRes = mock(HttpServletResponse.class);
-    FilterChain mockChain = mock(FilterChain.class);
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
 
     // Object under test
     CrossOriginFilter filter = new CrossOriginFilter();
     filter.init(filterConfig);
     filter.doFilter(mockReq, mockRes, mockChain);
 
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
   }
 
   @Test
@@ -187,24 +189,26 @@ public class TestCrossOriginFilter {
     FilterConfig filterConfig = new FilterConfigTest(conf);
 
     // Origin is not specified for same origin requests
-    HttpServletRequest mockReq = mock(HttpServletRequest.class);
-    when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
         .thenReturn("GET");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
         .thenReturn("Disallowed-Header");
 
     // Objects to verify interactions based on request
-    HttpServletResponse mockRes = mock(HttpServletResponse.class);
-    FilterChain mockChain = mock(FilterChain.class);
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
 
     // Object under test
     CrossOriginFilter filter = new CrossOriginFilter();
     filter.init(filterConfig);
     filter.doFilter(mockReq, mockRes, mockChain);
 
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
   }
 
   @Test
@@ -216,32 +220,34 @@ public class TestCrossOriginFilter {
     FilterConfig filterConfig = new FilterConfigTest(conf);
 
     // Origin is not specified for same origin requests
-    HttpServletRequest mockReq = mock(HttpServletRequest.class);
-    when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
+    HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
+    Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
         .thenReturn("GET");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
         .thenReturn("X-Requested-With");
 
     // Objects to verify interactions based on request
-    HttpServletResponse mockRes = mock(HttpServletResponse.class);
-    FilterChain mockChain = mock(FilterChain.class);
+    HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
+    FilterChain mockChain = Mockito.mock(FilterChain.class);
 
     // Object under test
     CrossOriginFilter filter = new CrossOriginFilter();
     filter.init(filterConfig);
     filter.doFilter(mockReq, mockRes, mockChain);
 
-    verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN,
+    Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN,
         "example.com");
-    verify(mockRes).setHeader(
+    Mockito.verify(mockRes).setHeader(
         CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS,
         Boolean.TRUE.toString());
-    verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS,
+    Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS,
         filter.getAllowedMethodsHeader());
-    verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS,
+    Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS,
         filter.getAllowedHeadersHeader());
-    verify(mockChain).doFilter(mockReq, mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
   }
 
   @Test

+ 2 - 0
hadoop-yarn-project/CHANGES.txt

@@ -988,6 +988,8 @@ Release 2.7.2 - UNRELEASED
 
   IMPROVEMENTS
 
+    YARN-4009. CORS support for ResourceManager REST API. ( Varun Vasudev via jeagles)
+
     YARN-3170. YARN architecture document needs updating. (Brahma Reddy Battula
     via ozawa)
 

+ 10 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java

@@ -337,6 +337,11 @@ public class YarnConfiguration extends Configuration {
   public static final boolean DEFAULT_RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER =
       true;
 
+  /** Enable cross origin (CORS) support. **/
+  public static final String RM_WEBAPP_ENABLE_CORS_FILTER =
+      RM_PREFIX + "webapp.cross-origin.enabled";
+  public static final boolean DEFAULT_RM_WEBAPP_ENABLE_CORS_FILTER = false;
+
   /** How long to wait until a container is considered dead.*/
   public static final String RM_CONTAINER_ALLOC_EXPIRY_INTERVAL_MS = 
     RM_PREFIX + "rm.container-allocation.expiry-interval-ms";
@@ -974,6 +979,11 @@ public class YarnConfiguration extends Configuration {
   public static final String DEFAULT_NM_WEBAPP_HTTPS_ADDRESS = "0.0.0.0:"
       + DEFAULT_NM_WEBAPP_HTTPS_PORT; 
 
+  /** Enable/disable CORS filter. */
+  public static final String NM_WEBAPP_ENABLE_CORS_FILTER =
+      NM_PREFIX + "webapp.cross-origin.enabled";
+  public static final boolean DEFAULT_NM_WEBAPP_ENABLE_CORS_FILTER = false;
+
   /** How often to monitor resource in a node.*/
   public static final String NM_RESOURCE_MON_INTERVAL_MS =
       NM_PREFIX + "resource-monitor.interval-ms";

+ 16 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml

@@ -254,6 +254,14 @@
     <value>true</value>
   </property>
 
+  <property>
+    <description>Flag to enable cross-origin (CORS) support in the RM. This flag
+    requires the CORS filter initializer to be added to the filter initializers
+    list in core-site.xml.</description>
+    <name>yarn.resourcemanager.webapp.cross-origin.enabled</name>
+    <value>false</value>
+  </property>
+
   <property>
     <description>How long to wait until a node manager is considered dead.</description>
     <name>yarn.nm.liveness-monitor.expiry-interval-ms</name>
@@ -2317,6 +2325,14 @@
     <value>false</value>
   </property>
 
+  <property>
+    <description>Flag to enable cross-origin (CORS) support in the NM. This flag
+    requires the CORS filter initializer to be added to the filter initializers
+    list in core-site.xml.</description>
+    <name>yarn.nodemanager.webapp.cross-origin.enabled</name>
+    <value>false</value>
+  </property>
+
   <property>
     <description>
     Defines maximum application priority in a cluster.

+ 11 - 3
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java

@@ -30,6 +30,7 @@ import org.apache.hadoop.http.HttpServer2;
 import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
 import org.apache.hadoop.metrics2.source.JvmMetrics;
 import org.apache.hadoop.security.AuthenticationFilterInitializer;
+import org.apache.hadoop.security.HttpCrossOriginFilterInitializer;
 import org.apache.hadoop.security.SecurityUtil;
 import org.apache.hadoop.service.CompositeService;
 import org.apache.hadoop.service.Service;
@@ -252,10 +253,17 @@ public class ApplicationHistoryServer extends CompositeService {
       if(conf.getBoolean(YarnConfiguration
           .TIMELINE_SERVICE_HTTP_CROSS_ORIGIN_ENABLED, YarnConfiguration
               .TIMELINE_SERVICE_HTTP_CROSS_ORIGIN_ENABLED_DEFAULT)) {
-        if (initializers.length() != 0) {
-          initializers += ",";
+        if (initializers.contains(HttpCrossOriginFilterInitializer.class.getName())) {
+          initializers =
+            initializers.replaceAll(HttpCrossOriginFilterInitializer.class.getName(),
+              CrossOriginFilterInitializer.class.getName());
+        }
+        else {
+          if (initializers.length() != 0) {
+            initializers += ",";
+          }
+          initializers += CrossOriginFilterInitializer.class.getName();
         }
-        initializers += CrossOriginFilterInitializer.class.getName();
         modifiedInitializers = true;
       }
     }

+ 20 - 20
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java

@@ -18,35 +18,35 @@
 
 package org.apache.hadoop.yarn.server.timeline.webapp;
 
-import java.util.HashMap;
-import java.util.Map;
-
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.http.FilterContainer;
-import org.apache.hadoop.http.FilterInitializer;
+import org.apache.hadoop.security.HttpCrossOriginFilterInitializer;
+import org.apache.hadoop.security.http.CrossOriginFilter;
+
+import java.util.Map;
 
-public class CrossOriginFilterInitializer extends FilterInitializer {
+public class CrossOriginFilterInitializer extends HttpCrossOriginFilterInitializer {
 
   public static final String PREFIX =
       "yarn.timeline-service.http-cross-origin.";
 
+  @Override
+  protected String getPrefix() {
+    return PREFIX;
+  }
+
   @Override
   public void initFilter(FilterContainer container, Configuration conf) {
 
-    container.addGlobalFilter("Cross Origin Filter",
-        CrossOriginFilter.class.getName(), getFilterParameters(conf));
-  }
+    // setup the filter
+    // use the keys with "yarn.timeline-service.http-cross-origin" prefix to
+    // override the ones with the "hadoop.http.cross-origin" prefix.
 
-  static Map<String, String> getFilterParameters(Configuration conf) {
-    Map<String, String> filterParams =
-        new HashMap<String, String>();
-    for (Map.Entry<String, String> entry : conf.getValByRegex(PREFIX)
-        .entrySet()) {
-      String name = entry.getKey();
-      String value = entry.getValue();
-      name = name.substring(PREFIX.length());
-      filterParams.put(name, value);
-    }
-    return filterParams;
+    Map<String, String> filterParameters =
+        getFilterParameters(conf, HttpCrossOriginFilterInitializer.PREFIX);
+    filterParameters.putAll(getFilterParameters(conf, getPrefix()));
+
+    container.addGlobalFilter("Cross Origin Filter",
+          CrossOriginFilter.class.getName(), filterParameters);
   }
-}
+}

+ 5 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml

@@ -81,6 +81,11 @@
       <artifactId>junit</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.zookeeper</groupId>
       <artifactId>zookeeper</artifactId>

+ 9 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java

@@ -22,6 +22,7 @@ import static org.apache.hadoop.yarn.util.StringHelper.pajoin;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.security.HttpCrossOriginFilterInitializer;
 import org.apache.hadoop.service.AbstractService;
 import org.apache.hadoop.yarn.conf.YarnConfiguration;
 import org.apache.hadoop.yarn.exceptions.YarnRuntimeException;
@@ -59,7 +60,14 @@ public class WebServer extends AbstractService {
     String bindAddress = WebAppUtils.getWebAppBindURL(getConfig(),
                           YarnConfiguration.NM_BIND_HOST,
                           WebAppUtils.getNMWebAppURLWithoutScheme(getConfig()));
-    
+    boolean enableCors = getConfig()
+        .getBoolean(YarnConfiguration.NM_WEBAPP_ENABLE_CORS_FILTER,
+            YarnConfiguration.DEFAULT_NM_WEBAPP_ENABLE_CORS_FILTER);
+    if (enableCors) {
+      getConfig().setBoolean(HttpCrossOriginFilterInitializer.PREFIX
+          + HttpCrossOriginFilterInitializer.ENABLED_SUFFIX, true);
+    }
+
     LOG.info("Instantiating NMWebApp at " + bindAddress);
     try {
       this.webApp =

+ 10 - 4
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java

@@ -28,10 +28,7 @@ import org.apache.hadoop.ha.HAServiceProtocol.HAServiceState;
 import org.apache.hadoop.http.lib.StaticUserWebFilter;
 import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
 import org.apache.hadoop.metrics2.source.JvmMetrics;
-import org.apache.hadoop.security.AuthenticationFilterInitializer;
-import org.apache.hadoop.security.Groups;
-import org.apache.hadoop.security.SecurityUtil;
-import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.*;
 import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
 import org.apache.hadoop.security.authorize.ProxyUsers;
 import org.apache.hadoop.service.AbstractService;
@@ -866,6 +863,9 @@ public class ResourceManager extends CompositeService implements Recoverable {
     // 4. hadoop.http.filter.initializers container AuthenticationFilterInitializer
 
     Configuration conf = getConfig();
+    boolean enableCorsFilter =
+        conf.getBoolean(YarnConfiguration.RM_WEBAPP_ENABLE_CORS_FILTER,
+            YarnConfiguration.DEFAULT_RM_WEBAPP_ENABLE_CORS_FILTER);
     boolean useYarnAuthenticationFilter =
         conf.getBoolean(
           YarnConfiguration.RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER,
@@ -877,6 +877,12 @@ public class ResourceManager extends CompositeService implements Recoverable {
     Class<?>[] initializersClasses =
         conf.getClasses(filterInitializerConfKey);
 
+    // setup CORS
+    if (enableCorsFilter) {
+      conf.setBoolean(HttpCrossOriginFilterInitializer.PREFIX
+          + HttpCrossOriginFilterInitializer.ENABLED_SUFFIX, true);
+    }
+
     boolean hasHadoopAuthFilterInitializer = false;
     boolean hasRMAuthFilterInitializer = false;
     if (initializersClasses != null) {

+ 9 - 1
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md

@@ -16,6 +16,7 @@ NodeManager REST API's
 =======================
 
 * [Overview](#Overview)
+* [Enabling CORS support](#Enabling_CORS_support)
 * [NodeManager Information API](#NodeManager_Information_API)
 * [Applications API](#Applications_API)
 * [Application API](#Application_API)
@@ -27,6 +28,13 @@ Overview
 
 The NodeManager REST API's allow the user to get status on the node and information about applications and containers running on that node.
 
+Enabling CORS support
+---------------------
+To enable cross-origin support (CORS) for the NM only(without enabling it for the RM), please set the following configuration parameters:
+
+In core-site.xml, add org.apache.hadoop.security.HttpCrossOriginFilterInitializer to hadoop.http.filter.initializers.
+In yarn-site.xml, set yarn.nodemanager.webapp.cross-origin.enabled to true.
+
 NodeManager Information API
 ---------------------------
 
@@ -540,4 +548,4 @@ Response Body:
   <containerLogsLink>http://host.domain.com:8042/node/containerlogs/container_1326121700862_0007_01_000001/user1</containerLogsLink>
   <nodeId>host.domain.com:8041</nodeId>
 </container>
-```
+```

+ 8 - 0
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md

@@ -16,6 +16,7 @@ ResourceManager REST API's.
 ===========================
 
 * [Overview](#Overview)
+* [Enabling CORS support](#Enabling_CORS_support)
 * [Cluster Information API](#Cluster_Information_API)
 * [Cluster Metrics API](#Cluster_Metrics_API)
 * [Cluster Scheduler API](#Cluster_Scheduler_API)
@@ -38,6 +39,13 @@ Overview
 
 The ResourceManager REST API's allow the user to get information about the cluster - status on the cluster, metrics on the cluster, scheduler information, information about nodes in the cluster, and information about applications on the cluster.
 
+Enabling CORS support
+---------------------
+To enable cross-origin support (CORS) for the RM only(without enabling it for the NM), please set the following configuration parameters:
+
+In core-site.xml, add org.apache.hadoop.security.HttpCrossOriginFilterInitializer to hadoop.http.filter.initializers.
+In yarn-site.xml, set yarn.resourcemanager.webapp.cross-origin.enabled to true.
+
 Cluster Information API
 -----------------------