99import org .elasticsearch .common .Base64 ;
1010
1111import org .elasticsearch .rest .RestRequest ;
12- import org .elasticsearch .rest .StringRestResponse ;
1312
1413import static org .elasticsearch .rest .RestStatus .*;
1514
1615import 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 */
2137public 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+ }
0 commit comments