Skip to content

Commit 5602008

Browse files
author
Simone Scarduzio
committed
Accept X-Forwarded-For configurable as a rule and limited to a hosts list (still, host list accepts a net mask -> be cautious!)
1 parent d50a52e commit 5602008

File tree

7 files changed

+63
-29
lines changed

7 files changed

+63
-29
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Explicitly allow/forbid requests by access control rule parameters:
3434
* ```hosts``` a list of origin IP addresses or subnets
3535
* ```api_keys``` a list of api keys passed in via header ```X-Api-Key```
3636
* ```methods``` a list of HTTP methods
37+
* ```accept_x-forwarded-for_header``` interpret the ```X-Forwarded-For``` header as origin host (useful for AWS ELB and other reverse proxies)
3738
* ```uri_re``` a regular expression to match the request URI (useful to restrict certain indexes)
3839
* ```maxBodyLength``` limit HTTP request body length.
3940

@@ -119,6 +120,12 @@ readonlyrest:
119120
type: allow
120121
hosts: [127.0.0.1, 10.0.1.0/24]
121122

123+
# Allow if the origin host or the host in X-Forwarded-For header is 9.9.9.9 (useful for AWS ELB and other reverse proxies)
124+
- name: full access to internal servers
125+
type: allow
126+
accept_x-forwarded-for_header: true
127+
hosts: [9.9.9.9]
128+
122129
# From these API Keys, accept any method, any URI, any HTTP body
123130
- name: full access to remote authorized clients
124131
type: allow

