HADOOP-14908. CrossOriginFilter should trigger regex on more input (Johannes Alberti via aw)

This commit is contained in:
Allen Wittenauer 2017-10-03 10:58:28 -07:00
parent 4111e6c781
commit 4d5dd75b60
No known key found for this signature in database
GPG Key ID: E01B34FBE846DF38
5 changed files with 125 additions and 27 deletions

View File

@ -37,6 +37,7 @@
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import java.util.stream.Collectors;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -66,6 +67,7 @@ public class CrossOriginFilter implements Filter {
// Filter configuration // Filter configuration
public static final String ALLOWED_ORIGINS = "allowed-origins"; public static final String ALLOWED_ORIGINS = "allowed-origins";
public static final String ALLOWED_ORIGINS_DEFAULT = "*"; public static final String ALLOWED_ORIGINS_DEFAULT = "*";
public static final String ALLOWED_ORIGINS_REGEX_PREFIX = "regex:";
public static final String ALLOWED_METHODS = "allowed-methods"; public static final String ALLOWED_METHODS = "allowed-methods";
public static final String ALLOWED_METHODS_DEFAULT = "GET,POST,HEAD"; public static final String ALLOWED_METHODS_DEFAULT = "GET,POST,HEAD";
public static final String ALLOWED_HEADERS = "allowed-headers"; public static final String ALLOWED_HEADERS = "allowed-headers";
@ -194,6 +196,12 @@ private void initializeAllowedOrigins(FilterConfig filterConfig) {
allowAllOrigins = allowedOrigins.contains("*"); allowAllOrigins = allowedOrigins.contains("*");
LOG.info("Allowed Origins: " + StringUtils.join(allowedOrigins, ',')); LOG.info("Allowed Origins: " + StringUtils.join(allowedOrigins, ','));
LOG.info("Allow All Origins: " + allowAllOrigins); LOG.info("Allow All Origins: " + allowAllOrigins);
List<String> discouragedAllowedOrigins = allowedOrigins.stream()
.filter(s -> s.length() > 1 && s.contains("*"))
.collect(Collectors.toList());
for (String discouragedAllowedOrigin : discouragedAllowedOrigins) {
LOG.warn("Allowed Origin pattern '" + discouragedAllowedOrigin + "' is discouraged, use the 'regex:' prefix and use a Java regular expression instead.");
}
} }
private void initializeMaxAge(FilterConfig filterConfig) { private void initializeMaxAge(FilterConfig filterConfig) {
@ -228,13 +236,18 @@ boolean areOriginsAllowed(String originsList) {
String[] origins = originsList.trim().split("\\s+"); String[] origins = originsList.trim().split("\\s+");
for (String origin : origins) { for (String origin : origins) {
for (String allowedOrigin : allowedOrigins) { for (String allowedOrigin : allowedOrigins) {
if (allowedOrigin.contains("*")) { Pattern regexPattern = null;
if (allowedOrigin.startsWith(ALLOWED_ORIGINS_REGEX_PREFIX)) {
String regex = allowedOrigin.substring(ALLOWED_ORIGINS_REGEX_PREFIX.length());
regexPattern = Pattern.compile(regex);
} else if (allowedOrigin.contains("*")) {
String regex = allowedOrigin.replace(".", "\\.").replace("*", ".*"); String regex = allowedOrigin.replace(".", "\\.").replace("*", ".*");
Pattern p = Pattern.compile(regex); regexPattern = Pattern.compile(regex);
Matcher m = p.matcher(origin);
if (m.matches()) {
return true;
} }
if (regexPattern != null
&& regexPattern.matcher(origin).matches()) {
return true;
} else if (allowedOrigin.equals(origin)) { } else if (allowedOrigin.equals(origin)) {
return true; return true;
} }

View File

@ -1861,9 +1861,15 @@
<property> <property>
<name>hadoop.http.cross-origin.allowed-origins</name> <name>hadoop.http.cross-origin.allowed-origins</name>
<value>*</value> <value>*</value>
<description>Comma separated list of origins that are allowed for web <description>Comma separated list of origins that are allowed for web services
services needing cross-origin (CORS) support. Wildcards (*) and patterns needing cross-origin (CORS) support. If a value in the list contains an
allowed</description> asterix (*), a regex pattern, escaping any dots ('.' -> '\.') and replacing
the asterix such that it captures any characters ('*' -> '.*'), is generated.
Values prefixed with 'regex:' are interpreted directly as regular expressions,
e.g. use the expression 'regex:https?:\/\/foo\.bar:([0-9]+)?' to allow any
origin using the 'http' or 'https' protocol in the domain 'foo.bar' on any
port. The use of simple wildcards ('*') is discouraged, and only available for
backward compatibility.</description>
</property> </property>
<property> <property>

View File

@ -60,7 +60,7 @@ Add org.apache.hadoop.security.HttpCrossOriginFilterInitializer to hadoop.http.f
| Property | Default Value | Description | | Property | Default Value | Description |
|:---------------------------------------- |:--------------------------------------------- |:------------------------------------------------------------------------------------- | |:---------------------------------------- |:--------------------------------------------- |:------------------------------------------------------------------------------------- |
| hadoop.http.cross-origin.enabled | `false` | Enables cross origin support for all web-services | | 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-origins | `*` | Comma separated list of origins that are allowed. Values prefixed with `regex:` are interpreted as regular expressions. Values containing wildcards (`*`) are possible as well, here a regular expression is generated, the use is discouraged and support is only available for backward compatibility. |
| hadoop.http.cross-origin.allowed-methods | `GET,POST,HEAD` | Comma separated list of methods that are 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.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 | | hadoop.http.cross-origin.max-age | `1800` | Number of seconds a pre-flighted request can be cached |

View File

@ -127,6 +127,85 @@ public void testPatternMatchingOrigins() throws ServletException, IOException {
Assert.assertFalse(filter.areOriginsAllowed("foo.nomatch1.com foo.nomatch2.com")); Assert.assertFalse(filter.areOriginsAllowed("foo.nomatch1.com foo.nomatch2.com"));
} }
@Test
public void testRegexPatternMatchingOrigins() throws ServletException, IOException {
// Setup the configuration settings of the server
Map<String, String> conf = new HashMap<String, String>();
conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "regex:.*[.]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 testComplexRegexPatternMatchingOrigins() throws ServletException, IOException {
// Setup the configuration settings of the server
Map<String, String> conf = new HashMap<String, String>();
conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "regex:https?:\\/\\/sub1[.]example[.]com(:[0-9]+)?");
FilterConfig filterConfig = new FilterConfigTest(conf);
// Object under test
CrossOriginFilter filter = new CrossOriginFilter();
filter.init(filterConfig);
Assert.assertTrue(filter.areOriginsAllowed("http://sub1.example.com"));
Assert.assertTrue(filter.areOriginsAllowed("https://sub1.example.com"));
Assert.assertTrue(filter.areOriginsAllowed("http://sub1.example.com:1234"));
Assert.assertTrue(filter.areOriginsAllowed("https://sub1.example.com:8080"));
// No origin in list is allowed
Assert.assertFalse(filter.areOriginsAllowed("foo.nomatch1.com foo.nomatch2.com"));
}
@Test
public void testMixedRegexPatternMatchingOrigins() throws ServletException, IOException {
// Setup the configuration settings of the server
Map<String, String> conf = new HashMap<String, String>();
conf.put(CrossOriginFilter.ALLOWED_ORIGINS, "regex:https?:\\/\\/sub1[.]example[.]com(:[0-9]+)?, "
+ "*.example2.com");
FilterConfig filterConfig = new FilterConfigTest(conf);
// Object under test
CrossOriginFilter filter = new CrossOriginFilter();
filter.init(filterConfig);
Assert.assertTrue(filter.areOriginsAllowed("http://sub1.example.com"));
Assert.assertTrue(filter.areOriginsAllowed("https://sub1.example.com"));
Assert.assertTrue(filter.areOriginsAllowed("http://sub1.example.com:1234"));
Assert.assertTrue(filter.areOriginsAllowed("https://sub1.example.com:8080"));
// match multiple sub-domains
Assert.assertFalse(filter.areOriginsAllowed("example2.com"));
Assert.assertFalse(filter.areOriginsAllowed("foo:example2.com"));
Assert.assertTrue(filter.areOriginsAllowed("foo.example2.com"));
Assert.assertTrue(filter.areOriginsAllowed("foo.bar.example2.com"));
// First origin is allowed
Assert.assertTrue(filter.areOriginsAllowed("foo.example2.com foo.nomatch.com"));
// Second origin is allowed
Assert.assertTrue(filter.areOriginsAllowed("foo.nomatch.com foo.example2.com"));
// No origin in list is allowed
Assert.assertFalse(filter.areOriginsAllowed("foo.nomatch1.com foo.nomatch2.com"));
}
@Test @Test
public void testDisallowedOrigin() throws ServletException, IOException { public void testDisallowedOrigin() throws ServletException, IOException {

View File

@ -156,7 +156,7 @@ and cluster operators.
| `yarn.timeline-service.webapp.https.address` | The https address of the Timeline service web application. Defaults to `${yarn.timeline-service.hostname}:8190`. | | `yarn.timeline-service.webapp.https.address` | The https address of the Timeline service web application. Defaults to `${yarn.timeline-service.hostname}:8190`. |
| `yarn.timeline-service.bind-host` | The actual address the server will bind to. If this optional address is set, the RPC and webapp servers will bind to this address and the port specified in `yarn.timeline-service.address` and `yarn.timeline-service.webapp.address`, respectively. This is most useful for making the service listen on all interfaces by setting to `0.0.0.0`. | | `yarn.timeline-service.bind-host` | The actual address the server will bind to. If this optional address is set, the RPC and webapp servers will bind to this address and the port specified in `yarn.timeline-service.address` and `yarn.timeline-service.webapp.address`, respectively. This is most useful for making the service listen on all interfaces by setting to `0.0.0.0`. |
| `yarn.timeline-service.http-cross-origin.enabled` | Enables cross-origin support (CORS) for web services where cross-origin web response headers are needed. For example, javascript making a web services request to the timeline server. Defaults to `false`. | | `yarn.timeline-service.http-cross-origin.enabled` | Enables cross-origin support (CORS) for web services where cross-origin web response headers are needed. For example, javascript making a web services request to the timeline server. Defaults to `false`. |
| `yarn.timeline-service.http-cross-origin.allowed-origins` | Comma separated list of origins that are allowed for web services needing cross-origin (CORS) support. Wildcards `(*)` and patterns allowed. Defaults to `*`. | | `yarn.timeline-service.http-cross-origin.allowed-origins` | Comma separated list of origins that are allowed. Values prefixed with `regex:` are interpreted as regular expressions. Values containing wildcards (`*`) are possible as well, here a regular expression is generated, the use is discouraged and support is only available for backward compatibility. Defaults to `*`. |
| `yarn.timeline-service.http-cross-origin.allowed-methods` | Comma separated list of methods that are allowed for web services needing cross-origin (CORS) support. Defaults to `GET,POST,HEAD`. | | `yarn.timeline-service.http-cross-origin.allowed-methods` | Comma separated list of methods that are allowed for web services needing cross-origin (CORS) support. Defaults to `GET,POST,HEAD`. |
| `yarn.timeline-service.http-cross-origin.allowed-headers` | Comma separated list of headers that are allowed for web services needing cross-origin (CORS) support. Defaults to `X-Requested-With,Content-Type,Accept,Origin`. | | `yarn.timeline-service.http-cross-origin.allowed-headers` | Comma separated list of headers that are allowed for web services needing cross-origin (CORS) support. Defaults to `X-Requested-With,Content-Type,Accept,Origin`. |
| `yarn.timeline-service.http-cross-origin.max-age` | The number of seconds a pre-flighted request can be cached for web services needing cross-origin (CORS) support. Defaults to `1800`. | | `yarn.timeline-service.http-cross-origin.max-age` | The number of seconds a pre-flighted request can be cached for web services needing cross-origin (CORS) support. Defaults to `1800`. |