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
[DO NOT MERGE] bug: CORS headers not returned when mw shortcircuits upsteam #1550
Conversation
909ddd8
to
593debb
Compare
PR submitted for early feedback please |
593debb
to
2711ade
Compare
Present implementation: If a POST request is sent with a body, we get an error. But if body is empty, then it works as expected.
Need to investigate this. |
d0bdbe5
to
7ea4f2e
Compare
Per @asoorm have copied from the main issue:
Sorry, haven't thoroughly checked, this is off the top of my head |
err, errCode := mw.ProcessRequest(w, r, mwConf) | ||
if err != nil { | ||
|
||
if errCode == http.StatusForbidden { |
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.
how would we know that this http.StatusForbidden
caused by one of our middlewares, not from actual upstream?
I think if it is reply from upstream we don't need to do anything as it was resposibility of upstream to set CORS headers and they might already there.
also, how about other HTTP 4XX which can be set by other our middlewares?
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.
Because at the point this has been injected - it's from the middleware, and not the upstream. So at this stage, the request has not yet been proxied and we are about to return an error to the user.
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.
my understanding is that this depends on order of middlewares. it is not super clear for me that the request has not yet been proxied
corsRes, err := corsClient.Do(corsRequest) | ||
if err != nil { | ||
log.WithError(err).Error("Error from upstream OPTIONS request, ignoring and returning default response") | ||
} |
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.
connection leak, we should do defer corsRes.Body.Close()
7ea4f2e
to
04f0c36
Compare
} | ||
|
||
corsClient := &http.Client{Transport: tr} | ||
corsRes, err := corsClient.Do(corsRequest) |
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.
this needs to have some kind of circuit breaker or at least time out for client to avoid waiting for upstream when it is down (if we don't it could cause bump in gateway latency)
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.
So the circuit breaker I am not sure is required. because we are calling Tyk itself but this time, as a pre-flight request.
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.
oh, I though we were going to send OPTIONS to upstream directly. it seems to me that calling GW itself again is an extra step (but it should work). I'd add timeout to request anyways
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.
That's why it's called loopback. because the gw calls it's own API. The reasoning for this is because the OPTIONS request needs to go through the exact same MW chain as the original pre-flight request would have done before so that we get the same request to the same upstream endpoint.
e.g. there may be MW in place which re-writes the target.
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, got it. according to original pre-flight request - some browsers don't send pre-flight requests for all ajax calls, so original OPTIONS one might never happened. I don't think it would cause any issues in this case, just wanted to mention 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.
I am still not sure about this loopback round trip business, as looks like all we want to do is just to hit the code placed a couple of lines above here: https://github.com/TykTechnologies/tyk/pull/1550/files#diff-4c166b743d95629a9ab37eb6fe3df22aR86
why not just to prepare OPTIONS request like we do anyways and response writer (we can probably use httptest recoder) and then call h.ServeHTTP(optionsRecorder, optionsRequest)
to make it go through all underlaying middle-wares down to upstream?
middleware.go
Outdated
TLSClientConfig: &tls.Config{InsecureSkipVerify: config.Global.HttpServerOptions.SSLInsecureSkipVerify}, | ||
} | ||
|
||
corsClient := &http.Client{Transport: tr} |
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'd add corsClient
as field of middleware and change loopbackCORS
to be method of middleware so we won't re-create http.Client
within every call of loopbackCORS
and benefit from keep-alived connection
log.WithError(err).Error("Error from upstream OPTIONS request, ignoring and returning default response") | ||
} | ||
|
||
corsHeaders := map[string]string{} |
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.
might worth to cache the reply with using as key Origin
received from browser to improve latency and avoid bombing upstream with OPTIONS
for the same origin every time we error
|
||
for k, v := range corsHeaders { | ||
if v != "" { | ||
w.Header().Set(k, v) |
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.
we need to make sure that response wasn't already sent to browser at this point. setting headers on http.ResponseWriter
when response already sent won't take any affect
In the current scenario, when the gateway does not handle CORS, and OptionsPassthrough is set to true, if the tyk middleware shortcircuits the response, tyk is not able to return the required upstream CORS headers. This fixes the issue by detecting the appropriate conditions, and sending an OPTIONS request via loopback, then appending the full set of CORS headers to the error response.
by default, setting timeout to 2 seconds. Potential issue with becoming flooded with POST requests with failed auth - causing loopback OPTIONS requests. Need to investigate and look into further.
af429ec
to
5b16d40
Compare
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs, please add comments to this ticket if you would like it to stay open. Thank you for your contributions. |
#1506
In the current scenario, when the gateway does not handle CORS, and
OptionsPassthrough is set to true, if the tyk middleware shortcircuits
the response, tyk is not able to return the required upstream CORS
headers.
This fixes the issue by detecting the appropriate conditions, and
sending an OPTIONS request via loopback, then appending the full set of
CORS headers to the error response.