src/main/java/org/elasticsearch/rest/action/readonlyrest/acl/ACL.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
public class ACL {
1111

12-
private Settings s;
13-
private ESLogger logger;
14-
private TreeMap<Integer, Rule> rules = new TreeMap<>();
12+
private Settings s;
13+
private ESLogger logger;
14+
private TreeMap<Integer, Rule> rules = new TreeMap<>();
1515
private final static String PREFIX = "readonlyrest.access_control_rules";
1616

1717
public ACL(ESLogger logger, Settings s) {
@@ -38,7 +38,7 @@ private void readRules() {
3838
/**
3939
* Check the request against configured ACL rules. This does not work with try/catch because stacktraces are expensive
4040
* for performance.
41-
*
41+
*
4242
* @param req the ACLRequest to be checked by the ACL rules.
4343
* @return null if request pass the rules or the name of the first violated rule
4444
*/
@@ -47,7 +47,7 @@ public String check(ACLRequest req) {
4747
Rule rule = rules.get(exOrder);
4848
// The logic will exit at the first rule that matches the request
4949
boolean match = true;
50-
match &= rule.matchesAddress(req.getAddress());
50+
match &= rule.matchesAddress(req.getAddress(), req.getXForwardedForHeader());
5151
match &= rule.matchesApiKey(req.getApiKey());
5252
match &= rule.matchesAuthKey(req.getAuthKey());
5353
match &= rule.matchesMaxBodyLength(req.getBodyLength());

src/main/java/org/elasticsearch/rest/action/readonlyrest/acl/ACLRequest.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ public class ACLRequest {
3030
private String uri;
3131
private Integer bodyLength;
3232
private Method method;
33+
private String xForwardedForHeader;
3334

3435
@Override
3536
public String toString() {
36-
return method +" "+ uri + " len: "+ bodyLength + " originator address: " + address + " api key: " + apiKey;
37+
return method +" "+ uri + " len: "+ bodyLength + " originator address: " + address +
38+
" api key: " + apiKey + " auth_key: " + authKey + " acceptXForwardedForHeader:" + xForwardedForHeader;
3739
}
3840
public String getAddress() {
3941
return address;
@@ -51,8 +53,10 @@ public Integer getBodyLength() {
5153
return bodyLength;
5254
}
5355

56+
public String getXForwardedForHeader() {return xForwardedForHeader; }
57+
5458
public ACLRequest(RestRequest request, RestChannel channel) {
55-
this(request.uri(), getAddress(request, channel), request.header("X-Api-Key"), request.header("Authorization"), request.content().length(), request.method());
59+
this(request.uri(), getAddress(request, channel), request.header("X-Api-Key"), request.header("Authorization"), request.content().length(), request.method(), getXForwardedForHeader(request));
5660

5761
ESLogger logger = ESLoggerFactory.getLogger(ACLRequest.class.getName());
5862
logger.debug("Headers:\n");
@@ -61,24 +65,28 @@ public ACLRequest(RestRequest request, RestChannel channel) {
6165
}
6266
}
6367

64-
public ACLRequest(String uri, String address, String apiKey, String authKey, Integer bodyLength, Method method){
68+
public ACLRequest(String uri, String address, String apiKey, String authKey, Integer bodyLength, Method method, String xForwardedForHeader){
6569
this.uri = uri;
6670
this.address = address;
6771
this.apiKey = apiKey;
6872
this.authKey = authKey;
6973
this.bodyLength = bodyLength;
7074
this.method = method;
75+
this.xForwardedForHeader = xForwardedForHeader;
7176
}
7277

73-
static String getAddress(RestRequest request, RestChannel channel) {
74-
String remoteHost = null;
75-
78+
static String getXForwardedForHeader(RestRequest request) {
7679
if (!ConfigurationHelper.isNullOrEmpty(request.header("X-Forwarded-For"))) {
7780
String[] parts = request.header("X-Forwarded-For").split(",");
7881
if (!ConfigurationHelper.isNullOrEmpty(parts[0])) {
7982
return parts[0];
8083
}
8184
}
85+
return null;
86+
}
87+
88+
static String getAddress(RestRequest request, RestChannel channel) {
89+
String remoteHost = null;
8290

8391
try {
8492
NettyHttpChannel obj = (NettyHttpChannel) channel;

src/main/java/org/elasticsearch/rest/action/readonlyrest/acl/Rule.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ public static String valuesString(){
3838
String authKey;
3939
private List<Method> methods;
4040
String stringRepresentation;
41+
Boolean acceptXForwardedForHeader;
4142

42-
public Rule(String name, Type type, Pattern uri_re, Integer bodyLenght, List<String> addresses, List<String> apiKeys, String authKey, List<Method> methods, String toString) {
43+
public Rule(String name, Type type, Pattern uri_re, Integer bodyLenght, List<String> addresses, List<String> apiKeys, String authKey, List<Method> methods, String toString, Boolean acceptXForwardedForHeader) {
4344
this.name = name;
4445
this.type = type;
4546
this.uri_re = uri_re;
@@ -49,6 +50,7 @@ public Rule(String name, Type type, Pattern uri_re, Integer bodyLenght, List<Str
4950
this.authKey= authKey;
5051
this.methods = methods;
5152
this.stringRepresentation = toString;
53+
this.acceptXForwardedForHeader = acceptXForwardedForHeader;
5254
}
5355

5456
public static Rule build(Settings s) {
@@ -74,6 +76,8 @@ public static Rule build(Settings s) {
7476
}
7577
}
7678

79+
Boolean acceptXForwardedForHeader = s.getAsBoolean("accept_x-forwarded-for_header", false);
80+
7781
String authKey = s.get("auth_key");
7882
if(authKey != null && authKey.trim().length() > 0) {
7983
authKey = Base64.encodeBytes(authKey.getBytes(Charsets.UTF_8));
@@ -111,8 +115,8 @@ public static Rule build(Settings s) {
111115
Integer maxBodyLength = s.getAsInt("maxBodyLength", null);
112116

113117
if ((!ConfigurationHelper.isNullOrEmpty(name) && type != null) &&
114-
(uri_re != null || maxBodyLength != null || hosts != null || apiKeys != null || authKey != null|| methods != null)) {
115-
return new Rule(name.trim(), type, uri_re, maxBodyLength, hosts, apiKeys, authKey, methods, s.toDelimitedString(' '));
118+
(uri_re != null || maxBodyLength != null || hosts != null || apiKeys != null || authKey != null || methods != null || acceptXForwardedForHeader != null)) {
119+
return new Rule(name.trim(), type, uri_re, maxBodyLength, hosts, apiKeys, authKey, methods, s.toDelimitedString(' '), acceptXForwardedForHeader);
116120
}
117121
throw new RuleConfigurationError("insufficient or invalid configuration for rule: '" + name + "'", null);
118122

@@ -126,11 +130,14 @@ public String toString() {
126130
/*
127131
* All "matches" methods should return true if no explicit condition was configured
128132
*/
129-
public boolean matchesAddress(String address) {
133+
public boolean matchesAddress(String address, String xForwardedForHeader) {
130134
if (addresses == null) {
131135
return true;
132136
}
133-
137+
if(acceptXForwardedForHeader && xForwardedForHeader != null) {
138+
// Give it a try with the header
139+
if(matchesAddress(xForwardedForHeader, null)) return true;
140+
}
134141
for (String allowedAddress : addresses) {
135142
if (allowedAddress.indexOf("/") > 0) {
136143
try {
@@ -188,4 +195,5 @@ public boolean matchesMethods(Method method) {
188195
}
189196
return methods.contains(method);
190197
}
198+
191199
}

src/test/java/org/elasticsearch/rest/action/readonlyrest/acl/test/ACLTest.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,54 +45,60 @@ public void tearDown() throws Exception {
4545

4646
@Test
4747
public final void testExternalGet() {
48-
ACLRequest ar = new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "","", 0, Method.GET);
48+
ACLRequest ar = new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "","", 0, Method.GET,null);
4949
Assert.assertNull(acl.check(ar));
5050
}
5151

5252
@Test
5353
public final void testExternalPost() {
54-
Assert.assertNotNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "","", 0, Method.POST)));
54+
Assert.assertNotNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "","", 0, Method.POST, null)));
5555
}
5656

5757
@Test
58-
public final void testExternalURIRE() {
59-
Assert.assertNotNull(acl.check(new ACLRequest("http://localhost:9200/reservedIdx/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "","", 0, Method.GET)));
58+
public final void testExternalURIRegEx() {
59+
Assert.assertNotNull(acl.check(new ACLRequest("http://localhost:9200/reservedIdx/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "","", 0, Method.GET, null)));
6060
}
6161

6262
@Test
6363
public final void testExternalMatchAddress() {
64-
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "127.0.0.1", "","", 0, Method.GET)));
64+
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "127.0.0.1", "","", 0, Method.GET, null)));
6565
}
6666

6767
@Test
6868
public final void testExternalWithBody() {
69-
Assert.assertNotNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "","", 20, Method.GET)));
69+
Assert.assertNotNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "","", 20, Method.GET, null)));
7070
}
7171

7272
@Test
7373
public final void testExternalMethods() {
74-
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "", "", 0, Method.OPTIONS)));
74+
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "", "", 0, Method.OPTIONS, null)));
7575
}
7676

7777
@Test
7878
public final void testInternalMethods() {
79-
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "127.0.0.1", "","", 0, Method.HEAD)));
79+
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "127.0.0.1", "","", 0, Method.HEAD, null)));
8080
}
8181

