9
9
import org .elasticsearch .common .Base64 ;
10
10
11
11
import org .elasticsearch .rest .RestRequest ;
12
- import org .elasticsearch .rest .StringRestResponse ;
13
12
14
13
import static org .elasticsearch .rest .RestStatus .*;
15
14
16
15
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 ;
17
22
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
+ //
18
33
/**
19
34
* @author Florian Gilcher (florian.gilcher@asquera.de)
35
+ * @author Peter Karich
20
36
*/
21
37
public class HttpBasicServer extends HttpServer {
38
+
22
39
private final String user ;
23
40
private final String password ;
24
-
41
+ private final Set <String > whitelist ;
42
+ private final String xForwardFor ;
43
+ private final boolean log ;
44
+
25
45
@ 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 );
32
61
}
33
-
62
+
63
+ @ Override
34
64
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 )) {
36
75
super .internalDispatchRequest (request , channel );
37
76
} 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" ));
39
81
}
40
82
}
41
83
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 ("/" );
44
87
}
45
-
46
- private boolean authBasic ( final HttpRequest request ){
88
+
89
+ public String getDecoded ( HttpRequest request ) {
47
90
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 "" ;
54
93
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 );
57
101
}
102
+ }
58
103
59
- String decoded = null ;
60
-
104
+ private boolean authBasic ( final HttpRequest request ) {
105
+ String decoded = "" ;
61
106
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 ());
66
117
}
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 ;
75
126
} 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 ())
76
145
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 ;
77
161
}
162
+ return false ;
78
163
}
79
- }
164
+ }
0 commit comments