YARN-4009. CORS support for ResourceManager REST API. ( Varun Vasudev via jeagles)
This commit is contained in:
parent
35a303dfbe
commit
f8adeb712d
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 |
|
||||||
|
|
|
@ -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(HttpCrossOriginFilterInitializer.PREFIX + "rootparam",
|
||||||
conf.set(CrossOriginFilterInitializer.PREFIX + "nested.param",
|
"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 =
|
Map<String, String> filterParameters = HttpCrossOriginFilterInitializer
|
||||||
CrossOriginFilterInitializer.getFilterParameters(conf);
|
.getFilterParameters(conf, HttpCrossOriginFilterInitializer.PREFIX);
|
||||||
|
|
||||||
// retrieve values
|
// retrieve values
|
||||||
String rootvalue = filterParameters.get("rootparam");
|
String rootvalue = filterParameters.get("rootparam");
|
|
@ -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);
|
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
||||||
when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn(null);
|
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);
|
HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
|
||||||
FilterChain mockChain = mock(FilterChain.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);
|
Mockito.verifyZeroInteractions(mockRes);
|
||||||
verify(mockChain).doFilter(mockReq, mockRes);
|
Mockito.verify(mockChain).doFilter(mockReq, mockRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -95,7 +95,8 @@ public class TestCrossOriginFilter {
|
||||||
|
|
||||||
// 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);
|
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
||||||
when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.org");
|
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);
|
HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
|
||||||
FilterChain mockChain = mock(FilterChain.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);
|
Mockito.verifyZeroInteractions(mockRes);
|
||||||
verify(mockChain).doFilter(mockReq, 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);
|
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
||||||
when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
|
Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
|
||||||
when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
|
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);
|
HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
|
||||||
FilterChain mockChain = mock(FilterChain.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);
|
Mockito.verifyZeroInteractions(mockRes);
|
||||||
verify(mockChain).doFilter(mockReq, 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);
|
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
||||||
when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
|
Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
|
||||||
when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
|
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);
|
HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
|
||||||
FilterChain mockChain = mock(FilterChain.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);
|
Mockito.verifyZeroInteractions(mockRes);
|
||||||
verify(mockChain).doFilter(mockReq, 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);
|
HttpServletRequest mockReq = Mockito.mock(HttpServletRequest.class);
|
||||||
when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
|
Mockito.when(mockReq.getHeader(CrossOriginFilter.ORIGIN)).thenReturn("example.com");
|
||||||
when(mockReq.getHeader(CrossOriginFilter.ACCESS_CONTROL_REQUEST_METHOD))
|
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);
|
HttpServletResponse mockRes = Mockito.mock(HttpServletResponse.class);
|
||||||
FilterChain mockChain = mock(FilterChain.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
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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) {
|
if (initializers.contains(HttpCrossOriginFilterInitializer.class.getName())) {
|
||||||
initializers += ",";
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
public class CrossOriginFilterInitializer extends FilterInitializer {
|
import java.util.Map;
|
||||||
|
|
||||||
|
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",
|
// setup the filter
|
||||||
CrossOriginFilter.class.getName(), getFilterParameters(conf));
|
// 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> filterParameters =
|
||||||
Map<String, String> filterParams =
|
getFilterParameters(conf, HttpCrossOriginFilterInitializer.PREFIX);
|
||||||
new HashMap<String, String>();
|
filterParameters.putAll(getFilterParameters(conf, getPrefix()));
|
||||||
for (Map.Entry<String, String> entry : conf.getValByRegex(PREFIX)
|
|
||||||
.entrySet()) {
|
container.addGlobalFilter("Cross Origin Filter",
|
||||||
String name = entry.getKey();
|
CrossOriginFilter.class.getName(), filterParameters);
|
||||||
String value = entry.getValue();
|
|
||||||
name = name.substring(PREFIX.length());
|
|
||||||
filterParams.put(name, value);
|
|
||||||
}
|
|
||||||
return filterParams;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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,6 +60,13 @@ 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 {
|
||||||
|
|
|
@ -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.*;
|
||||||
import org.apache.hadoop.security.Groups;
|
|
||||||
import org.apache.hadoop.security.SecurityUtil;
|
|
||||||
import org.apache.hadoop.security.UserGroupInformation;
|
|
||||||
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) {
|
||||||
|
|
|
@ -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
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue