Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CORS: Allowed to configure allow-credentials header to work via SSL #6380

Closed
erikringsmuth opened this issue Jun 2, 2014 · 6 comments · Fixed by #7059
Closed

CORS: Allowed to configure allow-credentials header to work via SSL #6380

erikringsmuth opened this issue Jun 2, 2014 · 6 comments · Fixed by #7059

Comments

@erikringsmuth
Copy link
Contributor

Elasticsearch supports CORS and Basic Authentication but not together. A browser sending a cross-origin request will omit credentials unless the XHR withCredentials field is set to true. If withCredentials = true there are two additional checks the browser must perform on the preflight response before sending the real request.

  • Preflight response Access-Control-Allow-Origin must be the Origin (host), not *
  • Preflight response must have header Access-Control-Allow-Credentials: true

The W3C spec http://www.w3.org/TR/access-control/#resource-sharing-check-0 explains the CORS withCredentials check.

These headers can be added with 2 lines of code in org.elasticsearch.http.netty.NettyHttpChannel.java at line 95.

// Add support for cross-origin Ajax requests (CORS)
resp.headers().add("Access-Control-Allow-Origin", transport.settings().get("http.cors.allow-origin", HttpHeaders.getHeader(nettyRequest, "Origin", "*")));
resp.headers().add("Access-Control-Allow-Credentials", transport.settings().get("http.cors.allow-credentials", "true"));

This would make the Access-Control-Allow-Origin header fall back to the Origin header before falling back to *. It would also add the new header Access-Control-Allow-Credentials: true.

Reproduce steps

  1. Start an elasticsearch instance on localhost:9200
  2. Open a browser window with any domain other than localhost:9200. I'm using an empty Chrome tab in my example.
  3. Run this JavaScript to send a request with basic authentication
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://user:passwd@localhost:9200', true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send('{"query":{"match_all":{}}}');

Preflight Request

OPTIONS http://localhost:9200/ HTTP/1.1
Host: localhost:9200
Connection: keep-alive
Cache-Control: no-cache
Authorization: Basic YWRtaW46d2VzdA==
Access-Control-Request-Method: POST
Pragma: no-cache
Origin: https://www.google.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.116 Safari/537.36
Access-Control-Request-Headers: content-type
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8,et;q=0.6

Preflight Response

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 3
Access-Control-Allow-Methods: OPTIONS, HEAD, GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Requested-With, Content-Type, Content-Length
Content-Type: text/plain; charset=UTF-8
Content-Length: 0

The browser throws an error at this point saying the Access-Control-Allow-Origin and Access-Control-Allow-Credentials headers are bad.

Notes

After you make the code changes the browser will start to cache preflight requests. When this happens you will only see the real request in Fiddler. To clear the cache, close the window and open a new one.

The XHR must be a non-simple request. This is a rather vague spec but a POST with a content type of application/json should do it.

IE skips most of these checks so don't bother testing in IE.

The example preflight request header Authorization: Basic YWRtaW46d2VzdA== is a bug in Chrome and shouldn't be included until the real request. There is a bug open for this here https://code.google.com/p/chromium/issues/detail?id=377541. Firefox works correctly and doesn't include the credentials on the preflight.

@clintongormley
Copy link
Contributor

/cc @spinscale @rashidkpc

@spinscale spinscale removed the adoptme label Jul 25, 2014
@spinscale spinscale self-assigned this Jul 25, 2014
@spinscale
Copy link
Contributor

Hey,

first, thanks a lot for such a long and already very well described issue, makes it easy for me to followup.

Regarding sending the Origin headers, I just added #6923 to the master and 1.4 branch. I think this should already suffice you, right? If you wanted to allow everything, you could simply use the new regex feature and allow all origin headers.

Your second request is not yet solved, as far as I read it at https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS?#Requests_with_credentials - there is nothing more to do, than providing this with another configuration option, right (so basically incorporating your suggestion)?

I do get it correctly, that there is no real possibility on the server side, that credentials are sent, right? .There might be a cookie and there might be an auth header. As this is not my area of expertise, feel free to correct and I would be happy to get feedback on the above PR and if it solves your first problem, which I think it should.

@erikringsmuth
Copy link
Contributor Author

Hey @spinscale,

The regex change works great for the Access-Control-Allow-Origin header! I tested it out and it resolves to the caller's host. I then get the next CORS warning about credentials. Adding this code gets it completely working.

src/main/java/org/elasticsearch/http/netty/NettyHttpChannel.java line 100

if (transport.settings().getAsBoolean("http.cors.enabled", true)) {
    /// ... existing checks

    // new check starting at line 115
    if (transport.settings().getAsBoolean("http.cors.allow-credentials", false)) {
        resp.headers().add(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
    }
}

Then I set up my elasticsearch.yml like this.

http.cors.allow-origin: "/.*/"
http.cors.allow-credentials: true

Now running this JavaScript succeeds. It actually returns a 404 but this is the correct behavior since a POST http://localhost:9200/ HTTP/1.1 is invalid. The good news is it doesn't give any CORS warnings at this point.

var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://user:passwd@localhost:9200', true);
xhr.withCredentials = true;
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send('{"query":{"match_all":{}}}');

spinscale added a commit to spinscale/elasticsearch that referenced this issue Aug 5, 2014
This adds support to return the "Access-Control-Allow-Credentials" header
if needed, so CORS will work flawlessly with authenticated applications.

Closes elastic#6380
spinscale added a commit that referenced this issue Aug 5, 2014
This adds support to return the "Access-Control-Allow-Credentials" header
if needed, so CORS will work flawlessly with authenticated applications.

Closes #6380
@spinscale spinscale changed the title CORS With Basic Authentication Fails CORS: Allowed to configure allow-credentials header to work via SSL Aug 5, 2014
spinscale added a commit that referenced this issue Sep 8, 2014
This adds support to return the "Access-Control-Allow-Credentials" header
if needed, so CORS will work flawlessly with authenticated applications.

Closes #6380
@digitalpacman
Copy link

+1

Spent a whole day thinking it was my fault on how I setup cors on Grafana.

Could you please release this ASAP.

@rachellji
Copy link

happened to me too. I'm using 2.0. It was working fine. today I couldn't search and got "Request header field X-CSRF-TOKEN is not allowed by Access-Control-Allow-Headers in preflight response"

@clintongormley
Copy link
Contributor

@rachellji you just need to update your config to list that header

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants