8000 Merge pull request #6 from karussell/master · cweibel/elasticsearch-http-basic@26b58ce · GitHub
[go: up one dir, main page]

Skip to content

Commit 26b58ce

Browse files
author
8000
Peter
committed
Merge pull request Asquera#6 from karussell/master
Maven integration, 0.20.5 update, IP whitelist, CORS support
2 parents 8b94c87 + b9a4d21 commit 26b58ce

File tree

7 files changed

+231
-52
lines changed

7 files changed

+231
-52
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
*.class
22
*.jar
33
*.zip
4+
/target/
5+
*~
6+
deploy.sh

pom.xml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<groupId>com.asquera.elasticsearch</groupId>
6+
<artifactId>http-basic</artifactId>
7+
<version>0.20.5-SNAPSHOT</version>
8+
<packaging>jar</packaging>
9+
10+
<name>Basic Authentication Plugin</name>
11+
<url>http://maven.apache.org</url>
12+
13+
<properties>
14+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15+
<elasticsearch.version>0.20.5</elasticsearch.version>
16+
</properties>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>org.elasticsearch</groupId>
21+
<artifactId>elasticsearch</artifactId>
22+
<version>${elasticsearch.version}</version>
23+
</dependency>
24+
25+
<dependency>
26+
<groupId>org.testng</groupId>
27+
<artifactId>testng</artifactId>
28+
<version>6.8</version>
29+
<scope>test</scope>
30+
<exclusions>
31+
<exclusion>
32+
<groupId>org.hamcrest</groupId>
33+
<artifactId>hamcrest-core</artifactId>
34+
</exclusion>
35+
<exclusion>
36+
<groupId>junit</groupId>
37+
<artifactId>junit</artifactId>
38+
</exclusion>
39+
</exclusions>
40+
</dependency>
41+
<dependency>
42+
<groupId>org.hamcrest</groupId>
43+
<artifactId>hamcrest-all</artifactId>
44+
<version>1.3</version>
45+
<scope>test</scope>
46+
</dependency>
47+
</dependencies>
48+
<build>
49+
<!-- Create a zip file according to elasticsearch naming scheme -->
50+
<plugins>
51+
<plugin>
52+
<groupId>org.apache.maven.plugins</groupId>
53+
<artifactId>maven-antrun-plugin</artifactId>
54+
<version>1.6</version>
55+
<executions>
56+
<execution>
57+
<id>zip</id>
58+
<phase>package</phase>
59+
<configuration>
60+
<target>
61+
<zip basedir="${project.build.directory}"
62+
includes="${project.build.finalName}.jar"
63+
destfile="${project.build.directory}/elasticsearch-${project.artifactId}-${elasticsearch.version}.zip" />
64+
</target>
65+
</configuration>
66+
<goals>
67+
<goal>run</goal>
68+
</goals>
69+
</execution>
70+
</executions>
71+
</plugin>
72+
</plugins>
73+
74+
<pluginManagement>
75+
<plugins>
76+
<plugin>
77+
<groupId>org.apache.maven.plugins</groupId>
78+
<artifactId>maven-compiler-plugin</artifactId>
79+
<configuration>
80+
<source>1.6</source>
81+
<target>1.6</target>
82+
</configuration>
83+
</plugin>
84+
</plugins>
85+
</pluginManagement>
86+
</build>
87+
</project>

reinstall.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
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/java/com/asquera/elasticsearch/plugins/http/HttpBasicServer.java

Lines changed: 123 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,71 +9,156 @@
99
import org.elasticsearch.common.Base64;
1010

1111
import org.elasticsearch.rest.RestRequest;
12-
import org.elasticsearch.rest.StringRestResponse;
1312

1413
import static org.elasticsearch.rest.RestStatus.*;
1514

1615
import java.io.IOException;
16+
import java.util.Arrays;
17+
import java.util.HashSet;
18+
import java.util.Set;
19+
import org.elasticsearch.common.logging.Loggers;
20+
import org.elasticsearch.rest.RestRequest.Method;
21+
import org.elasticsearch.rest.StringRestResponse;
1722

23+
// # possible http config
24+
// http.basic.user: admin
25+
// http.basic.password: password
26+
// http.basic.ipwhitelist: ["localhost", "somemoreip"]
27+
// http.basic.xforward: "X-Forwarded-For"
28+
// # if you use javascript
29+
// # EITHER $.ajaxSetup({ headers: { 'Authorization': "Basic " + credentials }});
30+
// # OR use beforeSend in $.ajax({
31+
// http.cors.allow-headers: "X-Requested-With, Content-Type, Content-Length, Authorization"
32+
//
1833
/**
1934
* @author Florian Gilcher (florian.gilcher@asquera.de)
35+
* @author Peter Karich
2036
*/
2137
public class HttpBasicServer extends HttpServer {
38+
2239
private final String user;
2340
private final String password;
24-
41+
private final Set<String> whitelist;
42+
private final String xForwardFor;
43+
private final boolean log;
44+
2545
@Inject public HttpBasicServer(Settings settings, Environment environment, HttpServerTransport transport,
26-
RestController restController,
27-
NodeService nodeService) {
28-
super(settings, environment, transport, restController, nodeService);
29-
30-
this.user = settings.get("http.basic.user");
31-
this.password = settings.get("http.basic.password");
46+
RestController restController,
47+
NodeService nodeService) {
48+
super(settings, environment, transport, restController, nodeService);
49+
50+
this.user = settings.get("http.basic.user", "admin");
51+
this.password = settings.get("http.basic.password", "admin_pw");
52+
this.whitelist = new HashSet<String>(Arrays.asList(
53+
settings.getAsArray("http.basic.ipwhitelist",
54+
new String[]{"localhost", "127.0.0.1"})));
55+
56+
// for AWS load balancers it is X-Forwarded-For -> hmmh does not work
57+
this.xForwardFor = settings.get("http.basic.xforward", "");
58+
this.log = settings.getAsBoolean("http.basic.log", false);
59+
Loggers.getLogger(getClass()).info("using {}:{} with whitelist {}, xforward {}",
60+
user, password, whitelist, xForwardFor);
3261
}
33-
62+
63+
@Override
3464
public void internalDispatchRequest(final HttpRequest request, final HttpChannel channel) {
35-
if (shouldLetPass(request) || authBasic(request)) {
65+
if (log)
66+
logger.info("Authorization:{}, host:{}, xforward:{}, path:{}, isInWhitelist:{}, Client-IP:{}, X-Client-IP:{}",
67+
request.header("Authorization"), request.header("host"),
68+
request.header(xForwardFor), request.path(), isInIPWhitelist(request),
69+
request.header("X-Client-IP"), request.header("Client-IP"));
70+
71+
// allow health check even without authorization
72+
if (healthCheck(request)) {
73+
channel.sendResponse(new StringRestResponse(OK, "{\"OK\":{}}"));
74+
} else if (allowOptionsForCORS(request) || authBasic(request) || isInIPWhitelist(request)) {
3675
super.internalDispatchRequest(request, channel);
3776
} else {
38-
channel.sendResponse(new StringRestResponse(UNAUTHORIZED));
77+
String addr = getAddress(request);
78+
Loggers.getLogger(getClass()).error("UNAUTHORIZED type:{}, address:{}, path:{}, request:{}, content:{}, credentials:{}",
79+
request.method(), addr, request.path(), request.params(), request.content().toUtf8(), getDecoded(request));
80+
channel.sendResponse(new StringRestResponse(UNAUTHORIZED, "Authentication Required"));
3981
}
4082
}
4183

42-
private boolean shouldLetPass(final HttpRequest request) {
43-
return (request.method() == RestRequest.Method.GET) && request.path().equals("/");
84+
private boolean healthCheck(final HttpRequest request) {
85+
String path = request.path();
86+
return (request.method() == RestRequest.Method.GET) && path.equals("/");
4487
}
45-
46-
private boolean authBasic(final HttpRequest request){
88+
89+
public String getDecoded(HttpRequest request) {
4790
String authHeader = request.header("Authorization");
48-
49-
if (authHeader == null) {
50-
return false;
51-
}
52-
53-
String[] split = authHeader.split(" ");
91+
if (authHeader == null)
92+
return "";
5493

55-
if (!split[0].equals("Basic")){
56-
return false;
94+
String[] split = authHeader.split(" ", 2);
95+
if (split.length != 2 || !split[0].equals("Basic"))
96+
return "";
97+
try {
98+
return new String(Base64.decode(split[1]));
99+
} catch (IOException ex) {
100+
throw new RuntimeException(ex);
57101
}
102+
}
58103

59-
String decoded = null;
60-
104+
private boolean authBasic(final HttpRequest request) {
105+
String decoded = "";
61106
try {
62-
decoded = new String(Base64.decode(split[1]));
63-
} catch (IOException e) {
64-
logger.warn("Decoding of basic auth failed.");
65-
return false;
107+
decoded = getDecoded(request);
108+
if (!decoded.isEmpty()) {
109+
String[] userAndPassword = decoded.split(":", 2);
110+
String givenUser = userAndPassword[0];
111+
String givenPass = userAndPassword[1];
112+
if (this.user.equals(givenUser) && this.password.equals(givenPass))
113+
return true;
114+
}
115+
} catch (Exception e) {
116+
logger.warn("Retrieving of user and password failed for " + decoded + " ," + e.getMessage());
66117
}
67-
68-
String[] user_and_password = decoded.split(":");
69-
String given_user = user_and_password[0];
70-
String given_pass = user_and_password[1];
71-
72-
if (this.user.equals(given_user) &&
73-
this.password.equals(given_pass)) {
74-
return true;
118+
return false;
119+
}
120+
121+
private String getAddress(HttpRequest request) {
122+
String addr;
123+
if (xForwardFor.isEmpty()) {
124+
addr = request.header("Host");
125+
addr = addr == null ? "" : addr;
75126
} else {
127+
addr = request.header(xForwardFor);
128+
addr = addr == null ? "" : addr;
129+
int addrIndex = addr.indexOf(',');
130+
if (addrIndex >= 0)
131+
addr = addr.substring(0, addrIndex);
132+
}
133+
134+
int portIndex = addr.indexOf(":");
135+
if (portIndex >= 0)
136+
addr = addr.substring(0, portIndex);
137+
return addr;
138+
}
139+
140+
private boolean isInIPWhitelist(HttpRequest request) {
141+
String addr = getAddress(request);
142+
// Loggers.getLogger(getClass()).info("address {}, path {}, request {}",
143+
// addr, request.path(), request.params());
144+
if (whitelist.isEmpty() || addr.isEmpty())
76145
return false;
146+
return whitelist.contains(addr);
147+
}
148+
149+
/**
150+
* https://en.wikipedia.org/wiki/Cross-origin_resource_sharing the
151+
* specification mandates that browsers “preflight” the request, soliciting
152+
* supported methods from the server with an HTTP OPTIONS request
153+
*/
154+
private boolean allowOptionsForCORS(HttpRequest request) {
155+
// in elasticsearch.yml set
156+
// http.cors.allow-headers: "X-Requested-With, Content-Type, Content-Length, Authorization"
157+
if (request.method() == Method.OPTIONS) {
158+
// Loggers.getLogger(getClass()).error("CORS type {}, address {}, path {}, request {}, content {}",
159+
// request.method(), getAddress(request), request.path(), request.params(), request.content().toUtf8());
160+
return true;
77161
}
162+
return false;
78163
}
79-
}
164+
}

src/main/java/com/asquera/elasticsearch/plugins/http/HttpBasicServerModule.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.asquera.elasticsearch.plugins.http;
22

3-
import org.elasticsearch.common.inject.AbstractModule;
4-
import com.asquera.elasticsearch.plugins.http.HttpBasicServer;
53
import org.elasticsearch.http.HttpServerModule;
64
import org.elasticsearch.common.settings.Settings;
75

src/main/java/com/asquera/elasticsearch/plugins/http/HttpBasicServerPlugin.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@
44
import org.elasticsearch.common.inject.Module;
55
import org.elasticsearch.common.settings.Settings;
66
import org.elasticsearch.plugins.AbstractPlugin;
7-
import org.elasticsearch.http.HttpServerModule;
8-
import org.elasticsearch.http.HttpServer;
9-
import com.asquera.elasticsearch.plugins.http.HttpBasicServer;
10-
import com.asquera.elasticsearch.plugins.http.HttpBasicServerModule;
117
import org.elasticsearch.common.component.LifecycleComponent;
128
import org.elasticsearch.common.settings.ImmutableSettings;
139

@@ -19,8 +15,10 @@
1915
* @author Florian Gilcher (florian.gilcher@asquera.de)
2016
*/
2117
public class HttpBasicServerPlugin extends AbstractPlugin {
18+
19+
private boolean enabledByDefault = true;
2220
private final Settings settings;
23-
21+
2422
@Inject public HttpBasicServerPlugin(Settings settings) {
2523
this.settings = settings;
2624
}
@@ -32,26 +30,28 @@ public class HttpBasicServerPlugin extends AbstractPlugin {
3230
@Override public String description() {
3331
return "HTTP Basic Server Plugin";
3432
}
35-
33+
3634
@Override public Collection<Class<? extends Module>> modules() {
3735
Collection<Class<? extends Module>> modules = newArrayList();
38-
if (settings.getAsBoolean("http.basic.enabled", false)) {
36+
if (settings.getAsBoolean("http.basic.enabled", enabledByDefault)) {
3937
modules.add(HttpBasicServerModule.class);
4038
}
4139
return modules;
4240
}
43-
41+
4442
@Override public Collection<Class<? extends LifecycleComponent>> services() {
4543
Collection<Class<? extends LifecycleComponent>> services = newArrayList();
46-
if (settings.getAsBoolean("http.basic.enabled", false)) {
44+
if (settings.getAsBoolean("http.basic.enabled", enabledByDefault)) {
4745
services.add(HttpBasicServer.class);
4846
}
4947
return services;
5048
}
51-
49+
5250
@Override public Settings additionalSettings() {
53-
if (settings.getAsBoolean("http.basic.enabled", false)) {
54-
return ImmutableSettings.settingsBuilder().put("http.enabled", false).build();
51+
if (settings.getAsBoolean("http.basic.enabled", enabledByDefault)) {
52+
return ImmutableSettings.settingsBuilder().
53+
put("http.enabled", false).
54+
build();
5555
} else {
5656
return ImmutableSettings.Builder.EMPTY_SETTINGS;
5757
}

0 commit comments

Comments
 (0)
0