8282
@Test
8383
public final void testNetMask() {
84-
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "192.168.1.5", "","", 0, Method.POST)));
84+
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "192.168.1.5", "","", 0, Method.POST, null)));
8585
}
8686

8787
@Test
8888
public final void testApiKey() {
89-
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "192.168.1.5", "1234567890","", 0, Method.POST)));
89+
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "1234567890","", 0, Method.POST, null)));
9090
}
9191

9292
@Test
9393
public final void testHttpBasicAuth() {
9494
String secret64 = Base64.encodeBytes("1234567890".getBytes(Charsets.UTF_8));
95-
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "192.168.1.5","", secret64, 0, Method.POST)));
95+
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1","", secret64, 0, Method.POST, null)));
9696
}
9797

98+
@Test
99+
public final void testXforwardedForHeader() {
100+
Assert.assertNull(acl.check(new ACLRequest("http://es/index1/_search?q=item.name:fishingpole&size=200", "1.1.1.1", "","", 0, Method.POST, "9.9.9.9")));
101+
}
102+
103+
98104
}

src/test/java/org/elasticsearch/rest/action/readonlyrest/acl/test/MarvelPassthroughACL.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public void tearDown() throws Exception {}
4141

4242
@Test
4343
public final void testInternalMethods(){
44-
Assert.assertNull(acl.check( new ACLRequest(".marvel-2015.11.10/_search", "127.0.0.1", "", "", 0, Method.POST)));
44+
Assert.assertNull(acl.check( new ACLRequest(".marvel-2015.11.10/_search", "127.0.0.1", "", "", 0, Method.POST, null)));
4545
}
4646

4747
}

src/test/three_rules.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,13 @@ readonlyrest:
2828
# Default policy is to forbid everything, so let's define a whitelist
2929
access_control_rules:
3030

31-
- name: full access to Basic Auth
31+
- name: read X-Forwarded-For header and interpret it as part of "hosts"
3232
type: allow
33+
accept_x-forwarded-for_header: true
34+
hosts: [9.9.9.9]
35+
36+
- name: full access to Basic Auth
37+
type: allow
3338
auth_key: 1234567890
3439

3540
- name: full access to API Key

0 commit comments

Comments
 (0)