فهرست منبع

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.
  * limitations under the License.
  */
  */
 
 
-package org.apache.hadoop.yarn.server.timeline.webapp;
+package org.apache.hadoop.security.http;
 
 
 import java.io.IOException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.ArrayList;
@@ -246,7 +246,7 @@ public class CrossOriginFilter implements Filter {
     if (accessControlRequestHeaders == null) {
     if (accessControlRequestHeaders == null) {
       return true;
       return true;
     }
     }
-    String headers[] = accessControlRequestHeaders.trim().split("\\s*,\\s*");
+    String[] headers = accessControlRequestHeaders.trim().split("\\s*,\\s*");
     return allowedHeaders.containsAll(Arrays.asList(headers));
     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>
   </description>
 </property>
 </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>
 <property>
   <name>dfs.ha.fencing.methods</name>
   <name>dfs.ha.fencing.methods</name>
   <value></value>
   <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.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
 `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.
  * limitations under the License.
  */
  */
 
 
-package org.apache.hadoop.yarn.server.timeline.webapp;
+package org.apache.hadoop.security;
 
 
 import java.util.Map;
 import java.util.Map;
 
 
@@ -25,21 +25,22 @@ import org.apache.hadoop.conf.Configuration;
 import org.junit.Assert;
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.Test;
 
 
-public class TestCrossOriginFilterInitializer {
+public class TestHttpCrossOriginFilterInitializer {
 
 
   @Test
   @Test
   public void testGetFilterParameters() {
   public void testGetFilterParameters() {
 
 
     // Initialize configuration object
     // Initialize configuration object
     Configuration conf = new Configuration();
     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");
         "nestedvalue");
     conf.set("outofscopeparam", "outofscopevalue");
     conf.set("outofscopeparam", "outofscopevalue");
 
 
     // call function under test
     // call function under test
-    Map<String, String> filterParameters =
-        CrossOriginFilterInitializer.getFilterParameters(conf);
+    Map<String, String> filterParameters = HttpCrossOriginFilterInitializer
+        .getFilterParameters(conf, HttpCrossOriginFilterInitializer.PREFIX);
 
 
     // retrieve values
     // retrieve values
     String rootvalue = filterParameters.get("rootparam");
     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.
  * limitations under the License.
  */
  */
 
 
-package org.apache.hadoop.yarn.server.timeline.webapp;
+package org.apache.hadoop.security.http;
 
 
 import java.io.IOException;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.Collections;
@@ -31,13 +31,13 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponse;
 
 
+import org.apache.hadoop.security.http.CrossOriginFilter;
 import org.junit.Assert;
 import org.junit.Assert;
 import org.junit.Test;
 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.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 
 public class TestCrossOriginFilter {
 public class TestCrossOriginFilter {
 
 
@@ -50,20 +50,20 @@ public class TestCrossOriginFilter {
     FilterConfig filterConfig = new FilterConfigTest(conf);
     FilterConfig filterConfig = new FilterConfigTest(conf);
 
 
     // Origin is not specified for same origin requests
     // 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
     // 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
     // Object under test
     CrossOriginFilter filter = new CrossOriginFilter();
     CrossOriginFilter filter = new CrossOriginFilter();
     filter.init(filterConfig);
     filter.init(filterConfig);
     filter.doFilter(mockReq, mockRes, mockChain);
     filter.doFilter(mockReq, mockRes, mockChain);
 
 
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
   }
   }
 
 
   @Test
   @Test
@@ -91,11 +91,12 @@ public class TestCrossOriginFilter {
     String encodedResponseSplitOrigin =
     String encodedResponseSplitOrigin =
       CrossOriginFilter.encodeHeader(httpResponseSplitOrigin);
       CrossOriginFilter.encodeHeader(httpResponseSplitOrigin);
     Assert.assertEquals("Http response split origin should be protected against",
     Assert.assertEquals("Http response split origin should be protected against",
-        validOrigin, encodedResponseSplitOrigin); 
+        validOrigin, encodedResponseSplitOrigin);
 
 
     // Test Origin List
     // Test Origin List
     String validOriginList = "http://foo.example.com:12345 http://bar.example.com:12345";
     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",
     Assert.assertEquals("Valid origin list encoding should match exactly",
         validOriginList, encodedValidOriginList);
         validOriginList, encodedValidOriginList);
   }
   }
@@ -135,20 +136,20 @@ public class TestCrossOriginFilter {
     FilterConfig filterConfig = new FilterConfigTest(conf);
     FilterConfig filterConfig = new FilterConfigTest(conf);
 
 
     // Origin is not specified for same origin requests
     // 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
     // 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
     // Object under test
     CrossOriginFilter filter = new CrossOriginFilter();
     CrossOriginFilter filter = new CrossOriginFilter();
     filter.init(filterConfig);
     filter.init(filterConfig);
     filter.doFilter(mockReq, mockRes, mockChain);
     filter.doFilter(mockReq, mockRes, mockChain);
 
 
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
   }
   }
 
 
   @Test
   @Test
@@ -160,22 +161,23 @@ public class TestCrossOriginFilter {
     FilterConfig filterConfig = new FilterConfigTest(conf);
     FilterConfig filterConfig = new FilterConfigTest(conf);
 
 
     // Origin is not specified for same origin requests
     // 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");
         .thenReturn("DISALLOWED_METHOD");
 
 
     // Objects to verify interactions based on request
     // 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
     // Object under test
     CrossOriginFilter filter = new CrossOriginFilter();
     CrossOriginFilter filter = new CrossOriginFilter();
     filter.init(filterConfig);
     filter.init(filterConfig);
     filter.doFilter(mockReq, mockRes, mockChain);
     filter.doFilter(mockReq, mockRes, mockChain);
 
 
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
   }
   }
 
 
   @Test
   @Test
@@ -187,24 +189,26 @@ public class TestCrossOriginFilter {
     FilterConfig filterConfig = new FilterConfigTest(conf);
     FilterConfig filterConfig = new FilterConfigTest(conf);
 
 
     // Origin is not specified for same origin requests
     // 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");
         .thenReturn("GET");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
         .thenReturn("Disallowed-Header");
         .thenReturn("Disallowed-Header");
 
 
     // Objects to verify interactions based on request
     // 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
     // Object under test
     CrossOriginFilter filter = new CrossOriginFilter();
     CrossOriginFilter filter = new CrossOriginFilter();
     filter.init(filterConfig);
     filter.init(filterConfig);
     filter.doFilter(mockReq, mockRes, mockChain);
     filter.doFilter(mockReq, mockRes, mockChain);
 
 
-    verifyZeroInteractions(mockRes);
-    verify(mockChain).doFilter(mockReq, mockRes);
+    Mockito.verifyZeroInteractions(mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
   }
   }
 
 
   @Test
   @Test
@@ -216,32 +220,34 @@ public class TestCrossOriginFilter {
     FilterConfig filterConfig = new FilterConfigTest(conf);
     FilterConfig filterConfig = new FilterConfigTest(conf);
 
 
     // Origin is not specified for same origin requests
     // 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");
         .thenReturn("GET");
-    when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
+    Mockito.when(
+        mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS))
         .thenReturn("X-Requested-With");
         .thenReturn("X-Requested-With");
 
 
     // Objects to verify interactions based on request
     // 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
     // Object under test
     CrossOriginFilter filter = new CrossOriginFilter();
     CrossOriginFilter filter = new CrossOriginFilter();
     filter.init(filterConfig);
     filter.init(filterConfig);
     filter.doFilter(mockReq, mockRes, mockChain);
     filter.doFilter(mockReq, mockRes, mockChain);
 
 
-    verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN,
+    Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN,
         "example.com");
         "example.com");
-    verify(mockRes).setHeader(
+    Mockito.verify(mockRes).setHeader(
         CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS,
         CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS,
         Boolean.TRUE.toString());
         Boolean.TRUE.toString());
-    verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS,
+    Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS,
         filter.getAllowedMethodsHeader());
         filter.getAllowedMethodsHeader());
-    verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS,
+    Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS,
         filter.getAllowedHeadersHeader());
         filter.getAllowedHeadersHeader());
-    verify(mockChain).doFilter(mockReq, mockRes);
+    Mockito.verify(mockChain).doFilter(mockReq, mockRes);
   }
   }
 
 
   @Test
   @Test

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

@@ -988,6 +988,8 @@ Release 2.7.2 - UNRELEASED
 
 
   IMPROVEMENTS
   IMPROVEMENTS
 
 
+    YARN-4009. CORS support for ResourceManager REST API. ( Varun Vasudev via jeagles)
+
     YARN-3170. YARN architecture document needs updating. (Brahma Reddy Battula
     YARN-3170. YARN architecture document needs updating. (Brahma Reddy Battula
     via ozawa)
     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 =
   public static final boolean DEFAULT_RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER =
       true;
       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.*/
   /** How long to wait until a container is considered dead.*/
   public static final String RM_CONTAINER_ALLOC_EXPIRY_INTERVAL_MS = 
   public static final String RM_CONTAINER_ALLOC_EXPIRY_INTERVAL_MS = 
     RM_PREFIX + "rm.container-allocation.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:"
   public static final String DEFAULT_NM_WEBAPP_HTTPS_ADDRESS = "0.0.0.0:"
       + DEFAULT_NM_WEBAPP_HTTPS_PORT; 
       + 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.*/
   /** How often to monitor resource in a node.*/
   public static final String NM_RESOURCE_MON_INTERVAL_MS =
   public static final String NM_RESOURCE_MON_INTERVAL_MS =
       NM_PREFIX + "resource-monitor.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>
     <value>true</value>
   </property>
   </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>
   <property>
     <description>How long to wait until a node manager is considered dead.</description>
     <description>How long to wait until a node manager is considered dead.</description>
     <name>yarn.nm.liveness-monitor.expiry-interval-ms</name>
     <name>yarn.nm.liveness-monitor.expiry-interval-ms</name>
@@ -2317,6 +2325,14 @@
     <value>false</value>
     <value>false</value>
   </property>
   </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>
   <property>
     <description>
     <description>
     Defines maximum application priority in a cluster.
     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.lib.DefaultMetricsSystem;
 import org.apache.hadoop.metrics2.source.JvmMetrics;
 import org.apache.hadoop.metrics2.source.JvmMetrics;
 import org.apache.hadoop.security.AuthenticationFilterInitializer;
 import org.apache.hadoop.security.AuthenticationFilterInitializer;
+import org.apache.hadoop.security.HttpCrossOriginFilterInitializer;
 import org.apache.hadoop.security.SecurityUtil;
 import org.apache.hadoop.security.SecurityUtil;
 import org.apache.hadoop.service.CompositeService;
 import org.apache.hadoop.service.CompositeService;
 import org.apache.hadoop.service.Service;
 import org.apache.hadoop.service.Service;
@@ -252,10 +253,17 @@ public class ApplicationHistoryServer extends CompositeService {
       if(conf.getBoolean(YarnConfiguration
       if(conf.getBoolean(YarnConfiguration
           .TIMELINE_SERVICE_HTTP_CROSS_ORIGIN_ENABLED, YarnConfiguration
           .TIMELINE_SERVICE_HTTP_CROSS_ORIGIN_ENABLED, YarnConfiguration
               .TIMELINE_SERVICE_HTTP_CROSS_ORIGIN_ENABLED_DEFAULT)) {
               .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;
         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;
 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.conf.Configuration;
 import org.apache.hadoop.http.FilterContainer;
 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 =
   public static final String PREFIX =
       "yarn.timeline-service.http-cross-origin.";
       "yarn.timeline-service.http-cross-origin.";
 
 
+  @Override
+  protected String getPrefix() {
+    return PREFIX;
+  }
+
   @Override
   @Override
   public void initFilter(FilterContainer container, Configuration conf) {
   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>
       <artifactId>junit</artifactId>
       <scope>test</scope>
       <scope>test</scope>
     </dependency>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-all</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
     <dependency>
       <groupId>org.apache.zookeeper</groupId>
       <groupId>org.apache.zookeeper</groupId>
       <artifactId>zookeeper</artifactId>
       <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.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.security.HttpCrossOriginFilterInitializer;
 import org.apache.hadoop.service.AbstractService;
 import org.apache.hadoop.service.AbstractService;
 import org.apache.hadoop.yarn.conf.YarnConfiguration;
 import org.apache.hadoop.yarn.conf.YarnConfiguration;
 import org.apache.hadoop.yarn.exceptions.YarnRuntimeException;
 import org.apache.hadoop.yarn.exceptions.YarnRuntimeException;
@@ -59,7 +60,14 @@ public class WebServer extends AbstractService {
     String bindAddress = WebAppUtils.getWebAppBindURL(getConfig(),
     String bindAddress = WebAppUtils.getWebAppBindURL(getConfig(),
                           YarnConfiguration.NM_BIND_HOST,
                           YarnConfiguration.NM_BIND_HOST,
                           WebAppUtils.getNMWebAppURLWithoutScheme(getConfig()));
                           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);
     LOG.info("Instantiating NMWebApp at " + bindAddress);
     try {
     try {
       this.webApp =
       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.http.lib.StaticUserWebFilter;
 import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
 import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
 import org.apache.hadoop.metrics2.source.JvmMetrics;
 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.authentication.server.KerberosAuthenticationHandler;
 import org.apache.hadoop.security.authorize.ProxyUsers;
 import org.apache.hadoop.security.authorize.ProxyUsers;
 import org.apache.hadoop.service.AbstractService;
 import org.apache.hadoop.service.AbstractService;
@@ -866,6 +863,9 @@ public class ResourceManager extends CompositeService implements Recoverable {
     // 4. hadoop.http.filter.initializers container AuthenticationFilterInitializer
     // 4. hadoop.http.filter.initializers container AuthenticationFilterInitializer
 
 
     Configuration conf = getConfig();
     Configuration conf = getConfig();
+    boolean enableCorsFilter =
+        conf.getBoolean(YarnConfiguration.RM_WEBAPP_ENABLE_CORS_FILTER,
+            YarnConfiguration.DEFAULT_RM_WEBAPP_ENABLE_CORS_FILTER);
     boolean useYarnAuthenticationFilter =
     boolean useYarnAuthenticationFilter =
         conf.getBoolean(
         conf.getBoolean(
           YarnConfiguration.RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER,
           YarnConfiguration.RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER,
@@ -877,6 +877,12 @@ public class ResourceManager extends CompositeService implements Recoverable {
     Class<?>[] initializersClasses =
     Class<?>[] initializersClasses =
         conf.getClasses(filterInitializerConfKey);
         conf.getClasses(filterInitializerConfKey);
 
 
+    // setup CORS
+    if (enableCorsFilter) {
+      conf.setBoolean(HttpCrossOriginFilterInitializer.PREFIX
+          + HttpCrossOriginFilterInitializer.ENABLED_SUFFIX, true);
+    }
+
     boolean hasHadoopAuthFilterInitializer = false;
     boolean hasHadoopAuthFilterInitializer = false;
     boolean hasRMAuthFilterInitializer = false;
     boolean hasRMAuthFilterInitializer = false;
     if (initializersClasses != null) {
     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)
 * [Overview](#Overview)
+* [Enabling CORS support](#Enabling_CORS_support)
 * [NodeManager Information API](#NodeManager_Information_API)
 * [NodeManager Information API](#NodeManager_Information_API)
 * [Applications API](#Applications_API)
 * [Applications API](#Applications_API)
 * [Application API](#Application_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.
 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
 NodeManager Information API
 ---------------------------
 ---------------------------
 
 
@@ -540,4 +548,4 @@ Response Body:
   <containerLogsLink>http://host.domain.com:8042/node/containerlogs/container_1326121700862_0007_01_000001/user1</containerLogsLink>
   <containerLogsLink>http://host.domain.com:8042/node/containerlogs/container_1326121700862_0007_01_000001/user1</containerLogsLink>
   <nodeId>host.domain.com:8041</nodeId>
   <nodeId>host.domain.com:8041</nodeId>
 </container>
 </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)
 * [Overview](#Overview)
+* [Enabling CORS support](#Enabling_CORS_support)
 * [Cluster Information API](#Cluster_Information_API)
 * [Cluster Information API](#Cluster_Information_API)
 * [Cluster Metrics API](#Cluster_Metrics_API)
 * [Cluster Metrics API](#Cluster_Metrics_API)
 * [Cluster Scheduler API](#Cluster_Scheduler_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.
 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
 Cluster Information API
 -----------------------
 -----------------------