YARN-4009. CORS support for ResourceManager REST API. ( Varun Vasudev via jeagles)
Project: http://git-wip-us.apache.org/repos/asf/hadoop/repo Commit: http://git-wip-us.apache.org/repos/asf/hadoop/commit/f8adeb71 Tree: http://git-wip-us.apache.org/repos/asf/hadoop/tree/f8adeb71 Diff: http://git-wip-us.apache.org/repos/asf/hadoop/diff/f8adeb71 Branch: refs/heads/HADOOP-11890 Commit: f8adeb712dc834c27cec15c04a986f2f635aba83 Parents: 35a303d Author: Jonathan Eagles <jeag...@yahoo-inc.com> Authored: Fri Oct 23 10:34:08 2015 -0500 Committer: Jonathan Eagles <jeag...@yahoo-inc.com> Committed: Fri Oct 23 10:34:08 2015 -0500 ---------------------------------------------------------------------- .../HttpCrossOriginFilterInitializer.java | 74 +++++ .../hadoop/security/http/CrossOriginFilter.java | 259 +++++++++++++++ .../src/main/resources/core-default.xml | 36 ++ .../src/site/markdown/HttpAuthentication.md | 14 + .../TestHttpCrossOriginFilterInitializer.java | 58 ++++ .../security/http/TestCrossOriginFilter.java | 330 +++++++++++++++++++ hadoop-yarn-project/CHANGES.txt | 2 + .../hadoop/yarn/conf/YarnConfiguration.java | 10 + .../src/main/resources/yarn-default.xml | 16 + .../ApplicationHistoryServer.java | 14 +- .../timeline/webapp/CrossOriginFilter.java | 259 --------------- .../webapp/CrossOriginFilterInitializer.java | 40 +-- .../timeline/webapp/TestCrossOriginFilter.java | 324 ------------------ .../TestCrossOriginFilterInitializer.java | 57 ---- .../hadoop-yarn-server-common/pom.xml | 5 + .../server/nodemanager/webapp/WebServer.java | 10 +- .../server/resourcemanager/ResourceManager.java | 14 +- .../src/site/markdown/NodeManagerRest.md | 10 +- .../src/site/markdown/ResourceManagerRest.md | 8 + 19 files changed, 871 insertions(+), 669 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HttpCrossOriginFilterInitializer.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HttpCrossOriginFilterInitializer.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/HttpCrossOriginFilterInitializer.java new file mode 100644 index 0000000..f9c1816 --- /dev/null +++ b/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; + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/CrossOriginFilter.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/CrossOriginFilter.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/CrossOriginFilter.java new file mode 100644 index 0000000..ea78762 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/http/CrossOriginFilter.java @@ -0,0 +1,259 @@ +/** + * 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.http; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.google.common.annotations.VisibleForTesting; + +public class CrossOriginFilter implements Filter { + + private static final Log LOG = LogFactory.getLog(CrossOriginFilter.class); + + // HTTP CORS Request Headers + static final String ORIGIN = "Origin"; + static final String ACCESS_CONTROL_REQUEST_METHOD = + "Access-Control-Request-Method"; + static final String ACCESS_CONTROL_REQUEST_HEADERS = + "Access-Control-Request-Headers"; + + // HTTP CORS Response Headers + static final String ACCESS_CONTROL_ALLOW_ORIGIN = + "Access-Control-Allow-Origin"; + static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = + "Access-Control-Allow-Credentials"; + static final String ACCESS_CONTROL_ALLOW_METHODS = + "Access-Control-Allow-Methods"; + static final String ACCESS_CONTROL_ALLOW_HEADERS = + "Access-Control-Allow-Headers"; + static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; + + // Filter configuration + public static final String ALLOWED_ORIGINS = "allowed-origins"; + public static final String ALLOWED_ORIGINS_DEFAULT = "*"; + public static final String ALLOWED_METHODS = "allowed-methods"; + public static final String ALLOWED_METHODS_DEFAULT = "GET,POST,HEAD"; + public static final String ALLOWED_HEADERS = "allowed-headers"; + public static final String ALLOWED_HEADERS_DEFAULT = + "X-Requested-With,Content-Type,Accept,Origin"; + public static final String MAX_AGE = "max-age"; + public static final String MAX_AGE_DEFAULT = "1800"; + + private List<String> allowedMethods = new ArrayList<String>(); + private List<String> allowedHeaders = new ArrayList<String>(); + private List<String> allowedOrigins = new ArrayList<String>(); + private boolean allowAllOrigins = true; + private String maxAge; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + initializeAllowedMethods(filterConfig); + initializeAllowedHeaders(filterConfig); + initializeAllowedOrigins(filterConfig); + initializeMaxAge(filterConfig); + } + + @Override + public void doFilter(ServletRequest req, ServletResponse res, + FilterChain chain) + throws IOException, ServletException { + doCrossFilter((HttpServletRequest) req, (HttpServletResponse) res); + chain.doFilter(req, res); + } + + @Override + public void destroy() { + allowedMethods.clear(); + allowedHeaders.clear(); + allowedOrigins.clear(); + } + + private void doCrossFilter(HttpServletRequest req, HttpServletResponse res) { + + String originsList = encodeHeader(req.getHeader(ORIGIN)); + if (!isCrossOrigin(originsList)) { + if(LOG.isDebugEnabled()) { + LOG.debug("Header origin is null. Returning"); + } + return; + } + + if (!areOriginsAllowed(originsList)) { + if(LOG.isDebugEnabled()) { + LOG.debug("Header origins '" + originsList + "' not allowed. Returning"); + } + return; + } + + String accessControlRequestMethod = + req.getHeader(ACCESS_CONTROL_REQUEST_METHOD); + if (!isMethodAllowed(accessControlRequestMethod)) { + if(LOG.isDebugEnabled()) { + LOG.debug("Access control method '" + accessControlRequestMethod + + "' not allowed. Returning"); + } + return; + } + + String accessControlRequestHeaders = + req.getHeader(ACCESS_CONTROL_REQUEST_HEADERS); + if (!areHeadersAllowed(accessControlRequestHeaders)) { + if(LOG.isDebugEnabled()) { + LOG.debug("Access control headers '" + accessControlRequestHeaders + + "' not allowed. Returning"); + } + return; + } + + if(LOG.isDebugEnabled()) { + LOG.debug("Completed cross origin filter checks. Populating " + + "HttpServletResponse"); + } + res.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, originsList); + res.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString()); + res.setHeader(ACCESS_CONTROL_ALLOW_METHODS, getAllowedMethodsHeader()); + res.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, getAllowedHeadersHeader()); + res.setHeader(ACCESS_CONTROL_MAX_AGE, maxAge); + } + + @VisibleForTesting + String getAllowedHeadersHeader() { + return StringUtils.join(allowedHeaders, ','); + } + + @VisibleForTesting + String getAllowedMethodsHeader() { + return StringUtils.join(allowedMethods, ','); + } + + private void initializeAllowedMethods(FilterConfig filterConfig) { + String allowedMethodsConfig = + filterConfig.getInitParameter(ALLOWED_METHODS); + if (allowedMethodsConfig == null) { + allowedMethodsConfig = ALLOWED_METHODS_DEFAULT; + } + allowedMethods.addAll( + Arrays.asList(allowedMethodsConfig.trim().split("\\s*,\\s*"))); + LOG.info("Allowed Methods: " + getAllowedMethodsHeader()); + } + + private void initializeAllowedHeaders(FilterConfig filterConfig) { + String allowedHeadersConfig = + filterConfig.getInitParameter(ALLOWED_HEADERS); + if (allowedHeadersConfig == null) { + allowedHeadersConfig = ALLOWED_HEADERS_DEFAULT; + } + allowedHeaders.addAll( + Arrays.asList(allowedHeadersConfig.trim().split("\\s*,\\s*"))); + LOG.info("Allowed Headers: " + getAllowedHeadersHeader()); + } + + private void initializeAllowedOrigins(FilterConfig filterConfig) { + String allowedOriginsConfig = + filterConfig.getInitParameter(ALLOWED_ORIGINS); + if (allowedOriginsConfig == null) { + allowedOriginsConfig = ALLOWED_ORIGINS_DEFAULT; + } + allowedOrigins.addAll( + Arrays.asList(allowedOriginsConfig.trim().split("\\s*,\\s*"))); + allowAllOrigins = allowedOrigins.contains("*"); + LOG.info("Allowed Origins: " + StringUtils.join(allowedOrigins, ',')); + LOG.info("Allow All Origins: " + allowAllOrigins); + } + + private void initializeMaxAge(FilterConfig filterConfig) { + maxAge = filterConfig.getInitParameter(MAX_AGE); + if (maxAge == null) { + maxAge = MAX_AGE_DEFAULT; + } + LOG.info("Max Age: " + maxAge); + } + + static String encodeHeader(final String header) { + if (header == null) { + return null; + } + // Protect against HTTP response splitting vulnerability + // since value is written as part of the response header + // Ensure this header only has one header by removing + // CRs and LFs + return header.split("\n|\r")[0].trim(); + } + + static boolean isCrossOrigin(String originsList) { + return originsList != null; + } + + @VisibleForTesting + boolean areOriginsAllowed(String originsList) { + if (allowAllOrigins) { + return true; + } + + String[] origins = originsList.trim().split("\\s+"); + for (String origin : origins) { + for (String allowedOrigin : allowedOrigins) { + if (allowedOrigin.contains("*")) { + String regex = allowedOrigin.replace(".", "\\.").replace("*", ".*"); + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(origin); + if (m.matches()) { + return true; + } + } else if (allowedOrigin.equals(origin)) { + return true; + } + } + } + return false; + } + + private boolean areHeadersAllowed(String accessControlRequestHeaders) { + if (accessControlRequestHeaders == null) { + return true; + } + String[] headers = accessControlRequestHeaders.trim().split("\\s*,\\s*"); + return allowedHeaders.containsAll(Arrays.asList(headers)); + } + + private boolean isMethodAllowed(String accessControlRequestMethod) { + if (accessControlRequestMethod == null) { + return true; + } + return allowedMethods.contains(accessControlRequestMethod); + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml b/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml index 9af86e0..df597a0 100644 --- a/hadoop-common-project/hadoop-common/src/main/resources/core-default.xml +++ b/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> http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md b/hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md index 46daaa9..ab2b06c 100644 --- a/hadoop-common-project/hadoop-common/src/site/markdown/HttpAuthentication.md +++ b/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 | http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestHttpCrossOriginFilterInitializer.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestHttpCrossOriginFilterInitializer.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestHttpCrossOriginFilterInitializer.java new file mode 100644 index 0000000..3db6ef1 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/TestHttpCrossOriginFilterInitializer.java @@ -0,0 +1,58 @@ +/** + * 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.Map; + +import org.apache.hadoop.conf.Configuration; + +import org.junit.Assert; +import org.junit.Test; + +public class TestHttpCrossOriginFilterInitializer { + + @Test + public void testGetFilterParameters() { + + // Initialize configuration object + Configuration conf = new Configuration(); + 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 = HttpCrossOriginFilterInitializer + .getFilterParameters(conf, HttpCrossOriginFilterInitializer.PREFIX); + + // retrieve values + String rootvalue = filterParameters.get("rootparam"); + String nestedvalue = filterParameters.get("nested.param"); + String outofscopeparam = filterParameters.get("outofscopeparam"); + + // verify expected values are in place + Assert.assertEquals("Could not find filter parameter", "rootvalue", + rootvalue); + Assert.assertEquals("Could not find filter parameter", "nestedvalue", + nestedvalue); + Assert.assertNull("Found unexpected value in filter parameters", + outofscopeparam); + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestCrossOriginFilter.java ---------------------------------------------------------------------- diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestCrossOriginFilter.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestCrossOriginFilter.java new file mode 100644 index 0000000..5c9b413 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/security/http/TestCrossOriginFilter.java @@ -0,0 +1,330 @@ +/** + * 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.http; + +import java.io.IOException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +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.mock; +import static org.mockito.Mockito.verify; + +public class TestCrossOriginFilter { + + @Test + public void testSameOrigin() throws ServletException, IOException { + + // Setup the configuration settings of the server + Map<String, String> conf = new HashMap<String, String>(); + conf.put(CrossOriginFilter.ALLOWED_ORIGINS, ""); + FilterConfig filterConfig = new FilterConfigTest(conf); + + // Origin is not specified for same origin requests + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn(null); + + // Objects to verify interactions based on request + 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); + + Mockito.verifyZeroInteractions(mockRes); + Mockito.verify(mockChain).doFilter(mockReq, mockRes); + } + + @Test + public void testAllowAllOrigins() throws ServletException, IOException { + + // Setup the configuration settings of the server + Map<String, String> conf = new HashMap<String, String>(); + conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "*"); + FilterConfig filterConfig = new FilterConfigTest(conf); + + // Object under test + CrossOriginFilter filter = new CrossOriginFilter(); + filter.init(filterConfig); + Assert.assertTrue(filter.areOriginsAllowed("example.com")); + } + + @Test + public void testEncodeHeaders() { + String validOrigin = "http://localhost:12345"; + String encodedValidOrigin = CrossOriginFilter.encodeHeader(validOrigin); + Assert.assertEquals("Valid origin encoding should match exactly", + validOrigin, encodedValidOrigin); + + String httpResponseSplitOrigin = validOrigin + " \nSecondHeader: value"; + String encodedResponseSplitOrigin = + CrossOriginFilter.encodeHeader(httpResponseSplitOrigin); + Assert.assertEquals("Http response split origin should be protected against", + validOrigin, encodedResponseSplitOrigin); + + // Test Origin List + String validOriginList = "http://foo.example.com:12345 http://bar.example.com:12345"; + String encodedValidOriginList = CrossOriginFilter + .encodeHeader(validOriginList); + Assert.assertEquals("Valid origin list encoding should match exactly", + validOriginList, encodedValidOriginList); + } + + @Test + public void testPatternMatchingOrigins() throws ServletException, IOException { + + // Setup the configuration settings of the server + Map<String, String> conf = new HashMap<String, String>(); + conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "*.example.com"); + FilterConfig filterConfig = new FilterConfigTest(conf); + + // Object under test + CrossOriginFilter filter = new CrossOriginFilter(); + filter.init(filterConfig); + + // match multiple sub-domains + Assert.assertFalse(filter.areOriginsAllowed("example.com")); + Assert.assertFalse(filter.areOriginsAllowed("foo:example.com")); + Assert.assertTrue(filter.areOriginsAllowed("foo.example.com")); + Assert.assertTrue(filter.areOriginsAllowed("foo.bar.example.com")); + + // First origin is allowed + Assert.assertTrue(filter.areOriginsAllowed("foo.example.com foo.nomatch.com")); + // Second origin is allowed + Assert.assertTrue(filter.areOriginsAllowed("foo.nomatch.com foo.example.com")); + // No origin in list is allowed + Assert.assertFalse(filter.areOriginsAllowed("foo.nomatch1.com foo.nomatch2.com")); + } + + @Test + public void testDisallowedOrigin() throws ServletException, IOException { + + // Setup the configuration settings of the server + Map<String, String> conf = new HashMap<String, String>(); + conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com"); + FilterConfig filterConfig = new FilterConfigTest(conf); + + // Origin is not specified for same origin requests + HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class); + Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.org"); + + // Objects to verify interactions based on request + 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); + + Mockito.verifyZeroInteractions(mockRes); + Mockito.verify(mockChain).doFilter(mockReq, mockRes); + } + + @Test + public void testDisallowedMethod() throws ServletException, IOException { + + // Setup the configuration settings of the server + Map<String, String> conf = new HashMap<String, String>(); + conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com"); + FilterConfig filterConfig = new FilterConfigTest(conf); + + // Origin is not specified for same origin requests + 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 = 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); + + Mockito.verifyZeroInteractions(mockRes); + Mockito.verify(mockChain).doFilter(mockReq, mockRes); + } + + @Test + public void testDisallowedHeader() throws ServletException, IOException { + + // Setup the configuration settings of the server + Map<String, String> conf = new HashMap<String, String>(); + conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com"); + FilterConfig filterConfig = new FilterConfigTest(conf); + + // Origin is not specified for same origin requests + 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"); + Mockito.when( + mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn("Disallowed-Header"); + + // Objects to verify interactions based on request + 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); + + Mockito.verifyZeroInteractions(mockRes); + Mockito.verify(mockChain).doFilter(mockReq, mockRes); + } + + @Test + public void testCrossOriginFilter() throws ServletException, IOException { + + // Setup the configuration settings of the server + Map<String, String> conf = new HashMap<String, String>(); + conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com"); + FilterConfig filterConfig = new FilterConfigTest(conf); + + // Origin is not specified for same origin requests + 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"); + Mockito.when( + mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_HEADERS)) + .thenReturn("X-Requested-With"); + + // Objects to verify interactions based on request + 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); + + Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN, + "example.com"); + Mockito.verify(mockRes).setHeader( + CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS, + Boolean.TRUE.toString()); + Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS, + filter.getAllowedMethodsHeader()); + Mockito.verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS, + filter.getAllowedHeadersHeader()); + Mockito.verify(mockChain).doFilter(mockReq, mockRes); + } + + @Test + public void testCrossOriginFilterAfterRestart() throws ServletException { + + // Setup the configuration settings of the server + Map<String, String> conf = new HashMap<String, String>(); + conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com"); + conf.put(CrossOriginFilter.ALLOWED_HEADERS, "X-Requested-With,Accept"); + conf.put(CrossOriginFilter.ALLOWED_METHODS, "GET,POST"); + FilterConfig filterConfig = new FilterConfigTest(conf); + + // Object under test + CrossOriginFilter filter = new CrossOriginFilter(); + filter.init(filterConfig); + + //verify filter values + Assert.assertTrue("Allowed headers do not match", + filter.getAllowedHeadersHeader() + .compareTo("X-Requested-With,Accept") == 0); + Assert.assertTrue("Allowed methods do not match", + filter.getAllowedMethodsHeader() + .compareTo("GET,POST") == 0); + Assert.assertTrue(filter.areOriginsAllowed("example.com")); + + //destroy filter values and clear conf + filter.destroy(); + conf.clear(); + + // Setup the configuration settings of the server + conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "newexample.com"); + conf.put(CrossOriginFilter.ALLOWED_HEADERS, "Content-Type,Origin"); + conf.put(CrossOriginFilter.ALLOWED_METHODS, "GET,HEAD"); + filterConfig = new FilterConfigTest(conf); + + //initialize filter + filter.init(filterConfig); + + //verify filter values + Assert.assertTrue("Allowed headers do not match", + filter.getAllowedHeadersHeader() + .compareTo("Content-Type,Origin") == 0); + Assert.assertTrue("Allowed methods do not match", + filter.getAllowedMethodsHeader() + .compareTo("GET,HEAD") == 0); + Assert.assertTrue(filter.areOriginsAllowed("newexample.com")); + + //destroy filter values + filter.destroy(); + } + + private static class FilterConfigTest implements FilterConfig { + + final Map<String, String> map; + + FilterConfigTest(Map<String, String> map) { + this.map = map; + } + + @Override + public String getFilterName() { + return "test-filter"; + } + + @Override + public String getInitParameter(String key) { + return map.get(key); + } + + @Override + public Enumeration<String> getInitParameterNames() { + return Collections.enumeration(map.keySet()); + } + + @Override + public ServletContext getServletContext() { + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/CHANGES.txt ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/CHANGES.txt b/hadoop-yarn-project/CHANGES.txt index 9f35307..53bf85a 100644 --- a/hadoop-yarn-project/CHANGES.txt +++ b/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) http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index 913b5df..dafd311 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 @@ -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"; http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml index c6ffe18..6d5b05a 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 @@ -255,6 +255,14 @@ </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> <value>600000</value> @@ -2318,6 +2326,14 @@ </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. If an application is submitted with a priority higher than this value, it will be http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java index dfacaf6..111a85f 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 @@ -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; } } http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java deleted file mode 100644 index 9edaefb..0000000 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilter.java +++ /dev/null @@ -1,259 +0,0 @@ -/** - * 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.server.timeline.webapp; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.lang.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.google.common.annotations.VisibleForTesting; - -public class CrossOriginFilter implements Filter { - - private static final Log LOG = LogFactory.getLog(CrossOriginFilter.class); - - // HTTP CORS Request Headers - static final String ORIGIN = "Origin"; - static final String ACCESS_CONTROL_REQUEST_METHOD = - "Access-Control-Request-Method"; - static final String ACCESS_CONTROL_REQUEST_HEADERS = - "Access-Control-Request-Headers"; - - // HTTP CORS Response Headers - static final String ACCESS_CONTROL_ALLOW_ORIGIN = - "Access-Control-Allow-Origin"; - static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = - "Access-Control-Allow-Credentials"; - static final String ACCESS_CONTROL_ALLOW_METHODS = - "Access-Control-Allow-Methods"; - static final String ACCESS_CONTROL_ALLOW_HEADERS = - "Access-Control-Allow-Headers"; - static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age"; - - // Filter configuration - public static final String ALLOWED_ORIGINS = "allowed-origins"; - public static final String ALLOWED_ORIGINS_DEFAULT = "*"; - public static final String ALLOWED_METHODS = "allowed-methods"; - public static final String ALLOWED_METHODS_DEFAULT = "GET,POST,HEAD"; - public static final String ALLOWED_HEADERS = "allowed-headers"; - public static final String ALLOWED_HEADERS_DEFAULT = - "X-Requested-With,Content-Type,Accept,Origin"; - public static final String MAX_AGE = "max-age"; - public static final String MAX_AGE_DEFAULT = "1800"; - - private List<String> allowedMethods = new ArrayList<String>(); - private List<String> allowedHeaders = new ArrayList<String>(); - private List<String> allowedOrigins = new ArrayList<String>(); - private boolean allowAllOrigins = true; - private String maxAge; - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - initializeAllowedMethods(filterConfig); - initializeAllowedHeaders(filterConfig); - initializeAllowedOrigins(filterConfig); - initializeMaxAge(filterConfig); - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, - FilterChain chain) - throws IOException, ServletException { - doCrossFilter((HttpServletRequest) req, (HttpServletResponse) res); - chain.doFilter(req, res); - } - - @Override - public void destroy() { - allowedMethods.clear(); - allowedHeaders.clear(); - allowedOrigins.clear(); - } - - private void doCrossFilter(HttpServletRequest req, HttpServletResponse res) { - - String originsList = encodeHeader(req.getHeader(ORIGIN)); - if (!isCrossOrigin(originsList)) { - if(LOG.isDebugEnabled()) { - LOG.debug("Header origin is null. Returning"); - } - return; - } - - if (!areOriginsAllowed(originsList)) { - if(LOG.isDebugEnabled()) { - LOG.debug("Header origins '" + originsList + "' not allowed. Returning"); - } - return; - } - - String accessControlRequestMethod = - req.getHeader(ACCESS_CONTROL_REQUEST_METHOD); - if (!isMethodAllowed(accessControlRequestMethod)) { - if(LOG.isDebugEnabled()) { - LOG.debug("Access control method '" + accessControlRequestMethod + - "' not allowed. Returning"); - } - return; - } - - String accessControlRequestHeaders = - req.getHeader(ACCESS_CONTROL_REQUEST_HEADERS); - if (!areHeadersAllowed(accessControlRequestHeaders)) { - if(LOG.isDebugEnabled()) { - LOG.debug("Access control headers '" + accessControlRequestHeaders + - "' not allowed. Returning"); - } - return; - } - - if(LOG.isDebugEnabled()) { - LOG.debug("Completed cross origin filter checks. Populating " + - "HttpServletResponse"); - } - res.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, originsList); - res.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE.toString()); - res.setHeader(ACCESS_CONTROL_ALLOW_METHODS, getAllowedMethodsHeader()); - res.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, getAllowedHeadersHeader()); - res.setHeader(ACCESS_CONTROL_MAX_AGE, maxAge); - } - - @VisibleForTesting - String getAllowedHeadersHeader() { - return StringUtils.join(allowedHeaders, ','); - } - - @VisibleForTesting - String getAllowedMethodsHeader() { - return StringUtils.join(allowedMethods, ','); - } - - private void initializeAllowedMethods(FilterConfig filterConfig) { - String allowedMethodsConfig = - filterConfig.getInitParameter(ALLOWED_METHODS); - if (allowedMethodsConfig == null) { - allowedMethodsConfig = ALLOWED_METHODS_DEFAULT; - } - allowedMethods.addAll( - Arrays.asList(allowedMethodsConfig.trim().split("\\s*,\\s*"))); - LOG.info("Allowed Methods: " + getAllowedMethodsHeader()); - } - - private void initializeAllowedHeaders(FilterConfig filterConfig) { - String allowedHeadersConfig = - filterConfig.getInitParameter(ALLOWED_HEADERS); - if (allowedHeadersConfig == null) { - allowedHeadersConfig = ALLOWED_HEADERS_DEFAULT; - } - allowedHeaders.addAll( - Arrays.asList(allowedHeadersConfig.trim().split("\\s*,\\s*"))); - LOG.info("Allowed Headers: " + getAllowedHeadersHeader()); - } - - private void initializeAllowedOrigins(FilterConfig filterConfig) { - String allowedOriginsConfig = - filterConfig.getInitParameter(ALLOWED_ORIGINS); - if (allowedOriginsConfig == null) { - allowedOriginsConfig = ALLOWED_ORIGINS_DEFAULT; - } - allowedOrigins.addAll( - Arrays.asList(allowedOriginsConfig.trim().split("\\s*,\\s*"))); - allowAllOrigins = allowedOrigins.contains("*"); - LOG.info("Allowed Origins: " + StringUtils.join(allowedOrigins, ',')); - LOG.info("Allow All Origins: " + allowAllOrigins); - } - - private void initializeMaxAge(FilterConfig filterConfig) { - maxAge = filterConfig.getInitParameter(MAX_AGE); - if (maxAge == null) { - maxAge = MAX_AGE_DEFAULT; - } - LOG.info("Max Age: " + maxAge); - } - - static String encodeHeader(final String header) { - if (header == null) { - return null; - } - // Protect against HTTP response splitting vulnerability - // since value is written as part of the response header - // Ensure this header only has one header by removing - // CRs and LFs - return header.split("\n|\r")[0].trim(); - } - - static boolean isCrossOrigin(String originsList) { - return originsList != null; - } - - @VisibleForTesting - boolean areOriginsAllowed(String originsList) { - if (allowAllOrigins) { - return true; - } - - String[] origins = originsList.trim().split("\\s+"); - for (String origin : origins) { - for (String allowedOrigin : allowedOrigins) { - if (allowedOrigin.contains("*")) { - String regex = allowedOrigin.replace(".", "\\.").replace("*", ".*"); - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(origin); - if (m.matches()) { - return true; - } - } else if (allowedOrigin.equals(origin)) { - return true; - } - } - } - return false; - } - - private boolean areHeadersAllowed(String accessControlRequestHeaders) { - if (accessControlRequestHeaders == null) { - return true; - } - String headers[] = accessControlRequestHeaders.trim().split("\\s*,\\s*"); - return allowedHeaders.containsAll(Arrays.asList(headers)); - } - - private boolean isMethodAllowed(String accessControlRequestMethod) { - if (accessControlRequestMethod == null) { - return true; - } - return allowedMethods.contains(accessControlRequestMethod); - } -} http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java index 148cc63..1e6a7ff 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/timeline/webapp/CrossOriginFilterInitializer.java +++ b/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); } -} +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java deleted file mode 100644 index 5e09341..0000000 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilter.java +++ /dev/null @@ -1,324 +0,0 @@ -/** - * 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.server.timeline.webapp; - -import java.io.IOException; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.junit.Assert; -import org.junit.Test; - -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 { - - @Test - public void testSameOrigin() throws ServletException, IOException { - - // Setup the configuration settings of the server - Map<String, String> conf = new HashMap<String, String>(); - conf.put(CrossOriginFilter.ALLOWED_ORIGINS, ""); - 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); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = mock(HttpServletResponse.class); - FilterChain mockChain = 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); - } - - @Test - public void testAllowAllOrigins() throws ServletException, IOException { - - // Setup the configuration settings of the server - Map<String, String> conf = new HashMap<String, String>(); - conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "*"); - FilterConfig filterConfig = new FilterConfigTest(conf); - - // Object under test - CrossOriginFilter filter = new CrossOriginFilter(); - filter.init(filterConfig); - Assert.assertTrue(filter.areOriginsAllowed("example.com")); - } - - @Test - public void testEncodeHeaders() { - String validOrigin = "http://localhost:12345"; - String encodedValidOrigin = CrossOriginFilter.encodeHeader(validOrigin); - Assert.assertEquals("Valid origin encoding should match exactly", - validOrigin, encodedValidOrigin); - - String httpResponseSplitOrigin = validOrigin + " \nSecondHeader: value"; - String encodedResponseSplitOrigin = - CrossOriginFilter.encodeHeader(httpResponseSplitOrigin); - Assert.assertEquals("Http response split origin should be protected against", - validOrigin, encodedResponseSplitOrigin); - - // Test Origin List - String validOriginList = "http://foo.example.com:12345 http://bar.example.com:12345"; - String encodedValidOriginList = CrossOriginFilter.encodeHeader(validOriginList); - Assert.assertEquals("Valid origin list encoding should match exactly", - validOriginList, encodedValidOriginList); - } - - @Test - public void testPatternMatchingOrigins() throws ServletException, IOException { - - // Setup the configuration settings of the server - Map<String, String> conf = new HashMap<String, String>(); - conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "*.example.com"); - FilterConfig filterConfig = new FilterConfigTest(conf); - - // Object under test - CrossOriginFilter filter = new CrossOriginFilter(); - filter.init(filterConfig); - - // match multiple sub-domains - Assert.assertFalse(filter.areOriginsAllowed("example.com")); - Assert.assertFalse(filter.areOriginsAllowed("foo:example.com")); - Assert.assertTrue(filter.areOriginsAllowed("foo.example.com")); - Assert.assertTrue(filter.areOriginsAllowed("foo.bar.example.com")); - - // First origin is allowed - Assert.assertTrue(filter.areOriginsAllowed("foo.example.com foo.nomatch.com")); - // Second origin is allowed - Assert.assertTrue(filter.areOriginsAllowed("foo.nomatch.com foo.example.com")); - // No origin in list is allowed - Assert.assertFalse(filter.areOriginsAllowed("foo.nomatch1.com foo.nomatch2.com")); - } - - @Test - public void testDisallowedOrigin() throws ServletException, IOException { - - // Setup the configuration settings of the server - Map<String, String> conf = new HashMap<String, String>(); - conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com"); - 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"); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = mock(HttpServletResponse.class); - FilterChain mockChain = 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); - } - - @Test - public void testDisallowedMethod() throws ServletException, IOException { - - // Setup the configuration settings of the server - Map<String, String> conf = new HashMap<String, String>(); - conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com"); - 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)) - .thenReturn("DISALLOWED_METHOD"); - - // Objects to verify interactions based on request - HttpServletResponse mockRes = mock(HttpServletResponse.class); - FilterChain mockChain = 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); - } - - @Test - public void testDisallowedHeader() throws ServletException, IOException { - - // Setup the configuration settings of the server - Map<String, String> conf = new HashMap<String, String>(); - conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com"); - 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)) - .thenReturn("GET"); - 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); - - // Object under test - CrossOriginFilter filter = new CrossOriginFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - verifyZeroInteractions(mockRes); - verify(mockChain).doFilter(mockReq, mockRes); - } - - @Test - public void testCrossOriginFilter() throws ServletException, IOException { - - // Setup the configuration settings of the server - Map<String, String> conf = new HashMap<String, String>(); - conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com"); - 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)) - .thenReturn("GET"); - 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); - - // Object under test - CrossOriginFilter filter = new CrossOriginFilter(); - filter.init(filterConfig); - filter.doFilter(mockReq, mockRes, mockChain); - - verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN, - "example.com"); - verify(mockRes).setHeader( - CrossOriginFilter.ACCESS_CONTROL_ALLOW_CREDENTIALS, - Boolean.TRUE.toString()); - verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_METHODS, - filter.getAllowedMethodsHeader()); - verify(mockRes).setHeader(CrossOriginFilter.ACCESS_CONTROL_ALLOW_HEADERS, - filter.getAllowedHeadersHeader()); - verify(mockChain).doFilter(mockReq, mockRes); - } - - @Test - public void testCrossOriginFilterAfterRestart() throws ServletException { - - // Setup the configuration settings of the server - Map<String, String> conf = new HashMap<String, String>(); - conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "example.com"); - conf.put(CrossOriginFilter.ALLOWED_HEADERS, "X-Requested-With,Accept"); - conf.put(CrossOriginFilter.ALLOWED_METHODS, "GET,POST"); - FilterConfig filterConfig = new FilterConfigTest(conf); - - // Object under test - CrossOriginFilter filter = new CrossOriginFilter(); - filter.init(filterConfig); - - //verify filter values - Assert.assertTrue("Allowed headers do not match", - filter.getAllowedHeadersHeader() - .compareTo("X-Requested-With,Accept") == 0); - Assert.assertTrue("Allowed methods do not match", - filter.getAllowedMethodsHeader() - .compareTo("GET,POST") == 0); - Assert.assertTrue(filter.areOriginsAllowed("example.com")); - - //destroy filter values and clear conf - filter.destroy(); - conf.clear(); - - // Setup the configuration settings of the server - conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "newexample.com"); - conf.put(CrossOriginFilter.ALLOWED_HEADERS, "Content-Type,Origin"); - conf.put(CrossOriginFilter.ALLOWED_METHODS, "GET,HEAD"); - filterConfig = new FilterConfigTest(conf); - - //initialize filter - filter.init(filterConfig); - - //verify filter values - Assert.assertTrue("Allowed headers do not match", - filter.getAllowedHeadersHeader() - .compareTo("Content-Type,Origin") == 0); - Assert.assertTrue("Allowed methods do not match", - filter.getAllowedMethodsHeader() - .compareTo("GET,HEAD") == 0); - Assert.assertTrue(filter.areOriginsAllowed("newexample.com")); - - //destroy filter values - filter.destroy(); - } - - private static class FilterConfigTest implements FilterConfig { - - final Map<String, String> map; - - FilterConfigTest(Map<String, String> map) { - this.map = map; - } - - @Override - public String getFilterName() { - return "test-filter"; - } - - @Override - public String getInitParameter(String key) { - return map.get(key); - } - - @Override - public Enumeration<String> getInitParameterNames() { - return Collections.enumeration(map.keySet()); - } - - @Override - public ServletContext getServletContext() { - return null; - } - } -} http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java deleted file mode 100644 index cf26368..0000000 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/timeline/webapp/TestCrossOriginFilterInitializer.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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.server.timeline.webapp; - -import java.util.Map; - -import org.apache.hadoop.conf.Configuration; - -import org.junit.Assert; -import org.junit.Test; - -public class TestCrossOriginFilterInitializer { - - @Test - public void testGetFilterParameters() { - - // Initialize configuration object - Configuration conf = new Configuration(); - conf.set(CrossOriginFilterInitializer.PREFIX + "rootparam", "rootvalue"); - conf.set(CrossOriginFilterInitializer.PREFIX + "nested.param", - "nestedvalue"); - conf.set("outofscopeparam", "outofscopevalue"); - - // call function under test - Map<String, String> filterParameters = - CrossOriginFilterInitializer.getFilterParameters(conf); - - // retrieve values - String rootvalue = filterParameters.get("rootparam"); - String nestedvalue = filterParameters.get("nested.param"); - String outofscopeparam = filterParameters.get("outofscopeparam"); - - // verify expected values are in place - Assert.assertEquals("Could not find filter parameter", "rootvalue", - rootvalue); - Assert.assertEquals("Could not find filter parameter", "nestedvalue", - nestedvalue); - Assert.assertNull("Found unexpected value in filter parameters", - outofscopeparam); - } -} http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml index 18040b4..2958b81 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/pom.xml @@ -82,6 +82,11 @@ <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> <exclusions> http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/webapp/WebServer.java index 0e0c1e2..319c10c 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 @@ -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 = http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java index 88fb1cf..01a1c8f 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 @@ -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) { http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md index acafd28..2613b63 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/NodeManagerRest.md +++ b/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> -``` \ No newline at end of file +``` http://git-wip-us.apache.org/repos/asf/hadoop/blob/f8adeb71/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md ---------------------------------------------------------------------- diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md index 53df195..1d11ed4 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/ResourceManagerRest.md +++ b/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 -----------------------