-
Notifications
You must be signed in to change notification settings - Fork 489
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
GEP 1767: CORS Filter #3435
base: main
Are you sure you want to change the base?
GEP 1767: CORS Filter #3435
Conversation
Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: lianglli The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Hi @lianglli. Thanks for your PR. I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
Thanks for the energy here @liangli, but the correct process here is for you to add some discussion about this feature to the proposed set of Experimental changes in #3403, and then, if this CORS GEP is selected for an Experimental slot by the community's voting, this PR will be able to move forward. This is a useful feature that already has a GEP, has been discussed before, and is not too big, so it's reasonably likely that it will be included if we can free up enough Experimental slots by moving things to Standard. I note that you added this to the 1.2 Experimental discussion as well, so it's fine to just reuse the same description in the v1.3 scoping discussion there so that any new folks will have the context to vote. Until then, sadly, this PR will need to be on hold. /hold |
Thanks for writing up this GEP @lianglli! This made the cut for v1.3 release scoping, it's great to already have a GEP PR ready for review, will take a look shortly. /ok-to-test |
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.
Thanks for all your work on this @lianglli! This is incredibly detailed and well written. I took a first pass and left some initial comments.
geps/gep-1767/index.md
Outdated
// When responding to a credentialed requests, the gateway must specify | ||
// one or more HTTP headers in the value of the Access-Control-Allow-Headers response header, | ||
// instead of specifying the * wildcard. |
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.
What happens if a user has configured *
here? Should the Gateway implementation reject the request? Or do we just need to add CEL validation to prevent allowCredentials
being set to true
at the same time as this is set to *
?
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 you may be mixing up the user HTTPRoute config and the server's expected response. a user can legitimately set *
in the config certainly -- this seems to be saying they cannot set it in the response header.
However, I am not sure why? The spec does not seem to reject *
? https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers#syntax
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 had someone in ingress-nginx, request that we updated CORS to accept * and null
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.
Note that Envoy can't return *
directly because it treats *
specially:
https://github.com/envoyproxy/envoy/blob/6445d0dcdd33413e47454221fa80b3cfabea7d4e/source/extensions/filters/http/cors/cors_filter.cc#L159-L163.
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.
What happens if a user has configured
*
here? Should the Gateway implementation reject the request? Or do we just need to add CEL validation to preventallowCredentials
being set totrue
at the same time as this is set to*
?
When responding to a credentialed requests, the response headers Access-Control-Allow-Origin
, Access-Control-Expose-Headers
, Access-Control-Allow-Methods
and Access-Control-Allow-Headers
can NOT use *
as value.
However, if a HTTPCORSFilter sets AllowCredentials
as true and configures * for AllowHeaders
, the gateway will return HTTP response header Access-Control-Allow-Credentials: true
and Access-Control-Allow-Headers: *
to the "preflight" request based on the HTTPCORSFilter specifically.
Then, clients will NOT be able to send actual cross-origin 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.
Note that Envoy can't return
*
directly because it treats*
specially: https://github.com/envoyproxy/envoy/blob/6445d0dcdd33413e47454221fa80b3cfabea7d4e/source/extensions/filters/http/cors/cors_filter.cc#L159-L163.
Most gateways support the *
wildcard as the value of a HTTP header.
Moreover, *
wildcard is a valid character of header value based on rfc7230.
Hence, the envoy should support it in future.
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 field (and this spec in general) is pretty unclear as to which "user" we're talking about.
I suggest using different names for the HTTPRoute owner (maybe "user" is okay here), and the client, (should probably use that word instead).
So, I think what @robscott was saying is "What happens if the HTTPRoute owner has configured *
here?"
The text says:
// When responding to a credentialed requests, the gateway must specify
// one or more HTTP headers in the value of the Access-Control-Allow-Headers response header,
// instead of specifying the * wildcard.
What headers should the implementation respond with? Below, @spacewander suggests responding with whatever was sent in the Access-Control-Request-Headers
field in the OPTIONS request. Whatever we want to do MUST be defined in this field, as what to do in the other cases:
*
is configured in this field, andAccess-Control-Request-Headers
is not sent- One or more values are configured in this field and
Access-Control-Request-Headers
is sent - One or more values are configured in this field and
Access-Control-Request-Headers
is not sent
What we are aiming for is that someone who reads the spec should be able to understand enough to write conformance tests for the spec, ideally without looking at any other documents.
If the server doesn’t want to allow cross-origin access, it will respond with an error message to the client. | ||
|
||
## API | ||
This GEP proposes to add a new field `HTTPCORSFilter` to `HTTPRouteFilter`. |
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'm not 100% sure if this should be a filter vs a top level field in Routes similar to timeouts.
In almost all cases, filters modify a request or response (RequestHeaderModifier, ResponseHeaderModifier, RequestRedirect, URLRewrite). RequestMirror is a bit of an exception, but it's effectively creating a copy of a request and sending it somewhere else, so there's some overlap.
This admittedly can modify a request or response, so it could qualify as a filter, but it also feels conceptually similar to "timeouts" in that it's describing what kinds of requests are "valid" or "invalid".
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 don't have a strong preference but it does seem more filter-like. If we consider filters logically as a
fn run_filter(req: HTTPRequest) -> FilterResponse;
enum FilterResponse {
Request(HTTPRequest), // A request, maybe the same as the input or maybe modified
Response(HTTPResonse) // Respond directly
}
A timeout doesn't really meet that, but CORs does:
fn run_filter(req: HTTPRequest) -> FilterResponse {
if req.method == origin { return Response(cors_response()) }
return Request(req) // return the original request
}
(the function is not 100% correct in CORS semantics but close enough)
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.
CORS is a HTTP feature based on HTTP-header.
Hence, this GEP proposes to add a new field HTTPCORSFilter
to HTTPRouteFilter
.
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 this fits into a Filter pretty well.
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.
Seems like a pretty clear consensus that this fits as a filter, thanks for the responses here! @lianglli do you mind capturing some part of this thought process under a section like "Alternatives Considered" for future reference?
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.
Seems like a pretty clear consensus that this fits as a filter, thanks for the responses here! @lianglli do you mind capturing some part of this thought process under a section like "Alternatives Considered" for future reference?
ok.
// MaxAge indicates the duration (in seconds) for the client to cache | ||
// the results of a "preflight" request. | ||
// | ||
// The default value of header Access-Control-Max-Age is 5 (seconds). |
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.
Why have a default here at all? This seems like it could be surprising to users unless it's common for implementations to have a default.
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.
Based on Fetch Living Standard, the default value of Access-Control-Max-Age is 5 (seconds).
If a HTTPCORSFilter does NOT set 'MaxAge' specifically, the gateway will return the header "Access-Control-Max-Age: 5" to clients by default.
For example, the default value of Access-Control-Max-Age for Ingress-NGINX Enable CORS
is 1728000 seconds.
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 question I have: if unset, should the server respond Access-Control-Max-Age: 5
, or should the server not include anything and let the client decide (where it will likely default to 5s)?
Envoy prior art is "not include"
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.
Since the default value for MDN is 5 seconds, I think it's good to set an explicit default here: in case the gateway does not include such a header, it's up to the client such a decision, which is never a good thing in my opinion. Setting a default here makes the whole thing explicit and always defined.
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.
If the default is 'unset', we support all cases: if a user wants it to be unset (and let client decide) they can, and if a user wants it set they can explicitly set it.
Not sure how valid this is though
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.
Thanks for the GEP -- top notch details!
Credentials are cookies, TLS client certificates, or authentication headers containing a username and password. | ||
|
||
After the server has permitted the CORS "preflight" request, the client will be able to send actual cross-origin request. | ||
If the server doesn’t want to allow cross-origin access, it will respond with an error message to the client. |
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.
Is this accurate? AFAIK CORS is a client side security mechanism -- the thing that would reject something is the browser
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.
Which is a common mistake when testing CORS; someone would use curl
and expect it to fail but it works fine since curl does not implement CORS
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.
Which is a common mistake when testing CORS; someone would use
curl
and expect it to fail but it works fine since curl does not implement CORS
All major browsers support CORS.
However, some web application also support CORS.
For example, a web application is able to use CORS based on Apache Pekko HTTP’s cors module or ASP.NET Core.
Hence, when referring to "client" in the context of CORS, it means the web browser or applications and services inherently support Cross-Origin Resource Sharing (CORS).
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.
Is this accurate? AFAIK CORS is a client side security mechanism -- the thing that would reject something is the browser
Based on CORS spec,
for a successful HTTP response to a CORS-preflight request, it is restricted to an ok status, e.g., 200 or 204.
Any other kind of HTTP response is not successful and will either end up not being shared or fail the CORS-preflight request. Be aware that any work the server performs might nonetheless leak through side channels, such as timing. If server developers wish to denote this explicitly, the 403 status can be used, coupled with omitting the relevant headers.
Alternatively, if the server doesn’t want to allow cross-origin access, it will respond with an error message to the client.
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 don't think that is accurate. That says "anything except 200 or 204 means it failed", but it doesn't mean if the Cross origin request is not supported it should return a 403. Just that if it happened to do so it would be treated as failed.
I have never seen a server respond with a 403 from a CORS 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.
The specification for CORS is included as part of the WHATWG's Fetch Living Standard.
Yes, it is. The 403 is not mandatory for fail the CORS-preflight request.
For most Gateway implementation, we always return 204/200 to the the CORS-preflight request even if the request Origin
dos not match the configured allowed origins.
Alternatively, If CORS is not enabled or no CORS rule matches the preflight request, the server responds with status code 403 (Forbidden) to the CORS-preflight 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.
My concern is we are taking a very weak statement "If you wish you can return a 403" and saying "you must return a 403".
If the server doesn’t want to allow cross-origin access, it will respond with an error message to the client. | ||
|
||
## API | ||
This GEP proposes to add a new field `HTTPCORSFilter` to `HTTPRouteFilter`. |
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 don't have a strong preference but it does seem more filter-like. If we consider filters logically as a
fn run_filter(req: HTTPRequest) -> FilterResponse;
enum FilterResponse {
Request(HTTPRequest), // A request, maybe the same as the input or maybe modified
Response(HTTPResonse) // Respond directly
}
A timeout doesn't really meet that, but CORs does:
fn run_filter(req: HTTPRequest) -> FilterResponse {
if req.method == origin { return Response(cors_response()) }
return Request(req) // return the original request
}
(the function is not 100% correct in CORS semantics but close enough)
geps/gep-1767/index.md
Outdated
// | ||
// The status code of a successful response to a "preflight" request is an OK status (i.e., 204 or 200). | ||
// For the "preflight" request, if the request `Origin` dos not match the configured allowed origins, | ||
// the gateway will return a response with error status (e.g., 403). |
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 don't think this is how CORS works generally. Or, at the very least, not how Envoy's CORS filter works which is what I am familiar with so I will desribe that.
In envoy, if a request is a CORS request then there will be a direct response from the CORS filter. A "CORS request" means:
- method is OPTIONS
- "origin" header is not empty
- "Access-Control-Request-Method" is not empty"
If it meets this criteria, it will always return a 200 with the various CORS response headers. If its not allowed, it will be rejected by the browser -- NOT the server.
If it doesn't meet the criteria, the request is just passed through
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.
If it doesn't meet the criteria, the request is just passed through
Note that Envoy Gateway has changed this default behavior in envoyproxy/gateway#3002 to "aligns more closely with users' intuition." CC @zhaohuabing
Personally, I would vote for returning an OK status, which is chosen by some Nginx implementations like Kong.
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.
+1. The OPTIONS pre-flight response status should always be 200 or 204 (no content) and the actual result of the pre-flight should be included in the various 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.
Before the actual cross-origin requests, a client sends a cross-origin "preflight" request for asking a server whether it would allow an actual cross-origin request.
Based on CORS spec of Fetch Living Standard, the status code of a successful response to a "preflight" request is an OK status (i.e., 204 or 200).
Any other kind of HTTP response is not successful and will either end up not being shared or fail the CORS-preflight request. Be aware that any work the server performs might nonetheless leak through side channels, such as timing. If server developers wish to denote this explicitly, the 403 status can be used, coupled with omitting the relevant headers.
In the Gateway API context, the server is a Gateway implementation.
A "preflight" response will be generated by the Gateway. Moreover, the gateway will send the "preflight" response to the client directly.
Hence, for the "preflight" request, if the request Origin
does not match the configured allowed origins, alternatively, the Gateway will respond with an error message 403 to the client and omit the relevant CORS response headers.
geps/gep-1767/index.md
Outdated
// When responding to a credentialed requests, the gateway must specify | ||
// one or more HTTP headers in the value of the Access-Control-Allow-Headers response header, | ||
// instead of specifying the * wildcard. |
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 you may be mixing up the user HTTPRoute config and the server's expected response. a user can legitimately set *
in the config certainly -- this seems to be saying they cannot set it in the response header.
However, I am not sure why? The spec does not seem to reject *
? https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers#syntax
// MaxAge indicates the duration (in seconds) for the client to cache | ||
// the results of a "preflight" request. | ||
// | ||
// The default value of header Access-Control-Max-Age is 5 (seconds). |
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 question I have: if unset, should the server respond Access-Control-Max-Age: 5
, or should the server not include anything and let the client decide (where it will likely default to 5s)?
Envoy prior art is "not include"
// Origin: https://foo.example | ||
// | ||
// Config: | ||
// allowOrigins: ["https://foo.example", "http://foo.example", "https://test.example", "http://test.example"] |
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 would recommend using regex to specify the matched Origins. Otherwise, there is no way to configure it to match any subdomain of a given domain, or any port of a given domain.
Also, it would be better no export the *
wildcard directly, so the user doesn't need to study what a credentialed request means. She can use the .*
regex instead.
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 would recommend using regex to specify the matched Origins. Otherwise, there is no way to configure it to match any subdomain of a given domain, or any port of a given domain.
Also, it would be better no export the
*
wildcard directly, so the user doesn't need to study what a credentialed request means. She can use the.*
regex instead.
The type of AllowOrigins is a list of string.
Based on CORS spec, the valid origins include wildcard "*".
wildcard = "*"
Access-Control-Allow-Origin = origin-or-null / wildcard
An origin consists of three parts: the scheme, host and port.
An HTTP host can be a wildcard, meaning you can use an asterisk () in the hostname to match any subdomain within a domain.
For example:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: http-route-cors
spec:
hostnames:
- http.route.cors.com
parentRefs:
- group: gateway.networking.k8s.io
kind: Gateway
name: http-gateway
rules:
- backendRefs:
- kind: Service
name: http-route-cors
port: 80
matches:
- path:
type: PathPrefix
value: /resource/foo
filters:
- cors:
- allowOrigins:
- http://*.foo.example
- https://*.foo.example
allowMethods:
- GET
- PUT
- POST
- DELETE
- PATCH
- OPTIONS
allowHeaders:
- DNT
- X-CustomHeader
- Keep-Alive
- User-Agent
- X-Requested-With
- If-Modified-Since
- Cache-Control
- Content-Type
- Authorization
exposeHeaders:
- Content-Security-Policy
maxAge: 1728000
type: CORS
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 some suggested wording about possible formats for this field up above, which I think should make this clearer.
If that suggestion is okay, I'd suggest also adding an example to this godoc that shows how an Origin with scheme plus wildcarded host would work (as in the example @lianglli posted just above).
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
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 host part of the origin may contain the wildcard character
*
.
According to https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin,
Limiting the possible Access-Control-Allow-Origin values to a set of allowed origins requires code on the server side to check the value of the Origin request header, compare that to a list of allowed origins, and then if the Origin value is in the list, set the Access-Control-Allow-Origin value to the same value as the Origin value.
the Access-Control-Allow-Origin should return the Origin sent by the client. So if the client sends "a.com", the server can't reply with "*.com".
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 it again, maybe you mean "if configured with '*.com', the server should return ‘a.com’ when the client sends 'a.com' and 'b.com' when the client sends 'b.com'"?
What's the advantage of this wildcard host match expression?
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.
To be honest, I don't know. I'm just going by trying to clarify what I understand of the language - I haven't done a lot of work with CORS though.
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 it again, maybe you mean "if configured with '*.com', the server should return ‘a.com’ when the client sends 'a.com' and 'b.com' when the client sends 'b.com'"?
What's the advantage of this wildcard host match expression?
The following var $http_origin is the request header 'origin'.
For a simple cross-origin interaction, the gateway sets value of the header
Access-Control-Allow-Origin same as the Origin
header provided by the client. However, if AllowOrigins
has the *
wildcard, the gateway will return a response header Access-Control-Allow-Origin: *
.
if (allowOrigins has "*") {
set $http_origin "*";
}
more_set_headers 'Access-Control-Allow-Origin: $http_origin';
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.
Another example in the godoc here would serve to clarify this behavior, and remove any ambiguity.
geps/gep-1767/index.md
Outdated
// | ||
// The status code of a successful response to a "preflight" request is an OK status (i.e., 204 or 200). | ||
// For the "preflight" request, if the request `Origin` dos not match the configured allowed origins, | ||
// the gateway will return a response with error status (e.g., 403). |
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.
If it doesn't meet the criteria, the request is just passed through
Note that Envoy Gateway has changed this default behavior in envoyproxy/gateway#3002 to "aligns more closely with users' intuition." CC @zhaohuabing
Personally, I would vote for returning an OK status, which is chosen by some Nginx implementations like Kong.
// | ||
// Support: Extended | ||
// | ||
// +optional |
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.
As said in this proposal:
The server response for the CORS "preflight" request include the following headers:
Access-Control-Allow-Origin
response header indicates whether the response can be shared with requested resource from the givenOrigin
.
Access-Control-Allow-Methods
response header specifies one or more HTTP methods are accepted by the server when accessing the requested resource.
Access-Control-Allow-Headers
response header indicates which HTTP headers can be used during the actual cross-origin request.
Access-Control-Allow-Methods is not an optional response header. So we can't make it optional unless we provide a good default value. I have created a similar issue to Envoy before: envoyproxy/envoy#37533.
I would recommend echoing back the Access-Control-Request-Method
request header instead of *
, so the user doesn't need to study what a credentialed request means.
By the way, Envoy can't configure plain *
because it treats the *
specially.
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.
Using wildcard or regular expressions may create vulnerabilities.
However, based on the CORS spec of Fetch Living Standard, the wildcard "*" is a valid value of header ccess-Control-Allow-Origin.
wildcard = "*"
Access-Control-Allow-Origin = origin-or-null / wildcard
The Envoy should supports wildcard "*" in the CORS filter specifically.
Credentials is important for the CORS protocol.
Most gateway (E.g., Istio, Ingress-NGINX and etc.) supports Credential.
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 the point that @spacewander is making here is that, if we make this field +optional
, then we need to include some default value in this spec, because the config itself is not optional when an implementation is responding - the Gateway implementation must return something, so we should be configuring that in this object, rather than having some implicit behavior.
I think that this field should instead be +required
(or have no +optional
tag, which is the same thing), as setting any default could be insecure depending on the user. So it's better to force users of this config to have to specify something here.
geps/gep-1767/index.md
Outdated
// When responding to a credentialed requests, the gateway must specify | ||
// one or more HTTP headers in the value of the Access-Control-Allow-Headers response header, | ||
// instead of specifying the * wildcard. |
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.
Note that Envoy can't return *
directly because it treats *
specially:
https://github.com/envoyproxy/envoy/blob/6445d0dcdd33413e47454221fa80b3cfabea7d4e/source/extensions/filters/http/cors/cors_filter.cc#L159-L163.
// | ||
// Support: Extended | ||
// | ||
// +optional |
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.
As MDN mentions,
This header is required if the preflight request contains Access-Control-Request-Headers
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
I would recommend echoing back the Access-Control-Request-Headers
request header by default, so the user doesn't need to study what a credentialed request means.
A CORS request is an HTTP request that includes an `Origin` header. | ||
An origin consists of three parts: the scheme, host and port. Two URLs have the same origin if they have the same scheme, host, and port. |
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 is a great introduction! One thing I see implicit here is the default port for the schema: in the examples below, you outline how http://example.com:80
is equal to http://example.com
and how http://example.com
is different from https://example.com:80
, which is great. I think we could benefit from explicitly stating here how ports can be implicitly bound to the scheme.
geps/gep-1767/index.md
Outdated
// | ||
// The status code of a successful response to a "preflight" request is an OK status (i.e., 204 or 200). | ||
// For the "preflight" request, if the request `Origin` dos not match the configured allowed origins, | ||
// the gateway will return a response with error status (e.g., 403). |
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.
+1. The OPTIONS pre-flight response status should always be 200 or 204 (no content) and the actual result of the pre-flight should be included in the various headers.
// MaxAge indicates the duration (in seconds) for the client to cache | ||
// the results of a "preflight" request. | ||
// | ||
// The default value of header Access-Control-Max-Age is 5 (seconds). |
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.
Since the default value for MDN is 5 seconds, I think it's good to set an explicit default here: in case the gateway does not include such a header, it's up to the client such a decision, which is never a good thing in my opinion. Setting a default here makes the whole thing explicit and always defined.
geps/gep-1767/metadata.yaml
Outdated
kind: GEPDetails | ||
number: 1767 | ||
name: CORS Filter | ||
status: Experimental |
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.
Since we are introducing the GEP with this PR, and it aims at becoming experimental in the next release, we should set provisional
here.
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.
provisional
OK, I get it.
CORS GEP updates based on the review comments. Co-authored-by: Mattia Lavacca <[email protected]>
// AllowCredentials indicates whether the actual cross-origin request | ||
// allows to include credentials. | ||
// | ||
// The only valid value for the header `Access-Control-Allow-Credentials` is true (case-sensitive). |
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 not how boolean serialization works for YAML. YAML accepts truthy values like true
, True
, On
, etc for true
, and similar ones for false
.
If we want to specify that this can only be true
, or false
, precisely, then this should be a string enumerated field with constants for true
and false
.
Also, if we do stick with boolean, we need to be sure that we won't want to add any other values later, because once this goes in, we can't change the type later to string (which we would need to). It doesn't seem likely since this is implementing a defined-elsewhere boolean field, but it's worth noting.
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 should also include a mention that requests with AllowCredentials
set to true
are referred to as credentialled requests
in other parts of the spec.
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 get it.
// +optional | ||
// +listType=set | ||
// +kubebuilder:validation:MaxItems=16 | ||
AllowMethods []string `json:"allowMethods,omitempty"` |
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 should be an aliased string type, with included constants for all the valid HTTP methods, and should have kubebuilder Enum tags that set the permitted values. That will ensure that users will get immediate feedback if they provide an invalid value.
geps/gep-1767/index.md
Outdated
// ExposeHeaders indicates which HTTP response headers are exposed to clients | ||
// for the cross-origin request that is not a "preflight" request. | ||
// Header names are not case sensitive. | ||
// | ||
// Config: | ||
// exposeHeaders: ["Content-Security-Policy"] | ||
// Output: | ||
// Access-Control-Expose-Headers: Content-Security-Policy | ||
// | ||
// A wildcard indicates that the responses with all HTTP headers are exposed to clients. | ||
// When responding to a credentialed requests, the gateway must specify | ||
// one or more HTTP headers in the value of the Access-Control-Expose-Headers response header, | ||
// instead of specifying the * wildcard. |
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.
Does "exposed to clients" mean "returned as headers in requests"? Seems like it, but if that's the case, that should be said specifically here. Once we define that, then we can use "exposed to clients" and "returned as headers" interchangeably.
Access-Control-Expose-Headers: Content-Security-Policy | ||
``` | ||
|
||
## Prior Art |
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 section really should be before the API section and after the Introduction section. I think we may need a template update.
Co-authored-by: Nick Young <[email protected]>
CORS GEP updates based on the review comments. Co-authored-by: Mattia Lavacca <[email protected]>
…igins and AllowMethods
…igins and AllowMethods
@lianglli: The following test failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here. |
What type of PR is this?
/kind gep
What this PR does / why we need it:
This GEP proposes to add a new field
HTTPCORSFilter
toHTTPRouteFilter
.Which issue(s) this PR fixes:
Fixes #1767
Does this PR introduce a user-facing change?: