-
Notifications
You must be signed in to change notification settings - Fork 359
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
feat: pass only essential and configured headers to authenticator #952
Conversation
I tried to use oathkeeper with Ory cloud for the local project. I set up everything and it worked except the header mutator. Another solution to solve that case is to create an HTTP client with the DisableCompression option for http.Client.Transport
but it doesn't handle deflate encoding. |
Codecov Report
@@ Coverage Diff @@
## master #952 +/- ##
==========================================
+ Coverage 66.01% 66.26% +0.25%
==========================================
Files 106 107 +1
Lines 4723 4764 +41
==========================================
+ Hits 3118 3157 +39
- Misses 1324 1325 +1
- Partials 281 282 +1
Continue to review full report at Codecov.
|
body, err := ioutil.ReadAll(res.Body) | ||
var reader io.Reader | ||
switch res.Header.Get("Content-Encoding") { | ||
case "gzip": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
http.DefaultTransport
automatically handles gzip decoding if the correct headers are set: https://stackoverflow.com/questions/13130341/reading-gzipped-http-response-in-go
So this should not be required!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the http.DefaultTransport
does not use the DisableCompression
property, and it's disabled by default. Also, the standard HTTP client does not support automatic decompression of deflate
encoding.
I can give you a way how to reproduce the issue because I reproduced it by having this setup.
- Ory proxy proxies traffic from Ory Cloud
- Oathkeeper uses ory proxy endpoint and cookie_session authenticator
- Oathkeeper as one upstream to proxy traffic to
I found that with mutator headerX-User
that comes from the cookie_authenticator is always empty and after a couple of hours of debugging I found that's a gzip issue.
I think that I'll write a couple of tests to prove the bug, and maybe I'll find a better solution, but also a couple of users had the same issue in our Slack community
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, the default client handles this by itself . I think I'll start with the tests first and try to reproduce it on a clean setup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, that sounds great! We had issues with GZIP in the past in Ory Oathkeeper. I think it depends a bit on where in the pipeline the issue is. I think the proxy had issues with GZIP as it did not decode them automatically - here however I think we do?
Another problem that can happen is that we forward requests with all the headers that might include e.g. Content-Encoding: gzip
but we actually do not use gzip in the body. So basically an incoming request to oathkeeper is:
GET /foo
Content-Encoding: gzip
...gzip-body...
then Ory Oathkeeper reads that data and decompresses it. Next, it forwards the request to e.g. the session endpoint with all the HTTP headers:
GET /sessions/whoami
Content-Encoding: gzip
...here the body is actually not gzip any more because it was decoded in oathkeeper already...
which then causes issues at the receiver.
I think that, in the end, we will need to refactor this so that we only send "wanted" headers to the upstream services instead of sending a catch-all. The same underlying issues is the reason for this: #954
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the problem is the opposite way around... The behaviour of the Go HTTP client depends on whether the http.Request
explicitly sets the Accept-Encoding: gzip
header or not. If no header is set, the default http client automatically adds it before firing the request, and transparently handles decompression of the response for you (stripping out any Content-Encoding
response headers).
If the request header is set though, it assumes you want to handle the decompression manually, and so returns the response compressed.
This can happen and cause problems if the service you're proxying serves compressed content in response to a request with Accept-Encoding: gzip
(e.g. an nginx ingress with transparent compression enabled) - Oathkeeper would receive the response which would normally be decompressed, but because the inbound request had the Accept-Encoding
header set, and we pass it on to the the Go http client, the Go client would not transparently decompress it.
The offending lines are below, the blind copy of all inbound request headers onto the outbound request on L157.
for k, v := range r.Header {
req.Header[k] = v
}
We should add a check for the Accept-Encoding
header here, and not copy it, to avoid this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, that can also very well be the root cause! I think the effect and best resolution is to only pass essential and configured headers to the upstream and leave the user the ability to pass additional headers through the config too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a draft implementation to pass only essential headers. It's still a work in progress and has a lack of tests for gzip/deflate encodings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be a breaking change for the oathkeeper. I added "Authorization" and "Cookie" headers as default values to proxy_headers
to provide backward compatibility and not break things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, this goes in a very good direction! It will also need a breaking change note as well as some documentation in ory/docs :)
@aeneasr I think this is ready for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks quite good :)
Just a few questions
@@ -96,7 +96,7 @@ func TestAuthenticatorBearerToken(t *testing.T) { | |||
w.WriteHeader(200) | |||
w.Write([]byte(`{"sub": "123"}`)) | |||
}, | |||
config: []byte(`{"preserve_query": true}`), | |||
config: []byte(`{"preserve_query": true, "forward_http_headers": ["Authorization"]}`), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a question, i see here we are adding forward_http_headers
for Authorization
in many of these tests. Aren't they included automatically now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. I wrote tests first and later I decided to implement some backward compatibility. I can remove it from the tests if you want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah i think it makes sense to remove it. otherwise it will just confuse in future.
We should just add a test though to make sure these headers are always present in these authentictors (explicitly even though the other tests infer it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed it :)
if len(c.ForwardHTTPHeaders) == 0 { | ||
c.ForwardHTTPHeaders = []string{header.Authorization, header.Cookie} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to make sure I'm understanding this correctly, if we have no forward_http_headers
we would always add Authorization
and Cookie
. But shouldn't that be the case for bearer
and cookie
authenticators anyway, even if we add more forward_http_headers
?
referring to this discussion #952 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I found the issue here. Right now oathkeeper adds Authorization
and Cookie
headers only when we have no forward_http_headers
configured. However, I think that the oathkeeper should have them in any case because they're essential.
The issue may occur when someone configures something like
forward_http_headers:
- Accept
- X-User
In that case, the authentication flow breaks because the oathkeeper does not proxies essential headers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that we can remove if len(c.ForwardHTTPHeaders) == 0
and append []string{header.Authorization, header.Cookie}
anyway. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i agree since it is required for bearer
or cookie
authenticator to work properly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed it too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM :)
@aeneasr It's ready for another round of review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot better :)
Just a few more questions and minor changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, we're getting there :) Can you please take a look at the comments? Additionally, can you please add two tests:
- A test for bearer token where a custom header is forwarded
- A test for session cookie where a custom header is forwarded
Regarding the CI format failure, @kevgo will take a look
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job :)
BREAKING CHANGE: From now on, the
bearer_token
andcookie_session
handlers pass only the needed header (Authorization
,Cookie
) to the check URL. To pass additional headers, use theforward_http_headers
configuration key.Closes #954
Closes ory/network#76