├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── COPYING ├── LICENSE ├── README.md ├── pom.xml ├── reinstall.sh └── src ├── main ├── assemblies │ └── plugin.xml ├── java │ └── com │ │ └── asquera │ │ └── elasticsearch │ │ └── plugins │ │ └── http │ │ ├── HttpBasicServer.java │ │ ├── HttpBasicServerModule.java │ │ ├── HttpBasicServerPlugin.java │ │ └── auth │ │ ├── Client.java │ │ ├── InetAddressWhitelist.java │ │ ├── ProxyChain.java │ │ ├── ProxyChains.java │ │ └── XForwardedFor.java └── resources │ └── es-plugin.properties └── test ├── java └── com │ └── asquera │ └── elasticsearch │ └── plugins │ └── http │ └── auth │ ├── ClientTest.java │ ├── InetAddressWhitelistTest.java │ ├── ProxyChainTest.java │ ├── ProxyChainsTest.java │ ├── XForwardedForTest.java │ └── integration │ ├── DefaultConfigurationIntegrationTest.java │ ├── DisabledWhitelistIntegrationTest.java │ ├── HttpBasicServerPluginIntegrationTest.java │ └── IpAuthenticationIntegrationTest.java └── resources └── log4j.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.jar 3 | *.zip 4 | /build/ 5 | /target/ 6 | *~ 7 | deploy.sh 8 | .gradle 9 | .DS_Store 10 | .classpath 11 | .metadata/ 12 | .project 13 | .settings/ 14 | data/ 15 | /.local-execution-hints.log 16 | /.local-*-execution-hints.log 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: java 3 | install: 4 | - mvn install -Delasticsearch.version=$ES_VERSION -DskipTests=true 5 | script: 6 | - mvn -Delasticsearch.version=$ES_VERSION -Dtests.security.manager=false test 7 | env: 8 | - ES_VERSION=1.5.1 9 | - ES_VERSION=1.5.2 10 | - ES_VERSION=1.6.0 11 | - ES_VERSION=1.7.0 12 | cache: 13 | directories: 14 | - $HOME/.m2 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this 4 | file. This file is structured according to http://keepachangelog.com/ 5 | Use `date "+%Y-%m-%d"` to get the correct date formatting 6 | 7 | - - - 8 | ## [1.5.1][2015-08-30] 9 | ### - Added 10 | - allow HEAD root url authentication #39 11 | - log http method on any request. #42 12 | - doc: 1.6.0, 1.7.0 support #52 13 | ### - Fix 14 | - test: adapt to method signature change after 1.5.1 #55 15 | - test: run custom install and test commands in ci 16 | 17 | ## [1.5.0][2015-07-04] 18 | 19 | ### - Added 20 | - allow disabling ipwhitelist by setting its value to `false` 21 | - updated pom to depend on elasticsearch-parent project 22 | - travis test matrix for different ES versions 23 | 24 | ### Changed 25 | - restored default healthcheck for authenticated users 26 | - unauthenticated healthcheck for `/` returns `"{\"OK\":{}}"` 27 | - thanks @feaster83 28 | 29 | ## [1.4.0] 30 | ## [1.0.3] 31 | 32 | ### - Added 33 | - Changelog 34 | - Disable Authentication for `/`, allowing it to be used for healtchecks. 35 | - thanks @archiloque 36 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | elasticsearch-http-basic is Copyright (c) 2011, Florian and Felix Gilcher 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Florian Gilcher , Felix Gilcher 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **IMPORTANT NOTICE**: This project is currently not supported. We accept pull requests, but we're not doing any feature development/bug fixing 2 | 3 | For alternatives, see [Search Guard](https://docs.search-guard.com/latest/http-basic-authorization). 4 | 5 | 6 | [![Build Status](https://travis-ci.org/Asquera/elasticsearch-http-basic.svg?branch=master)](https://travis-ci.org/Asquera/elasticsearch-http-basic) 7 | 8 | **IMPORTANT NOTICE**: versions 1.0.4 is *insecure and should not be used*. 9 | They have a bug that allows an attacker to get ip authentication by setting 10 | its ip on the 'Host' header. 11 | 12 | # HTTP Basic / Ip auth for ElasticSearch 13 | 14 | This plugin provides an extension of ElasticSearchs HTTP Transport module to enable **HTTP basic authentication** and/or 15 | **Ip based authentication**. 16 | 17 | Requesting `/` does not request authentication to simplify health check configuration. 18 | 19 | There is no way to configure this on a per index basis. 20 | 21 | 22 | ## Version Mapping 23 | 24 | | Http Basic Plugin | elasticsearch | 25 | |-----------------------------|------------------------------| 26 | | v1.5.1(master) | 1.5.1, 1.5.2, 1.6.0, 1.7.0 | 27 | | v1.5.0 | 1.5.0 | 28 | | v1.4.0 | 1.4.0 | 29 | | v1.3.0 | 1.3.0 | 30 | | v1.2.0 | 1.2.0 | 31 | | 1.1.0 | 1.0.0 | 32 | | 1.0.4 | 0.90.7 | 33 | 34 | ## Installation 35 | 36 | Download the desired version from https://github.com/Asquera/elasticsearch-http-basic/releases and copy it to `plugins/http-basic`. 37 | 38 | ## Configuration 39 | 40 | Once the plugin is installed it can be configured in the [elasticsearch modules configuration file](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/setup-configuration.html#settings). See the [elasticserach directory layout information](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/setup-dir-layout.html) for more information about the default paths of an ES installation. 41 | 42 | | Setting key | Default value | Notes | 43 | |-----------------------------------|------------------------------|-------------------------------------------------------------------------| 44 | | `http.basic.enabled` | true | **true** disables the default ES HTTP Transport module | 45 | | `http.basic.user` | "admin" | | 46 | | `http.basic.password` | "admin_pw" | | 47 | | `http.basic.ipwhitelist` | ["localhost", "127.0.0.1"] | If set to `false` no ip will be whitelisted. Uses Host Name Resolution from [java.net.InetAddress](http://docs.oracle.com/javase/7/docs/api/java/net/InetAddress.html) | 48 | | `http.basic.trusted_proxy_chains` | [] | Set an array of trusted proxies ips chains | 49 | | `http.basic.log` | false | enables plugin logging to ES log. Unauthenticated requests are always logged. | 50 | | `http.basic.xforward` | "" | most common is [X-Forwarded-For](http://en.wikipedia.org/wiki/X-Forwarded-For) | 51 | 52 | Be aware that the password is stored in plain text. 53 | 54 | ## Http basic authentication 55 | 56 | see [this article](https://en.wikipedia.org/wiki/Basic_access_authentication) 57 | 58 | ## Ip based authentication 59 | 60 | A client is **Ip authenticated iff** its **request** is **trusted** and its **ip is whitelisted**. 61 | A Request from a client connected *directly* (direct client) is by definition **trusted**. Its ip is the request ip. 62 | A Request form a client connected *via proxies* (remote client) is **trusted iff** there is a tail 63 | subchain of the request chain that matches a tail subchain of the trusted proxy chains. 64 | 65 | **A tail subchain** of a chain "*A,B,C*" is a subchain that matches it by the end. 66 | Example: the 3 tail subchains of the ip chain *A,B,C* are: 67 | 68 | (pseudo code) tailSubchains("A,B,C") --> ["A,B,C", "B,C", "C"] 69 | 70 | The request chain of a remote client is obtained following these steps: 71 | 72 | - read the request's xforward configured header field. 73 | - remove the xforwarded defined client's ip (first listed ip as defined by X-Forwarded-For) from it. 74 | - append the request ip to it. 75 | 76 | The ip chain of a remote client is the ip previous to the longest trusted tail subchain .Is the ip used to check 77 | against the whitelist. 78 | 79 | 80 | ### Request chain checks 81 | 82 | Having the following configuration: 83 | 84 | http.basic.xforward = 'X-Forwarded-For' 85 | http.basic.trusted_proxy_chains = ["B,C", "Z"] 86 | 87 | #### Trusted cases: 88 | 89 | - A remote client with ip *A* connects to [server] via proxies with ips *B* and *C*. *X-Forwarded-For* header has "*A,B*", removing the client's ip "*A*" and adding the request ip *C*, the resulting chain *B,C* matches a trusted tail subchain. Client's ip is A. 90 | 91 | [A] --> B --> C --> [server] 92 | 93 | - A remote client with ip *A* connects to [server] via proxies with ips *R*, *P*, *B* and *C*. *X-Forwarded-For* header has "*A,R,P,B*". 94 | Removing the client's ip "*A*" and adding the request ip *C* , the resulting chain ** matches a trusted tail subchain. **note**: in this case "*P*" is taken as the client's ip, and checked against the white list. Client's ip is P. 95 | 96 | [A] --> R --> P --> B --> C --> [server] 97 | 98 | - A remote client with ip *A* connects to [server] via *C*. *X-Forwarded-For* header has 99 | *A*, removing the client's ip *A* and adding the request ip *C*, the resulting chain *C* matches a trusted tail subchain. Client's ip is A. 100 | 101 | [A] --> C --> [server] 102 | 103 | - client *A* connects directly to [server]. *X-Forwarded-For* header is not set. Client's ip is A. 104 | 105 | [A] --> [server] 106 | 107 | #### Untrusted cases: 108 | 109 | - A remote client with ip *A* connects to [server] via *D*. *X-Forwarded-For* header has 110 | "*A*", removing the client's ip "*A*" and adding the request ip *D*, the resulting chain *D* doesn't match any trusted sub ip chain. 111 | 112 | [A] --> D --> [server] 113 | 114 | - A remote client with ip *X* connects to proxy with ip *C* passing a faked *X-Forwarded-For* header "*R*". *C* will check the IP of the request and add it to the *X-Forwarded-For* field. the server will receive and *X-Forwarded-For* header 115 | as: "*R,X*", remove the client's ip "*R*", add the request ip "*C*" and finally drop the request, as "*X,C*" doesn't match the trusted ip. 116 | 117 | [X] -- R --> C --> [server] 118 | 119 | 120 | ### configuration example 121 | 122 | The following code enables plugin logging, sets user and password, sets chain 123 | "1.1.1.1,2.2.2.2" as trusted , whitelists ip 3.3.3.3 and defines xforward 124 | header as the common 'X-Forwarded-For': 125 | 126 | ``` 127 | http.basic.log: true 128 | http.basic.user: "some_user" 129 | http.basic.password: "some_password" 130 | http.basic.ipwhitelist: ["3.3.3.3"] 131 | http.basic.xforward: "X-Forwarded-For" 132 | http.basic.trusted_proxy_chains: ["1.1.1.1,2.2.2.2"] 133 | ``` 134 | 135 | ## Testing 136 | 137 | **note:** localhost is a whitelisted ip as default. 138 | Considering a default configuration with **my_username** and **my_password** configured. 139 | 140 | Correct credentials 141 | ``` 142 | $ curl -v localhost:9200 # works (returns 200) (by default localhost is configured as whitelisted ip) 143 | $ curl -v --user my_username:my_password no_local_host:9200/foo # works (returns 200) (if credentials are set in configuration) 144 | ``` 145 | 146 | Wrong credentials 147 | ``` 148 | $ curl -v --user my_username:wrong_password no_local_host:9200/ # health check, returns 200 with "{\"OK\":{}}" although Unauthorized 149 | $ curl -v --user my_username:password no_local_host:9200/foo # returns 401 150 | ``` 151 | 152 | ## Development 153 | 154 | ### Testing 155 | Maven is configured to run the unit and integration tests. This plugin makes 156 | use of [ES Integration Tests](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/integration-tests.html) 157 | 158 | We can configure at the cli the version of ES we want to test against: 159 | 160 | `mvn -Delasticsearch.version=1.5.2 -Dtests.security.manager=false test` runs all tests 161 | `mvn -Delasticsearch.version=1.5.2 -Dtests.security.manager=false integration` runs integration tests only 162 | 163 | 164 | ### Packaging 165 | `mvn -Delasticsearch.version=1.5.2 -Dtests.security.manager=false package` packages the plugin in a `jar` file 166 | 167 | ## Issues 168 | 169 | Please file your issue here: https://github.com/Asquera/elasticsearch-http-basic/issues 170 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | com.asquera.elasticsearch 7 | elasticsearch-http-basic 8 | 1.5.1 9 | jar 10 | Elasticsearch Http Basic plugin 11 | Adds HTTP Basic authentication (BA) to your Elasticsearch cluster 12 | https://github.com/Asquera/elasticsearch-http-basic/ 13 | 2011 14 | 15 | 16 | org.elasticsearch 17 | elasticsearch-parent 18 | 1.5.0 19 | 20 | 21 | 22 | 1 23 | INFO 24 | 25 | 26 | 27 | 28 | org.apache.httpcomponents 29 | httpclient 30 | 4.3.5 31 | test 32 | 33 | 34 | org.hamcrest 35 | hamcrest-all 36 | 37 | 38 | com.carrotsearch.randomizedtesting 39 | randomizedtesting-runner 40 | 41 | 42 | org.apache.lucene 43 | lucene-test-framework 44 | 45 | 46 | 47 | org.elasticsearch 48 | elasticsearch 49 | provided 50 | 51 | 52 | 53 | org.elasticsearch 54 | elasticsearch 55 | test-jar 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | src/main/resources 64 | true 65 | 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-compiler-plugin 71 | 72 | 73 | com.carrotsearch.randomizedtesting 74 | junit4-maven-plugin 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-surefire-plugin 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-source-plugin 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-assembly-plugin 87 | 88 | 89 | 90 | 91 | 92 | 93 | oss-snapshots 94 | Sonatype OSS Snapshots 95 | https://oss.sonatype.org/content/repositories/snapshots/ 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /reinstall.sh: -------------------------------------------------------------------------------- 1 | ES=/usr/share/elasticsearch 2 | sudo $ES/bin/plugin remove http-basic 3 | mvn -DskipTests clean package 4 | FILE=`ls ./target/elasticsearch-*zip` 5 | sudo $ES/bin/plugin -url file:$FILE -install http-basic 6 | sudo service elasticsearch restart -------------------------------------------------------------------------------- /src/main/assemblies/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | plugin 4 | 5 | zip 6 | 7 | false 8 | 9 | 10 | / 11 | true 12 | true 13 | 14 | org.elasticsearch:elasticsearch 15 | 16 | 17 | 18 | / 19 | true 20 | true 21 | 22 | com.asquera.elasticsearch:elasticsearch-http-basic 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/com/asquera/elasticsearch/plugins/http/HttpBasicServer.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http; 2 | 3 | import com.asquera.elasticsearch.plugins.http.auth.Client; 4 | import com.asquera.elasticsearch.plugins.http.auth.InetAddressWhitelist; 5 | import com.asquera.elasticsearch.plugins.http.auth.ProxyChains; 6 | import com.asquera.elasticsearch.plugins.http.auth.XForwardedFor; 7 | import org.elasticsearch.common.Base64; 8 | import org.elasticsearch.common.inject.Inject; 9 | import org.elasticsearch.common.logging.Loggers; 10 | import org.elasticsearch.common.settings.Settings; 11 | import org.elasticsearch.env.Environment; 12 | import org.elasticsearch.http.HttpChannel; 13 | import org.elasticsearch.http.HttpRequest; 14 | import org.elasticsearch.http.HttpServer; 15 | import org.elasticsearch.http.HttpServerTransport; 16 | import org.elasticsearch.node.service.NodeService; 17 | import org.elasticsearch.rest.BytesRestResponse; 18 | import org.elasticsearch.rest.RestController; 19 | import org.elasticsearch.rest.RestRequest; 20 | import org.elasticsearch.rest.RestRequest.Method; 21 | 22 | import java.io.IOException; 23 | import java.net.InetAddress; 24 | import java.util.Arrays; 25 | import java.net.InetSocketAddress; 26 | 27 | import static org.elasticsearch.rest.RestStatus.OK; 28 | import static org.elasticsearch.rest.RestStatus.UNAUTHORIZED; 29 | 30 | // # possible http config 31 | // http.basic.user: admin 32 | // http.basic.password: password 33 | // http.basic.ipwhitelist: ["localhost", "somemoreip"] 34 | // http.basic.xforward: "X-Forwarded-For" 35 | // # if you use javascript 36 | // # EITHER $.ajaxSetup({ headers: { 'Authorization': "Basic " + credentials }}); 37 | // # OR use beforeSend in $.ajax({ 38 | // http.cors.allow-headers: "X-Requested-With, Content-Type, Content-Length, Authorization" 39 | // 40 | /** 41 | * @author Florian Gilcher (florian.gilcher@asquera.de) 42 | * @author Peter Karich 43 | */ 44 | public class HttpBasicServer extends HttpServer { 45 | 46 | private final String user; 47 | private final String password; 48 | private final InetAddressWhitelist whitelist; 49 | private final ProxyChains proxyChains; 50 | private final String xForwardHeader; 51 | private final boolean log; 52 | 53 | @Inject public HttpBasicServer(Settings settings, Environment environment, HttpServerTransport transport, 54 | RestController restController, 55 | NodeService nodeService) { 56 | super(settings, environment, transport, restController, nodeService); 57 | 58 | this.user = settings.get("http.basic.user", "admin"); 59 | this.password = settings.get("http.basic.password", "admin_pw"); 60 | final boolean whitelistEnabled = settings.getAsBoolean("http.basic.ipwhitelist", true); 61 | String [] whitelisted = new String[0]; 62 | if (whitelistEnabled) { 63 | whitelisted = settings.getAsArray("http.basic.ipwhitelist", new String[]{"localhost", "127.0.0.1"}); 64 | } 65 | this.whitelist = new InetAddressWhitelist(whitelisted); 66 | this.proxyChains = new ProxyChains( 67 | settings.getAsArray( 68 | "http.basic.trusted_proxy_chains", new String[]{""})); 69 | 70 | // for AWS load balancers it is X-Forwarded-For -> hmmh does not work 71 | this.xForwardHeader = settings.get("http.basic.xforward", ""); 72 | this.log = settings.getAsBoolean("http.basic.log", true); 73 | Loggers.getLogger(getClass()).info("using {}:{} with whitelist: {}, xforward header field: {}, trusted proxy chain: {}", 74 | user, password, whitelist, xForwardHeader, proxyChains); 75 | } 76 | 77 | @Override 78 | public void internalDispatchRequest(final HttpRequest request, final HttpChannel channel) { 79 | if (log) { 80 | logRequest(request); 81 | } 82 | 83 | if (authorized(request)) { 84 | super.internalDispatchRequest(request, channel); 85 | } else if (healthCheck(request)) { // display custom health check page when unauthorized (do not display too much server info) 86 | channel.sendResponse(new BytesRestResponse(OK, "{\"OK\":{}}")); 87 | } else { 88 | logUnAuthorizedRequest(request); 89 | BytesRestResponse response = new BytesRestResponse(UNAUTHORIZED, "Authentication Required"); 90 | response.addHeader("WWW-Authenticate", "Basic realm=\"Restricted\""); 91 | channel.sendResponse(response); 92 | } 93 | } 94 | 95 | // @param an http method 96 | // @returns True iff the method is one of the methods used for health check 97 | private boolean isHealthCheckMethod(final RestRequest.Method method){ 98 | final RestRequest.Method[] healthCheckMethods = { RestRequest.Method.GET, RestRequest.Method.HEAD }; 99 | return Arrays.asList(healthCheckMethods).contains(method); 100 | } 101 | 102 | // @param an http Request 103 | // @returns True iff we check the root path and is a method allowed for healthCheck 104 | private boolean healthCheck(final HttpRequest request) { 105 | return request.path().equals("/") && isHealthCheckMethod(request.method()); 106 | } 107 | 108 | /** 109 | * 110 | * 111 | * @param request 112 | * @return true if the request is authorized 113 | */ 114 | private boolean authorized(final HttpRequest request) { 115 | return allowOptionsForCORS(request) || 116 | authBasic(request) || ipAuthorized(request); 117 | } 118 | 119 | /** 120 | * 121 | * 122 | * @param request 123 | * @return true iff the client is authorized by ip 124 | */ 125 | private boolean ipAuthorized(final HttpRequest request) { 126 | boolean ipAuthorized = false; 127 | String xForwardedFor = request.header(xForwardHeader); 128 | Client client = new Client(getAddress(request), 129 | whitelist, 130 | new XForwardedFor(xForwardedFor), 131 | proxyChains); 132 | ipAuthorized = client.isAuthorized(); 133 | if (ipAuthorized) { 134 | if (log) { 135 | String template = "Ip Authorized client: {}"; 136 | Loggers.getLogger(getClass()).info(template, client); 137 | } 138 | } else { 139 | String template = "Ip Unauthorized client: {}"; 140 | Loggers.getLogger(getClass()).error(template, client); 141 | } 142 | return ipAuthorized; 143 | } 144 | 145 | public String getDecoded(HttpRequest request) { 146 | String authHeader = request.header("Authorization"); 147 | if (authHeader == null) 148 | return ""; 149 | 150 | String[] split = authHeader.split(" ", 2); 151 | if (split.length != 2 || !split[0].equals("Basic")) 152 | return ""; 153 | try { 154 | return new String(Base64.decode(split[1])); 155 | } catch (IOException ex) { 156 | throw new RuntimeException(ex); 157 | } 158 | } 159 | 160 | private boolean authBasic(final HttpRequest request) { 161 | String decoded = ""; 162 | try { 163 | decoded = getDecoded(request); 164 | if (!decoded.isEmpty()) { 165 | String[] userAndPassword = decoded.split(":", 2); 166 | String givenUser = userAndPassword[0]; 167 | String givenPass = userAndPassword[1]; 168 | if (this.user.equals(givenUser) && this.password.equals(givenPass)) 169 | return true; 170 | } 171 | } catch (Exception e) { 172 | logger.warn("Retrieving of user and password failed for " + decoded + " ," + e.getMessage()); 173 | } 174 | return false; 175 | } 176 | 177 | 178 | /** 179 | * 180 | * 181 | * @param request 182 | * @return the IP adress of the direct client 183 | */ 184 | private InetAddress getAddress(HttpRequest request) { 185 | return ((InetSocketAddress) request.getRemoteAddress()).getAddress(); 186 | } 187 | 188 | 189 | /** 190 | * https://en.wikipedia.org/wiki/Cross-origin_resource_sharing the 191 | * specification mandates that browsers “preflight” the request, soliciting 192 | * supported methods from the server with an HTTP OPTIONS request 193 | */ 194 | private boolean allowOptionsForCORS(HttpRequest request) { 195 | // in elasticsearch.yml set 196 | // http.cors.allow-headers: "X-Requested-With, Content-Type, Content-Length, Authorization" 197 | if (request.method() == Method.OPTIONS) { 198 | // Loggers.getLogger(getClass()).error("CORS type {}, address {}, path {}, request {}, content {}", 199 | // request.method(), getAddress(request), request.path(), request.params(), request.content().toUtf8()); 200 | return true; 201 | } 202 | return false; 203 | } 204 | 205 | public void logRequest(final HttpRequest request) { 206 | String addr = getAddress(request).getHostAddress(); 207 | String t = "Authorization:{}, type: {}, Host:{}, Path:{}, {}:{}, Request-IP:{}, " + 208 | "Client-IP:{}, X-Client-IP{}"; 209 | logger.info(t, 210 | request.header("Authorization"), 211 | request.method(), 212 | request.header("Host"), 213 | request.path(), 214 | xForwardHeader, 215 | request.header(xForwardHeader), 216 | addr, 217 | request.header("X-Client-IP"), 218 | request.header("Client-IP")); 219 | } 220 | 221 | public void logUnAuthorizedRequest(final HttpRequest request) { 222 | String addr = getAddress(request).getHostAddress(); 223 | String t = "UNAUTHORIZED type:{}, address:{}, path:{}, request:{}," 224 | + "content:{}, credentials:{}"; 225 | Loggers.getLogger(getClass()).error(t, 226 | request.method(), addr, request.path(), request.params(), 227 | request.content().toUtf8(), getDecoded(request)); 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /src/main/java/com/asquera/elasticsearch/plugins/http/HttpBasicServerModule.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http; 2 | 3 | import org.elasticsearch.http.HttpServerModule; 4 | import org.elasticsearch.common.settings.Settings; 5 | 6 | /** 7 | * @author Florian Gilcher (florian.gilcher@asquera.de) 8 | */ 9 | public class HttpBasicServerModule extends HttpServerModule { 10 | 11 | public HttpBasicServerModule(Settings settings) { 12 | super(settings); 13 | } 14 | 15 | @Override protected void configure() { 16 | super.configure(); 17 | bind(HttpBasicServer.class).asEagerSingleton(); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/main/java/com/asquera/elasticsearch/plugins/http/HttpBasicServerPlugin.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http; 2 | 3 | import org.elasticsearch.common.inject.Inject; 4 | import org.elasticsearch.common.inject.Module; 5 | import org.elasticsearch.common.settings.Settings; 6 | import org.elasticsearch.plugins.AbstractPlugin; 7 | import org.elasticsearch.common.component.LifecycleComponent; 8 | import org.elasticsearch.common.settings.ImmutableSettings; 9 | 10 | import java.util.Collection; 11 | 12 | import static org.elasticsearch.common.collect.Lists.*; 13 | 14 | /** 15 | * @author Florian Gilcher (florian.gilcher@asquera.de) 16 | */ 17 | public class HttpBasicServerPlugin extends AbstractPlugin { 18 | 19 | private boolean enabledByDefault = true; 20 | private final Settings settings; 21 | 22 | @Inject public HttpBasicServerPlugin(Settings settings) { 23 | this.settings = settings; 24 | } 25 | 26 | @Override public String name() { 27 | return "http-basic-server-plugin"; 28 | } 29 | 30 | @Override public String description() { 31 | return "HTTP Basic Server Plugin"; 32 | } 33 | 34 | @Override public Collection> modules() { 35 | Collection> modules = newArrayList(); 36 | if (settings.getAsBoolean("http.basic.enabled", enabledByDefault)) { 37 | modules.add(HttpBasicServerModule.class); 38 | } 39 | return modules; 40 | } 41 | 42 | @Override public Collection> services() { 43 | Collection> services = newArrayList(); 44 | if (settings.getAsBoolean("http.basic.enabled", enabledByDefault)) { 45 | services.add(HttpBasicServer.class); 46 | } 47 | return services; 48 | } 49 | 50 | @Override public Settings additionalSettings() { 51 | if (settings.getAsBoolean("http.basic.enabled", enabledByDefault)) { 52 | return ImmutableSettings.settingsBuilder(). 53 | put("http.enabled", false). 54 | build(); 55 | } else { 56 | return ImmutableSettings.Builder.EMPTY_SETTINGS; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/asquera/elasticsearch/plugins/http/auth/Client.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http.auth; 2 | import java.net.InetAddress; 3 | import java.util.List; 4 | import java.util.ArrayList; 5 | 6 | /** 7 | * This class is responsible for determining the ip of the 8 | * remote client and if the client is authorized based on its ip. 9 | * a client is authorized iff it's ip is whitelisted and the path to the 10 | * server is trusted 11 | *

