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

GEP 1767: CORS Filter #3435

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open

Conversation

lianglli
Copy link

@lianglli lianglli commented Nov 5, 2024

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 to HTTPRouteFilter.

Which issue(s) this PR fixes:
Fixes #1767

Does this PR introduce a user-facing change?:

NONE

@k8s-ci-robot k8s-ci-robot added kind/gep PRs related to Gateway Enhancement Proposal(GEP) do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Nov 5, 2024
@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Nov 5, 2024
@k8s-ci-robot
Copy link
Contributor

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 /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

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.

@k8s-ci-robot k8s-ci-robot added needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Nov 5, 2024
@youngnick
Copy link
Contributor

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

@robscott
Copy link
Member

robscott commented Dec 6, 2024

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

@k8s-ci-robot k8s-ci-robot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Dec 6, 2024
Copy link
Member

@robscott robscott left a 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.

/cc @strongjz @howardjohn @youngnick

Comment on lines 187 to 189
// 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.
Copy link
Member

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 *?

Copy link
Contributor

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

Copy link

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

kubernetes/ingress-nginx#12402

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

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 *?

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.

Copy link
Author

@lianglli lianglli Dec 10, 2024

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.

Copy link
Contributor

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, and Access-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.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    // A wildcard indicates that the requests with all HTTP headers are allowed.
    // The Access-Control-Allow-Headers response header can only use `*` wildcard 
    // as value when the field AllowCredentials is unspecified.
    //
    // Input:
    //   Access-Control-Request-Headers: Content-Type, Cache-Control
    //
    // Config:
    //   allowHeaders: ["*"]
    //
    // Output:
    //   Access-Control-Allow-Headers: *
    //
    // When the field AllowCredentials is specified, 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. The value of the 
    // header Access-Control-Allow-Headers same as the Access-Control-Request-Headers 
    // header provided by the client.
    //
    // Input:
    //   Access-Control-Request-Headers: Content-Type, Cache-Control
    //
    // Config:
    //   allowHeaders: ["*"]
    //   allowCredentials: true
    //
    // Output:
    //   Access-Control-Allow-Headers: Content-Type, Cache-Control 
    //   Access-Control-Allow-Credentials: true

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • 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
    // Multiple header names in the value of the Access-Control-Allow-Headers 
    // response header are separated by a comma (",").
    //
    // Config:
    //   allowHeaders: ["DNT", "Keep-Alive", "User-Agent", "X-Requested-With", "If-Modified-Since", "Cache-Control", "Content-Type", "Range", "Authorization"]
    //
    // Output:
    //   Access-Control-Allow-Headers: DNT, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Range, Authorization

If one or more values are configured in this field AllowHeaders, the gateway always return response header 'Access-Control-Allow-Headers: $allowHeaders' , regardless of whether the request includes the header Access-Control-Request-Headers.

geps/gep-1767/index.md Show resolved Hide resolved
// 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).
Copy link
Member

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.

Copy link
Author

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.

Copy link
Contributor

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"

Copy link
Member

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.

Copy link
Contributor

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

@robscott robscott added this to the v1.3.0 milestone Dec 7, 2024
Copy link
Contributor

@howardjohn howardjohn left a 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!

geps/gep-1767/index.md Outdated Show resolved Hide resolved
geps/gep-1767/index.md Show resolved Hide resolved
geps/gep-1767/index.md Outdated Show resolved Hide resolved
Comment on lines 187 to 189
// 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.
Copy link
Contributor

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).
Copy link
Contributor

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"

geps/gep-1767/index.md Outdated Show resolved Hide resolved
geps/gep-1767/index.md Outdated Show resolved Hide resolved
geps/gep-1767/index.md Outdated Show resolved Hide resolved
Comment on lines 187 to 189
// 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

geps/gep-1767/index.md Outdated Show resolved Hide resolved
geps/gep-1767/index.md Outdated Show resolved Hide resolved
geps/gep-1767/index.md Outdated Show resolved Hide resolved
geps/gep-1767/index.md Show resolved Hide resolved
geps/gep-1767/index.md Outdated Show resolved Hide resolved
// 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).
Copy link
Member

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 Show resolved Hide resolved
geps/gep-1767/index.md Outdated Show resolved Hide resolved
geps/gep-1767/index.md Outdated Show resolved Hide resolved
geps/gep-1767/index.md Outdated Show resolved Hide resolved
geps/gep-1767/index.md Outdated Show resolved Hide resolved
…ontrol-Allow-Methods, Access-Control-Allow-Headers and Access-Control-Expose-Headers are separated by a comma
…ds, Access-Control-Allow-Headers and Access-Control-Expose-Headers
@k8s-ci-robot k8s-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Jan 27, 2025
@robscott
Copy link
Member

Thanks for all the work on this @lianglli!

/lgtm
/approve
/release-note-none

Although I reopened a couple comment threads that I think could use a bit more discussion, they're clearly small enough to cover when we get into the next phase of the enhancement process with additions to API types.

@k8s-ci-robot k8s-ci-robot added release-note-none Denotes a PR that doesn't merit a release note. and removed do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. labels Jan 31, 2025
@k8s-ci-robot k8s-ci-robot added lgtm "Looks good to me", indicates that a PR is ready to be merged. approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Jan 31, 2025
//
// If any header name in the `Access-Control-Request-Headers` request header
// is not included in the list of header names specified by the response header
// `Access-Control-Allow-Methods`, it will present an error on the client side.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and next paragraph, I think the Access-Control-Allow-Headers and Access-Control-Allow_methods headers are mixed up.

Copy link
Member

@mlavacca mlavacca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work on this, @lianglli!

/approve

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: lianglli, mlavacca, robscott

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@robscott
Copy link
Member

Thanks for all the work on this @lianglli!

I'll hold off on merging this until the most recent comment has been resolved, but IMO this is sufficiently close to keep it in the v1.3 milestone and on to the next phase.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. kind/gep PRs related to Gateway Enhancement Proposal(GEP) lgtm "Looks good to me", indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. release-note-none Denotes a PR that doesn't merit a release note. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

GEP: Add support for CORS
10 participants