12 | * a client can be trusted in this cases: 13 | *

    14 | *
  • client is directly connected to the server and its request ip is 15 | * whitelisted 16 | *
  • client is connected to the server via a chain of proxies trusted by 17 | * the server, and its ip is whitelisted 18 | * 19 | * @author Ernesto Miguez (ernesto.miguez@asquera.de) 20 | */ 21 | public class Client { 22 | private final InetAddress requestIp; 23 | private final InetAddressWhitelist whitelist; 24 | private final XForwardedFor xForwardedFor; 25 | private final ProxyChains trustedProxyChains; 26 | /** 27 | * the trusted state of the client. A client is trusted if it is connected 28 | * directly or if it is connected via a trusted ip chain. 29 | */ 30 | private boolean trusted; 31 | /** 32 | * the whitelisted state of the client. 33 | */ 34 | private boolean whitelisted; 35 | /** 36 | * the authorize state of the client. 37 | * true iff the client it is conected via a trusted proxy chain and 38 | * its client ip is whitelisted: 39 | */ 40 | private boolean authorized; 41 | 42 | /** 43 | * @param requestIp 44 | * @param whitelist 45 | * @param xForwardedFor 46 | * @param trustedProxyChains 47 | * @param authorized 48 | * @param trusted 49 | * @param whitelisted 50 | */ 51 | public Client(InetAddress requestIp, InetAddressWhitelist whitelist, 52 | XForwardedFor xForwardedFor, ProxyChains trustedProxyChains) 53 | { 54 | this.requestIp = requestIp; 55 | this.whitelist = whitelist; 56 | this.xForwardedFor = xForwardedFor; 57 | this.trustedProxyChains = trustedProxyChains; 58 | trusted = checkTrusted(); 59 | whitelisted = checkWhitelisted(); 60 | authorized = trusted && whitelisted; 61 | } 62 | 63 | /** 64 | * 65 | * @return the String representation of the client's ip 66 | * from the point of view of the server 67 | *

    68 | * this can be one of: 69 | *

      70 | *
    • client ip from X-Forwarded-For 71 | *
    • a proxy ip in the proxy chain defined in X-Forwarded-For 72 | *
    • the request ip 73 | * 74 | * 75 | */ 76 | public String ip() { 77 | String ip = requestIp.getHostAddress(); 78 | if (xForwardedFor.isSet()) { 79 | ip = remoteClientIp(); 80 | } 81 | return ip; 82 | } 83 | 84 | /** 85 | * 86 | * determines the trust state of the client. 87 | *

      88 | * The client is trusted when: 89 | *

        90 | *
      • it is not connected via proxy 91 | *
      • it is connected via proxies and least one of the proxies subchains is trusted 92 | * 93 | * @return true if the client's proxy chain is trusted or if the client is 94 | * not connected via proxy, false otherwise. 95 | */ 96 | 97 | private boolean checkTrusted() { 98 | boolean trusted = true; 99 | if (xForwardedFor.isSet()) { 100 | trusted = trustedProxyChains.trusts(requestChain()); 101 | } 102 | return trusted; 103 | } 104 | 105 | /** 106 | * If the client conects via proxy its ip can be any of the listed 107 | * in the X-Forwarded-For field; the trusted subchain determines which is the 108 | * client's remote ip, as the ip previous to the first item of the trusted 109 | * subchain. 110 | *

        111 | * If the client doesn't connect via proxy its ip is the request ip 112 | * 113 | * @return true iff clients ip is whitelisted 114 | * 115 | */ 116 | private boolean checkWhitelisted() { 117 | boolean whitelisted = false; 118 | if (xForwardedFor.isSet()) { 119 | whitelisted = whitelist.contains(remoteClientIp()); 120 | } else { 121 | whitelisted = whitelist.contains(requestIp); 122 | } 123 | return whitelisted; 124 | } 125 | 126 | /** 127 | * @return the trusted ip chain or null if none 128 | */ 129 | private ProxyChain trustedChain() { 130 | return trustedProxyChains.trustedSubchain(requestChain()); 131 | } 132 | 133 | /** 134 | * @param request_ip 135 | * @return an request chain in the form of [proxy-1, .., proxy-n, request] 136 | */ 137 | private ProxyChain requestChain() { 138 | List ipsChain = new ArrayList(); 139 | ipsChain.addAll(xForwardedFor.proxies()); 140 | ipsChain.add(requestIp.getHostAddress()); 141 | return new ProxyChain(ipsChain); 142 | } 143 | 144 | /** 145 | * 146 | * In case of X-Forwarded-For set, the ip of the remote client relative 147 | * to the server is defined as follows: 148 | *

        149 | * by default the remote client ip is the first ip of the X-Forwarded-For. 150 | * If there is a sub proxy chain in the X-Forwarded-For that is trusted, the 151 | * client ip is the ip that precedes the starting of the trusted subchain.

        152 | * example:

        153 | * 154 | * a X-Forwarded-For value "1.1.1.1,2.2.2.2,3.3.3.3" with "3.3.3.3" as 155 | * trusted proxy chain will have the "3.3.3.3" subchain trusted. This 156 | * determines "2.2.2.2" as the server's remote client 157 | * 158 | * @return the remote client's ip relative to the server 159 | */ 160 | private String remoteClientIp() { 161 | String clientIp = xForwardedFor.client(); 162 | if (trustedChain() != null) { 163 | List trustedProxies = trustedChain().getProxyChain(); 164 | List proxies = xForwardedFor.proxies(); 165 | proxies.removeAll(trustedProxies); 166 | if (proxies.size() > 0) { 167 | clientIp = proxies.get(proxies.size() - 1); 168 | } 169 | } 170 | return clientIp; 171 | } 172 | 173 | @Override 174 | public String toString() { 175 | String addr = requestIp.getHostAddress(); 176 | String s = "client with request ip " + addr 177 | + (xForwardedFor.isSet() ? ", remoteIp: " + remoteClientIp() : "") 178 | + " is:" 179 | + (authorized ? "Authorized" : "NotAuthorized") 180 | + ", " 181 | + (trusted ? "Trusted" : "NotTrusted") 182 | + " (X-Forwarded-For: " 183 | + xForwardedFor 184 | + "), " 185 | + (whitelisted ? "Whitelisted" : "NotWhitelisted"); 186 | return s; 187 | } 188 | 189 | /** 190 | * @return the trusted 191 | */ 192 | public boolean isTrusted() { 193 | return trusted; 194 | } 195 | 196 | /** 197 | * @return the whitelisted 198 | */ 199 | public boolean isWhitelisted() { 200 | return whitelisted; 201 | } 202 | 203 | /** 204 | * @return the authorized 205 | */ 206 | public boolean isAuthorized() { 207 | return authorized; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/main/java/com/asquera/elasticsearch/plugins/http/auth/InetAddressWhitelist.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http.auth; 2 | import org.elasticsearch.common.logging.Loggers; 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.HashSet; 7 | import java.util.Set; 8 | import java.util.Iterator; 9 | import java.util.Arrays; 10 | import java.net.InetAddress; 11 | import java.net.UnknownHostException; 12 | 13 | /** 14 | * 15 | * Wraps the configured whitelisted ips. 16 | * It uses a set of {@link InetAddress} internally. 17 | *

        18 | * 19 | * 20 | * 21 | * @author Ernesto Miguez (ernesto.miguez@asquera.de) 22 | */ 23 | 24 | public class InetAddressWhitelist { 25 | private Set whitelist; 26 | /** 27 | * 28 | * 29 | * @param whitelist 30 | */ 31 | public InetAddressWhitelist(Set whitelist) { 32 | this.whitelist = whitelist; 33 | } 34 | 35 | /** 36 | * 37 | * 38 | * @param sWhitelist 39 | * 40 | */ 41 | public InetAddressWhitelist(String[] sWhitelist) { 42 | this(toInetAddress(Arrays.asList(sWhitelist))); 43 | } 44 | 45 | /** 46 | * Checks the request ip for inclusion. 47 | * Since that ip comes in a {@link InetAddress} representation, it is checked 48 | * against the whitelist. 49 | * 50 | * @param candidate 51 | * @return if the ip is included in the whitelist 52 | */ 53 | public Boolean contains(InetAddress candidate) { 54 | return this.whitelist.contains(candidate); 55 | } 56 | 57 | /** 58 | * 59 | * Checks the xForwardedFor defined client ip for inclusion. 60 | * Since that ip comes in a String representation, it is checked against 61 | * the String representation of the defined whitelist. 62 | * 63 | * @param candidate 64 | * @return if the ip is included in the String representation of the 65 | * whitelist ips 66 | */ 67 | public Boolean contains(String candidate) { 68 | return getStringWhitelist().contains(candidate); 69 | } 70 | 71 | /** 72 | * @return set of the string representations of the whitelist 73 | */ 74 | Set getStringWhitelist() { 75 | Iterator iterator = this.whitelist.iterator(); 76 | Set set = new HashSet(); 77 | while (iterator.hasNext()) { 78 | InetAddress next = iterator.next(); 79 | set.add(next.getHostAddress()); 80 | } 81 | return set; 82 | } 83 | 84 | /** 85 | * when an configured InetAddress is Unkown or Invalid it is dropped from the 86 | * whitelist 87 | * 88 | * @param ips a list of string ips 89 | * @return a list of {@link InetAddress} objects 90 | * 91 | */ 92 | static Set toInetAddress(List ips) { 93 | List listIps = new ArrayList(); 94 | Iterator iterator = ips.iterator(); 95 | while (iterator.hasNext()) { 96 | String next = iterator.next(); 97 | try { 98 | listIps.add(InetAddress.getByName(next)); 99 | } catch (UnknownHostException e) { 100 | String template = "an ip set in the whitelist settings raised an " + 101 | "UnknownHostException: {}, dropping it"; 102 | Loggers.getLogger(InetAddressWhitelist.class).info(template, e.getMessage()); 103 | } 104 | } 105 | return new HashSet(listIps); 106 | } 107 | 108 | /** 109 | * delegate method 110 | */ 111 | @Override 112 | public String toString() { 113 | return whitelist.toString(); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/asquera/elasticsearch/plugins/http/auth/ProxyChain.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http.auth; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * 7 | * 8 | * This class wraps an ip chain (an ordered list of ips). 9 | * 10 | * @author Ernesto Miguez (ernesto.miguez@asquera.de) 11 | * 12 | */ 13 | public class ProxyChain { 14 | private List proxyChain; 15 | 16 | public ProxyChain() { 17 | this.proxyChain = new ArrayList(); 18 | } 19 | 20 | public ProxyChain(List proxyChain) { 21 | this.proxyChain = proxyChain; 22 | } 23 | 24 | public ProxyChain(String proxyChain) { 25 | this(new ArrayList(Arrays.asList(proxyChain.split(",")))); 26 | } 27 | 28 | 29 | /** 30 | * @return the proxy chain 31 | */ 32 | public List getProxyChain() { 33 | return proxyChain; 34 | } 35 | 36 | /** 37 | * A subchain is every segment of the list matching by the tail, included 38 | * itself.

        example: 39 | * "1.1.1.1,2.2.2.2" is trusted by trusted list "3.3.3.3,4.4.4.4,2.2.2.2" since the 40 | * subchain "2.2.2.2" is included in a subchain of the trusted list. 41 | * 42 | * @return a new {@link ProxyChain} instance having all the subchains of the 43 | * present instance 44 | */ 45 | public ProxyChains subchains() { 46 | List reversedIps = new ArrayList(proxyChain); 47 | Collections.reverse(reversedIps); 48 | ListIterator iterator = reversedIps.listIterator(); 49 | ProxyChains subchains = new ProxyChains((Set)new HashSet()); 50 | ProxyChain subChain = new ProxyChain(new ArrayList()); 51 | while (iterator.hasNext()) { 52 | String next = iterator.next(); 53 | subChain.add(next); 54 | List r = new ArrayList(subChain.getProxyChain()); 55 | Collections.reverse(r); 56 | subchains.add( new ProxyChain(r)); 57 | } 58 | return subchains; 59 | } 60 | 61 | /** 62 | * delegated method 63 | * @param o 64 | * @see List#add(Object o); 65 | */ 66 | public void add (Object o) { 67 | proxyChain.add((String)o); 68 | } 69 | 70 | /** 71 | * delegated method 72 | * @see List#toString(); 73 | */ 74 | @Override 75 | public String toString() { 76 | return proxyChain.toString(); 77 | } 78 | 79 | /** 80 | * delegated method 81 | * @see List#equals(); 82 | */ 83 | @Override 84 | public boolean equals(Object c) { 85 | return proxyChain.equals(((ProxyChain)c).getProxyChain()); 86 | } 87 | 88 | /** 89 | * delegated method 90 | * @see List#hashCode(); 91 | */ 92 | @Override 93 | public int hashCode() { 94 | return proxyChain.hashCode(); 95 | } 96 | 97 | /** 98 | * delegated method 99 | * @see List#size(); 100 | */ 101 | public int size() { 102 | return proxyChain.size(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/asquera/elasticsearch/plugins/http/auth/ProxyChains.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http.auth; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * This class wraps a set of {@link ProxyChain} 7 | * 8 | * @author Ernesto Miguez (ernesto.miguez@asquera.de) 9 | **/ 10 | 11 | public class ProxyChains { 12 | 13 | private Set proxyChains; 14 | 15 | public ProxyChains(Set proxyChains) { 16 | this.proxyChains = proxyChains; 17 | } 18 | 19 | public ProxyChains(String[] proxyChains) { 20 | this(getProxies(proxyChains)); 21 | } 22 | 23 | /** 24 | * 25 | * An ip chain is trusted iff any of it subchains is contained in 26 | * any of the instance subchains 27 | * 28 | * @param candidate the ip list to check 29 | * @return true iff the candidate is included 30 | */ 31 | public Boolean trusts(ProxyChain candidate) { 32 | return trustedSubchain(candidate) != null; 33 | } 34 | 35 | /** 36 | * 37 | * Find the trusted subchain if any.

        note: Any chain is included in its subchains 38 | * 39 | * @param candidate 40 | * @return the trusted subchain or nil if none is trusted. 41 | * If more than one is trusted, the longuest will be returned 42 | */ 43 | public ProxyChain trustedSubchain(ProxyChain candidate) { 44 | Set sub = subchains(); 45 | sub.retainAll(candidate.subchains().getProxyChains()); 46 | ProxyChain trusted = null; 47 | if (!sub.isEmpty()) { 48 | trusted = Collections.max(sub, new InetAddressChainComparator()); 49 | } 50 | return trusted; 51 | } 52 | 53 | /** 54 | * a comparator that uses ip chain size 55 | */ 56 | class InetAddressChainComparator implements Comparator { 57 | @Override 58 | public int compare(ProxyChain a, ProxyChain b) { 59 | return a.size() < b.size() ? -1 : a.size() == b.size() ? 0 : 1; 60 | } 61 | } 62 | 63 | /** 64 | * 65 | * @return the set of subchains of the trusted ip proxy chains 66 | * @see {@link ProxyChain#subchains()} 67 | */ 68 | 69 | public Set subchains() { 70 | Iterator iterator = proxyChains.iterator(); 71 | Set set = new HashSet(); 72 | while (iterator.hasNext()) { 73 | ProxyChain next = iterator.next(); 74 | set.addAll(next.subchains().getProxyChains()); 75 | } 76 | return set; 77 | } 78 | 79 | /** 80 | * 81 | * delegated method 82 | * 83 | */ 84 | public boolean contains(Object c) { 85 | return proxyChains.contains(c); 86 | } 87 | 88 | 89 | /** 90 | * 91 | * delegated method 92 | * 93 | * @param chain 94 | * @return if it is empty 95 | */ 96 | public boolean isEmpty() { 97 | return proxyChains.isEmpty(); 98 | } 99 | 100 | /** 101 | * 102 | * delegated method 103 | * 104 | * @param chain 105 | * @return true if it could be added 106 | */ 107 | public boolean add(ProxyChain chain) { 108 | return proxyChains.add(chain); 109 | } 110 | 111 | 112 | /** 113 | * 114 | * delegated method 115 | */ 116 | @Override 117 | public String toString() { 118 | return proxyChains.toString(); 119 | } 120 | 121 | /** 122 | * 123 | * @param array of proxies represented as comma separated strings 124 | * @return a {@link ProxyChain} object representing the passed proxies 125 | * 126 | */ 127 | 128 | private static Set getProxies(String[] ips) { 129 | Set pChainSet = new HashSet(); 130 | Iterator iterator = (Arrays.asList(ips)).iterator(); 131 | while (iterator.hasNext()) { 132 | String next = iterator.next(); 133 | pChainSet.add(new ProxyChain(next)); 134 | } 135 | return pChainSet; 136 | } 137 | 138 | /** 139 | * @return the proxyChains 140 | */ 141 | public Set getProxyChains() { 142 | return proxyChains; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/asquera/elasticsearch/plugins/http/auth/XForwardedFor.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http.auth; 2 | 3 | import java.util.List; 4 | import java.util.Arrays; 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * 9 | * Class that handles the values obtained from the X-Forwarded-For (XFF) HTTP Header 10 | * field. 11 | *

        12 | * The X-Forwarded-For (XFF) HTTP header field is a de facto standard for 13 | * identifying the originating IP address of a client connecting to a web 14 | * server through an HTTP proxy or load balancer. 15 | *

        16 | * The usefulness of XFF depends on the proxy server truthfully reporting the 17 | * original host's IP address; for this reason, effective use of XFF requires 18 | * knowledge of which proxies are trustworthy, for instance by looking them 19 | * up in a whitelist of servers whose maintainers can be trusted. 20 | * 21 | * @see X-Forwarded-For 22 | * 23 | * 24 | * 25 | * @author Ernesto Miguez (ernesto.miguez@asquera.de) 26 | */ 27 | 28 | public class XForwardedFor { 29 | /** 30 | * 31 | * The X-Forwarded-For Header value as received in the request 32 | * The general format of the field is: 33 | * 34 | * X-Forwarded-For: client, proxy1, proxy2 35 | */ 36 | private final String xForwardedFor; 37 | 38 | /** 39 | * 40 | * @param xForwardedFor 41 | */ 42 | public XForwardedFor(String xForwardedFor) { 43 | this.xForwardedFor = xForwardedFor != null ? xForwardedFor : ""; 44 | } 45 | 46 | /** 47 | * @return the ip of the client as defined by the X-Forwarded-For Header 48 | */ 49 | public String client() { 50 | ArrayList splitted_ips = new ArrayList( 51 | Arrays.asList(xForwardedFor.split(","))); 52 | return splitted_ips.remove(0); 53 | } 54 | 55 | /** 56 | * 57 | * @return true if the X-Forwarded-For header was set 58 | */ 59 | public boolean isSet() { 60 | return ! xForwardedFor.equals(""); 61 | } 62 | 63 | /** 64 | * delegate method 65 | */ 66 | @Override 67 | public String toString() { 68 | String s = "not used"; 69 | if (isSet()) { 70 | s = xForwardedFor; 71 | } 72 | return s; 73 | } 74 | 75 | /** 76 | * @return the ips of the proxies between the client(as defined by the 77 | * X-Forwarded-For Header) and the * server 78 | */ 79 | protected List proxies() { 80 | ArrayList splitted_ips = new ArrayList( 81 | Arrays.asList(xForwardedFor.split(","))); 82 | splitted_ips.remove(0); 83 | return splitted_ips; 84 | } 85 | 86 | /** 87 | * @return the xForwardedFor 88 | */ 89 | public String getxForwardedFor() { 90 | return xForwardedFor; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/resources/es-plugin.properties: -------------------------------------------------------------------------------- 1 | plugin=com.asquera.elasticsearch.plugins.http.HttpBasicServerPlugin -------------------------------------------------------------------------------- /src/test/java/com/asquera/elasticsearch/plugins/http/auth/ClientTest.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http.auth; 2 | 3 | import static org.junit.Assert.*; 4 | import static org.hamcrest.CoreMatchers.is; 5 | 6 | import org.junit.Test; 7 | 8 | import java.net.UnknownHostException; 9 | import java.net.InetAddress; 10 | 11 | 12 | public class ClientTest{ 13 | 14 | private final String whitelistedIp = "8.8.8.8"; 15 | private final String notWhitelistedIp = "42.42.42.42"; 16 | private final String untrustedRequestIp = "50.50.50.50"; 17 | private final String trustedRequestIp = "6.6.6.6"; 18 | private String[] trustedIps = {"7.7.7.7,"+ trustedRequestIp}; 19 | private String[] whitelist = { whitelistedIp }; 20 | private String xForwardedFor = "9.9.9.9,8.8.8.8,7.7.7.7"; 21 | 22 | @Test 23 | public void authorizedWhitelistedRequestNilXForwardedFor() throws UnknownHostException { 24 | Client c = new Client( 25 | InetAddress.getByName(whitelistedIp), 26 | new InetAddressWhitelist(whitelist), 27 | new XForwardedFor(null), 28 | new ProxyChains(trustedIps)); 29 | assertThat(c.ip(), is(whitelistedIp)); 30 | assertTrue(c.isTrusted()); 31 | assertTrue(c.isWhitelisted()); 32 | assertTrue(c.isAuthorized()); 33 | } 34 | 35 | public void authorizedWhitelistedRequestUnsetProxies() throws UnknownHostException { 36 | Client c = new Client( 37 | InetAddress.getByName(whitelistedIp), 38 | new InetAddressWhitelist(whitelist), 39 | new XForwardedFor(""), 40 | new ProxyChains(trustedIps)); 41 | assertThat(c.ip(), is(whitelistedIp)); 42 | assertTrue(c.isTrusted()); 43 | assertTrue(c.isWhitelisted()); 44 | assertTrue(c.isAuthorized()); 45 | } 46 | 47 | @Test 48 | public void unauthorizedWhitelistedRequestUnsetProxies() throws UnknownHostException { 49 | Client c = new Client( 50 | InetAddress.getByName(notWhitelistedIp), 51 | new InetAddressWhitelist(whitelist), 52 | new XForwardedFor(""), 53 | new ProxyChains(trustedIps)); 54 | assertThat(c.ip(), is(notWhitelistedIp)); 55 | assertTrue(c.isTrusted()); 56 | assertFalse(c.isWhitelisted()); 57 | assertFalse(c.isAuthorized()); 58 | } 59 | 60 | @Test 61 | public void ipOfUntrustedRequestViaProxiesIsFirstOfXForwardedFor() throws UnknownHostException { 62 | Client c = new Client( 63 | InetAddress.getByName(untrustedRequestIp), 64 | new InetAddressWhitelist(whitelist), 65 | new XForwardedFor(xForwardedFor), 66 | new ProxyChains(trustedIps)); 67 | assertThat(c.ip(), is("9.9.9.9")); 68 | assertFalse(c.isTrusted()); 69 | assertFalse(c.isWhitelisted()); 70 | assertFalse(c.isAuthorized()); 71 | } 72 | 73 | @Test 74 | public void ipOfTrustedRequestViaProxiesIsInXForwardedFor() throws UnknownHostException { 75 | Client c = new Client( 76 | InetAddress.getByName(trustedRequestIp), 77 | new InetAddressWhitelist(whitelist), 78 | new XForwardedFor(xForwardedFor), 79 | new ProxyChains(trustedIps)); 80 | assertThat(c.ip(), is("8.8.8.8")); 81 | assertTrue(c.isTrusted()); 82 | assertTrue(c.isWhitelisted()); 83 | assertTrue(c.isAuthorized()); 84 | } 85 | 86 | @Test 87 | public void ipOfNotWhitelistedIpViaTrustedProxiesIsFirstOfXForwardedFor() throws UnknownHostException { 88 | String[] whitelist = {"10.10.10.10"}; 89 | Client c = new Client( 90 | InetAddress.getByName(trustedRequestIp), 91 | new InetAddressWhitelist(whitelist), 92 | new XForwardedFor(xForwardedFor), 93 | new ProxyChains(trustedIps)); 94 | assertThat(c.ip(), is("8.8.8.8")); 95 | assertTrue(c.isTrusted()); 96 | assertFalse(c.isWhitelisted()); 97 | assertFalse(c.isAuthorized()); 98 | } 99 | 100 | @Test 101 | public void noXForwardedSetRequestIpWhitelisted() throws UnknownHostException { 102 | Client c = new Client( 103 | InetAddress.getByName(whitelistedIp), 104 | new InetAddressWhitelist(whitelist), 105 | new XForwardedFor(""), 106 | new ProxyChains(trustedIps)); 107 | assertThat(c.ip(), is(whitelistedIp)); 108 | assertTrue(c.isTrusted()); 109 | assertTrue(c.isWhitelisted()); 110 | assertTrue(c.isAuthorized()); 111 | } 112 | 113 | @Test 114 | public void noXForwardedSetRequestIpNotWhitelisted() throws UnknownHostException { 115 | Client c = new Client( 116 | InetAddress.getByName(notWhitelistedIp), 117 | new InetAddressWhitelist(whitelist), 118 | new XForwardedFor(""), 119 | new ProxyChains(trustedIps)); 120 | assertThat(c.ip(), is(notWhitelistedIp)); 121 | assertTrue(c.isTrusted()); 122 | assertFalse(c.isWhitelisted()); 123 | assertFalse(c.isAuthorized()); 124 | } 125 | 126 | @Test 127 | public void clientIsTrustedBySeveralProxyChains() throws UnknownHostException { 128 | 129 | String[] trustedIps = {"1.1.1.1,2.2.2.2,3.3.3.3","4.4.4.4,2.2.2.2,3.3.3.3"}; 130 | String xForwardedFor = whitelistedIp + ",2.2.2.2"; 131 | Client c = new Client( 132 | InetAddress.getByName("3.3.3.3"), 133 | new InetAddressWhitelist(whitelist), 134 | new XForwardedFor(xForwardedFor), 135 | new ProxyChains(trustedIps)); 136 | assertThat(c.ip(), is(whitelistedIp)); 137 | assertTrue(c.isTrusted()); 138 | assertTrue(c.isWhitelisted()); 139 | assertTrue(c.isAuthorized()); 140 | } 141 | 142 | @Test 143 | public void clientIsUntrustedAndInWhitelist() throws UnknownHostException { 144 | String xForwardedFor = whitelistedIp + ",2.2.2.2"; 145 | Client c = new Client( 146 | InetAddress.getByName(untrustedRequestIp), 147 | new InetAddressWhitelist(whitelist), 148 | new XForwardedFor(xForwardedFor), 149 | new ProxyChains(trustedIps)); 150 | assertThat(c.ip(), is(whitelistedIp)); 151 | assertFalse(c.isTrusted()); 152 | assertTrue(c.isWhitelisted()); 153 | assertFalse(c.isAuthorized()); 154 | } 155 | 156 | @Test 157 | public void clientIsTrustedAndInWhitelistViaOneProxy() throws UnknownHostException { 158 | String xForwardedFor = whitelistedIp; 159 | Client c = new Client( 160 | InetAddress.getByName(trustedRequestIp), 161 | new InetAddressWhitelist(whitelist), 162 | new XForwardedFor(xForwardedFor), 163 | new ProxyChains(trustedIps)); 164 | assertThat(c.ip(), is(whitelistedIp)); 165 | assertTrue(c.isTrusted()); 166 | assertTrue(c.isWhitelisted()); 167 | assertTrue(c.isAuthorized()); 168 | } 169 | 170 | @Test 171 | public void clientIsUntrustedAndInWhitelistViaOneProxy() throws UnknownHostException { 172 | String xForwardedFor = whitelistedIp; 173 | Client c = new Client( 174 | InetAddress.getByName(untrustedRequestIp), 175 | new InetAddressWhitelist(whitelist), 176 | new XForwardedFor(xForwardedFor), 177 | new ProxyChains(trustedIps)); 178 | assertThat(c.ip(), is(whitelistedIp)); 179 | assertFalse(c.isTrusted()); 180 | assertTrue(c.isWhitelisted()); 181 | assertFalse(c.isAuthorized()); 182 | } 183 | 184 | @Test 185 | public void lastXForwardTrustedProxyIsNotwhitelistedClient() throws UnknownHostException { 186 | String[] trustedIps = {"3.3.3.3,2.2.2.2,1.1.1.1," + trustedRequestIp}; 187 | String xForwardedFor = notWhitelistedIp + "," + whitelistedIp + ",3.3.3.3,2.2.2.2"; 188 | Client c = new Client( 189 | InetAddress.getByName(trustedRequestIp), 190 | new InetAddressWhitelist(whitelist), 191 | new XForwardedFor(xForwardedFor), 192 | new ProxyChains(trustedIps)); 193 | assertThat(c.ip(), is("2.2.2.2")); 194 | assertTrue(c.isTrusted()); 195 | assertFalse(c.isWhitelisted()); 196 | assertFalse(c.isAuthorized()); 197 | } 198 | 199 | @Test 200 | public void lastXForwardTrustedProxyIsWhitelistedClient() throws UnknownHostException { 201 | String[] trustedIps = {"3.3.3.3,2.2.2.2," + trustedRequestIp}; 202 | String xForwardedFor = notWhitelistedIp + "," + whitelistedIp + ",2.2.2.2"; 203 | Client c = new Client( 204 | InetAddress.getByName(trustedRequestIp), 205 | new InetAddressWhitelist(whitelist), 206 | new XForwardedFor(xForwardedFor), 207 | new ProxyChains(trustedIps)); 208 | assertThat(c.ip(), is(whitelistedIp)); 209 | assertTrue(c.isTrusted()); 210 | assertTrue(c.isWhitelisted()); 211 | assertTrue(c.isAuthorized()); 212 | } 213 | 214 | 215 | @Test 216 | public void longestTrustedChainDefinesClientNontInWhitelist() throws UnknownHostException { 217 | String xForwardedFor = notWhitelistedIp + "," + whitelistedIp + ",2.2.2.2"; 218 | String[] trustedIps = { xForwardedFor + "," + trustedRequestIp}; 219 | Client c = new Client( 220 | InetAddress.getByName(trustedRequestIp), 221 | new InetAddressWhitelist(whitelist), 222 | new XForwardedFor(xForwardedFor), 223 | new ProxyChains(trustedIps)); 224 | assertThat(c.ip(), is(notWhitelistedIp)); 225 | assertTrue(c.isTrusted()); 226 | assertFalse(c.isWhitelisted()); 227 | assertFalse(c.isAuthorized()); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/test/java/com/asquera/elasticsearch/plugins/http/auth/InetAddressWhitelistTest.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http.auth; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | import java.net.InetAddress; 8 | import java.net.UnknownHostException; 9 | 10 | public class InetAddressWhitelistTest { 11 | 12 | static final String localhost = "localhost"; 13 | static final String containedIp = "1.1.1.1"; 14 | static String notContainedIp = "2.2.2.2"; 15 | private InetAddressWhitelist whitelist(String ip) { 16 | String[] w = { ip }; 17 | return new InetAddressWhitelist(w); 18 | } 19 | 20 | @Test 21 | public void testInnetLocalhost() throws UnknownHostException { 22 | assertTrue(whitelist(localhost).contains(InetAddress.getByName(localhost))); 23 | } 24 | @Test 25 | public void testInnetNullDefaultsToLocalhost() throws UnknownHostException { 26 | assertTrue(whitelist(null).contains(InetAddress.getByName(localhost))); 27 | } 28 | @Test 29 | public void testStringLocalhostNotMatched() throws UnknownHostException { 30 | // the ip that "localhost" resolves to its matched ip and not the string 31 | // "localhost" itself 32 | assertFalse(whitelist(localhost).contains(localhost)); 33 | } 34 | 35 | @Test 36 | public void testIpContained() throws UnknownHostException { 37 | assertTrue(whitelist(containedIp).contains(containedIp)); 38 | } 39 | 40 | @Test 41 | public void testEmptyWhitelist() throws UnknownHostException { 42 | assertFalse(whitelist("").contains(notContainedIp)); 43 | } 44 | 45 | @Test 46 | public void testNotContained() throws UnknownHostException { 47 | assertFalse(whitelist(containedIp).contains(notContainedIp)); 48 | } 49 | 50 | @Test 51 | public void invalidIpIsDropped() throws UnknownHostException { 52 | String invalidIp = "555.555.555.555"; 53 | assertFalse(whitelist(invalidIp).contains(invalidIp)); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/asquera/elasticsearch/plugins/http/auth/ProxyChainTest.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http.auth; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | import java.util.ArrayList; 8 | 9 | public class ProxyChainTest { 10 | 11 | 12 | @Test 13 | public void emptyProxyChainGeneratesEmptySubchain() { 14 | assertTrue(new ProxyChain().subchains().isEmpty()); 15 | } 16 | 17 | @Test 18 | public void notEmptyIsIncludedInSubchain() { 19 | ArrayList c = new ArrayList(); 20 | c.add( "123.134.123.213" ); 21 | ProxyChain r = new ProxyChain(c); 22 | assertTrue(r.subchains().contains(r)); 23 | } 24 | 25 | @Test 26 | public void lastTwoIncludedInSubchain() { 27 | ArrayList c = new ArrayList(); 28 | c.add( "1.1.1.1" ); 29 | c.add( "3.3.3.3" ); 30 | ProxyChain r = new ProxyChain(c); 31 | ProxyChain s = new ProxyChain(); 32 | s.add( "1.1.1.1" ); 33 | s.add( "3.3.3.3" ); 34 | assertTrue(r.subchains().contains(s)); 35 | } 36 | 37 | @Test 38 | public void firstTwoNotIncludedInSubchain() { 39 | ArrayList c = new ArrayList(); 40 | c.add( "123.134.123.213" ); 41 | c.add( "1.1.1.1" ); 42 | c.add( "3.3.3.3" ); 43 | ProxyChain r = new ProxyChain(c); 44 | ProxyChain s = new ProxyChain(); 45 | s.add(c.get(0)); 46 | s.add(c.get(1)); 47 | assertFalse(r.subchains().contains(s)); 48 | } 49 | 50 | @Test 51 | public void middleNotIncludedInSubchain() { 52 | ArrayList c = new ArrayList(); 53 | c.add( "123.134.123.213" ); 54 | c.add( "1.1.1.1" ); 55 | c.add( "3.3.3.3" ); 56 | ProxyChain r = new ProxyChain(c); 57 | ProxyChain s = new ProxyChain(); 58 | s.add(c.get(1)); 59 | assertFalse(r.subchains().contains(s)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/com/asquera/elasticsearch/plugins/http/auth/ProxyChainsTest.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http.auth; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | import org.junit.Before; 7 | 8 | public class ProxyChainsTest { 9 | 10 | private ProxyChains trustedChains; 11 | private ProxyChain trustedCandidateChain; 12 | private ProxyChain unTrustedCandidateChain; 13 | private final String untrustedChain1 = "50.50.50.50"; 14 | private final String trustedChain1 = "7.7.7.7"; 15 | private final String trustedChain2 = "5.5.5.5,6.6.6.6"; 16 | private final String trustedChain3 = "8.8.8.8,9.9.9.9,10.10.10.10"; 17 | private final String trustedChain4 = "2.2.2.2" + "," + trustedChain2; 18 | private final String[] t = { trustedChain1, trustedChain2, trustedChain3, trustedChain4 }; 19 | 20 | @Before public void initialize() { 21 | trustedCandidateChain = new ProxyChain(trustedChain1); 22 | unTrustedCandidateChain = new ProxyChain(untrustedChain1); 23 | trustedChains = new ProxyChains(t); 24 | } 25 | 26 | @Test(expected=NullPointerException.class) 27 | public void NullPointerExceptionInNullTrustedIps() { 28 | String[] t = null; 29 | trustedChains = new ProxyChains(t); 30 | assertFalse(trustedChains.trusts(trustedCandidateChain)); 31 | } 32 | 33 | @Test 34 | public void unTrustsEmptyCandidate() { 35 | assertFalse(trustedChains.trusts(new ProxyChain(""))); 36 | } 37 | 38 | @Test 39 | public void unTrustsAnyCandidateWithEmptyTrustedChain() { 40 | String[] t = {""}; 41 | trustedChains = new ProxyChains(t); 42 | assertFalse(trustedChains.trusts(trustedCandidateChain)); 43 | } 44 | 45 | @Test 46 | public void trustsCandidatesInProxyChain() { 47 | assertTrue(trustedChains.trusts(trustedCandidateChain)); 48 | } 49 | 50 | @Test 51 | public void unTrustedCandidateNotInProxyChain() { 52 | assertFalse(trustedChains.trusts(unTrustedCandidateChain)); 53 | } 54 | 55 | @Test 56 | public void trustCandidateContainedInTwoTrustedChains() { 57 | trustedCandidateChain = new ProxyChain(trustedChain2); 58 | assertTrue(trustedChains.trusts(trustedCandidateChain)); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/asquera/elasticsearch/plugins/http/auth/XForwardedForTest.java: -------------------------------------------------------------------------------- 1 | package com.asquera.elasticsearch.plugins.http.auth; 2 | 3 | import static org.junit.Assert.*; 4 | import static org.hamcrest.CoreMatchers.is; 5 | import org.junit.Test; 6 | 7 | public class XForwardedForTest { 8 | 9 | @Test 10 | public void returnsClientWithClientAndProxy() { 11 | // It seems the getName lookup for empty string is localhost 12 | String xForwardedFor = "123.123.123.123,122.122.12.1" ; 13 | assertThat(new XForwardedFor(xForwardedFor).client(), 14 | is(xForwardedFor.split(",")[0])); 15 | } 16 | 17 | @Test 18 | public void returnsClientWithClient() { 19 | // It seems the getName lookup for empty string is localhost 20 | String xForwardedFor = "123.123.123.123" ; 21 | assertThat(new XForwardedFor(xForwardedFor).client(), 22 | is(xForwardedFor.split(",")[0])); 23 | } 24 | 25 | @Test 26 | public void returnsClientWithNil() { 27 | assertThat(new XForwardedFor(null).client(), is("")); 28 | } 29 | 30 | @Test 31 | public void unsetHeaderReturnsEmptyClient() { 32 | // It seems the getName lookup for empty string is localhost 33 | String xForwardedFor = "" ; 34 | assertFalse(new XForwardedFor(xForwardedFor).isSet()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/asquera/elasticsearch/plugins/http/auth/integration/DefaultConfigurationIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package com.asquera.elasticsearch.plugins.http.auth.integration; 20 | 21 | import org.elasticsearch.common.settings.Settings; 22 | import org.elasticsearch.rest.RestStatus; 23 | import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; 24 | import org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; 25 | import org.elasticsearch.test.rest.client.http.HttpResponse; 26 | import org.junit.Test; 27 | 28 | import static org.hamcrest.Matchers.equalTo; 29 | 30 | /** 31 | * Test a rest action that sets special response headers 32 | */ 33 | @ClusterScope(scope = Scope.SUITE, numDataNodes = 1) 34 | public class DefaultConfigurationIntegrationTest extends HttpBasicServerPluginIntegrationTest { 35 | 36 | @Override 37 | protected Settings nodeSettings(int nodeOrdinal) { 38 | return builderWithPlugin().build(); 39 | } 40 | 41 | @Test 42 | public void testHealthCheck() throws Exception { 43 | HttpResponse response = httpClient().path("/").execute(); 44 | assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); 45 | } 46 | 47 | @Test 48 | public void localhostClientIsIpAuthenticated() throws Exception { 49 | HttpResponse response = httpClient().path("/_status").execute(); 50 | assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/asquera/elasticsearch/plugins/http/auth/integration/DisabledWhitelistIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package com.asquera.elasticsearch.plugins.http.auth.integration; 20 | 21 | import org.elasticsearch.common.settings.Settings; 22 | import org.elasticsearch.common.Base64; 23 | import org.elasticsearch.rest.RestStatus; 24 | import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; 25 | import org.elasticsearch.test.rest.client.http.HttpRequestBuilder; 26 | import org.elasticsearch.test.rest.client.http.HttpResponse; 27 | import org.apache.http.client.methods.HttpHead; 28 | import org.junit.Test; 29 | 30 | import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; 31 | import static org.hamcrest.Matchers.equalTo; 32 | 33 | /** 34 | * Test a rest action that sets special response headers 35 | */ 36 | @ClusterScope(transportClientRatio = 0.0, scope = Scope.SUITE, numDataNodes = 1) 37 | public class DisabledWhitelistIntegrationTest extends HttpBasicServerPluginIntegrationTest { 38 | 39 | @Override 40 | protected Settings nodeSettings(int nodeOrdinal) { 41 | return builderWithPlugin(). 42 | put("http.basic.ipwhitelist", false) 43 | .build(); 44 | } 45 | 46 | // TODO put the set credentials ussing Setter 47 | @Test 48 | public void clientIpAuthenticationFails() throws Exception { 49 | HttpResponse response = httpClient().path("/_status").execute(); 50 | assertThat(response.getStatusCode(), equalTo(RestStatus.UNAUTHORIZED.getStatus())); 51 | } 52 | 53 | @Test 54 | // GET by default 55 | public void testHealthCheck() throws Exception { 56 | HttpResponse response = httpClient().path("/").execute(); 57 | assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); 58 | } 59 | 60 | @Test 61 | public void testHealthCheckHeadMethod() throws Exception { 62 | HttpResponse response = httpClient().method(HttpHead.METHOD_NAME).path("/").execute(); 63 | assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); 64 | } 65 | 66 | @Test 67 | public void clientGoodCredentialsBasicAuthenticationSuceeds() throws Exception { 68 | HttpResponse response = requestWithCredentials("admin:admin_pw") 69 | .addHeader("X-Forwarded-For", "1.1.1.1" ).execute(); 70 | assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); 71 | } 72 | 73 | @Test 74 | public void clientBadCredentialsBasicAuthenticationFails() throws Exception { 75 | HttpResponse response = requestWithCredentials("admin:wrong").execute(); 76 | assertThat(response.getStatusCode() 77 | , equalTo(RestStatus.UNAUTHORIZED.getStatus())); 78 | } 79 | 80 | @Test 81 | public void clientBadCredentialsSanityCheckOk() throws Exception { 82 | HttpResponse response = requestWithCredentials("admin:wrong").path("/").execute(); 83 | assertThat(response.getStatusCode() 84 | , equalTo(RestStatus.OK.getStatus())); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/com/asquera/elasticsearch/plugins/http/auth/integration/HttpBasicServerPluginIntegrationTest.java: -------------------------------------------------------------------------------- 1 | 2 | package com.asquera.elasticsearch.plugins.http.auth.integration; 3 | 4 | 5 | import java.net.InetSocketAddress; 6 | 7 | import org.apache.http.impl.client.HttpClients; 8 | import org.elasticsearch.common.transport.InetSocketTransportAddress; 9 | import org.elasticsearch.common.Base64; 10 | import org.elasticsearch.http.HttpServerTransport; 11 | import org.elasticsearch.test.ElasticsearchIntegrationTest; 12 | import org.elasticsearch.test.rest.client.http.HttpRequestBuilder; 13 | import com.asquera.elasticsearch.plugins.http.HttpBasicServerPlugin; 14 | import org.elasticsearch.common.settings.ImmutableSettings.Builder; 15 | import org.elasticsearch.common.settings.ImmutableSettings; 16 | 17 | /** 18 | * 19 | * @author Ernesto Miguez (ernesto.miguez@asquera.de) 20 | */ 21 | 22 | public class HttpBasicServerPluginIntegrationTest extends 23 | ElasticsearchIntegrationTest { 24 | 25 | protected final String localhost = "127.0.0.1"; 26 | 27 | 28 | /** 29 | * 30 | * @return a Builder with the plugin included and bind_host and publish_host 31 | * set to localhost, from where the client's request ip will be done. 32 | */ 33 | protected Builder builderWithPlugin() { 34 | return ImmutableSettings.settingsBuilder() 35 | .put("network.host", localhost) 36 | .put("plugin.types", HttpBasicServerPlugin.class.getName()); 37 | } 38 | 39 | protected HttpRequestBuilder requestWithCredentials(String credentials) throws Exception { 40 | return httpClient().path("/_status") 41 | .addHeader("Authorization", "Basic " + Base64.encodeBytes(credentials.getBytes())); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/asquera/elasticsearch/plugins/http/auth/integration/IpAuthenticationIntegrationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to Elasticsearch under one or more contributor 3 | * license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright 5 | * ownership. Elasticsearch licenses this file to you under 6 | * the Apache License, Version 2.0 (the "License"); you may 7 | * not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | package com.asquera.elasticsearch.plugins.http.auth.integration; 20 | 21 | import org.elasticsearch.common.settings.Settings; 22 | import org.elasticsearch.common.Base64; 23 | import org.elasticsearch.rest.RestStatus; 24 | import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; 25 | import org.elasticsearch.test.rest.client.http.HttpRequestBuilder; 26 | import org.elasticsearch.test.rest.client.http.HttpResponse; 27 | import org.junit.Test; 28 | 29 | import java.util.List; 30 | import java.util.ArrayList; 31 | import java.util.Iterator; 32 | 33 | import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; 34 | import static org.hamcrest.Matchers.equalTo; 35 | 36 | /** 37 | * Test a rest action that sets special response headers 38 | */ 39 | @ClusterScope(transportClientRatio = 0.0, scope = Scope.SUITE, numDataNodes = 1) 40 | public class IpAuthenticationIntegrationTest extends HttpBasicServerPluginIntegrationTest { 41 | 42 | protected final String whitelistedIp = "2.2.2.2"; 43 | protected final String notWhitelistedIp = "3.3.3.3"; 44 | protected final String trustedIp = "4.4.4.4"; 45 | 46 | @Override 47 | protected Settings nodeSettings(int nodeOrdinal) { 48 | return builderWithPlugin() 49 | .putArray("http.basic.ipwhitelist", whitelistedIp) 50 | .putArray("http.basic.trusted_proxy_chains", trustedIp + "," + localhost) 51 | .put("http.basic.xforward", "X-Forwarded-For") 52 | .build(); 53 | } 54 | 55 | @Test 56 | public void testHealthCheck() throws Exception { 57 | HttpResponse response = httpClient().path("/").execute(); 58 | assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); 59 | } 60 | 61 | @Test 62 | public void clientGoodCredentialsBasicAuthenticationSuceeds() throws Exception { 63 | HttpResponse response = requestWithCredentials("admin:admin_pw") 64 | .addHeader("X-Forwarded-For", "1.1.1.1" ).execute(); 65 | assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); 66 | } 67 | 68 | @Test 69 | public void clientBadCredentialsBasicAuthenticationFails() throws Exception { 70 | HttpResponse response = requestWithCredentials("admin:wrong").execute(); 71 | assertThat(response.getStatusCode() 72 | , equalTo(RestStatus.UNAUTHORIZED.getStatus())); 73 | } 74 | @Test 75 | public void proxyViaLocalhostIpAuthenticatesWhitelistedClients() throws Exception { 76 | List whitelists = new ArrayList(); 77 | whitelists.add(whitelistedIp); 78 | whitelists.add(notWhitelistedIp + "," + whitelistedIp); 79 | whitelists.add(notWhitelistedIp + "," + whitelistedIp + "," + trustedIp); 80 | Iterator iterator = whitelists.iterator(); 81 | while (iterator.hasNext()) { 82 | String next = iterator.next(); 83 | HttpResponse response = requestWithCredentials("admin:wrong") 84 | .addHeader("X-Forwarded-For", next) 85 | .execute(); 86 | assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); 87 | } 88 | } 89 | 90 | @Test 91 | public void proxyViaLocalhostIpUnauthenticatesNonWhitelistedClients() throws Exception { 92 | List whitelists = new ArrayList(); 93 | whitelists.add(notWhitelistedIp); 94 | whitelists.add(whitelistedIp + "," + notWhitelistedIp + "," + trustedIp); 95 | whitelists.add(""); 96 | Iterator iterator = whitelists.iterator(); 97 | while (iterator.hasNext()) { 98 | String next = iterator.next(); 99 | HttpResponse response = requestWithCredentials("admin:wrong") 100 | .addHeader("X-Forwarded-For", next) 101 | .execute(); 102 | assertThat(response.getStatusCode(), equalTo(RestStatus.UNAUTHORIZED.getStatus())); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO, out 2 | 3 | log4j.appender.out=org.apache.log4j.ConsoleAppender 4 | log4j.appender.out.layout=org.apache.log4j.PatternLayout 5 | log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n 6 | --------------------------------------------------------------------------------