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

fix(cors): avoid overwriting Access-Control-Expose-Headers response header #11136

Merged
merged 8 commits into from
Apr 23, 2024

Conversation

VanLiuZhi
Copy link
Contributor

@VanLiuZhi VanLiuZhi commented Apr 9, 2024

1. Problem Description

I used version APISIX 3.5 before and found that the code for the expose_headers setting in the core plugin is incorrect, which has not been addressed in the latest branch. This PR is intended to rectify this issue.

The original relevant code is as follows:

expose_headers = {
    description =
        "you can use '*' to expose all headers when no credentials," ..
        "'**' to allow forcefully (it will bring some security risks, be careful)," ..
        "multiple headers use ',' to split. default: *.",
    type = "string",
    default = "*"
},

core.response.set_header("Access-Control-Expose-Headers", conf.expose_headers)

This code describes the expose_headers configuration, suggesting that setting it to ** would expose all headers, but this is incorrect. The HTTP protocol does not have such a specification. You can refer to the description on MDN:

https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers

* (wildcard)
The value "*" only counts as a special wildcard value for requests without credentials
 (requests without HTTP cookies or HTTP authentication information). 
In requests with credentials, it is treated as the literal header name "*".

So ** is the wrong description and configuration, and setting it to * is also conditional.

2. Impact

If I enable this plugin globally and set allow_methods, allow_origins, and other configurations, not setting expose_headers using the default value * or setting ** will cause problems because the plugin's request header will override the Access-Control-Expose-Headers set by the upstream service, causing the upstream service to be unable to expose request headers.

I can only hard-code the:

expose_headers: Content-Encoding, My-Revision, My-Ikun

This is unreasonable. If my upstream service needs to expose a new header Project-App, I have to configure it, and let the plugin configure it as:

expose_headers: Content-Encoding, My-Revision, My-Ikun, Project-App

3. Recommended Solution

In most cases, expose_headers does not need to be set globally and can be managed by the upstream service.

I have used Kong before, and the strategy of the Kong core plugin is to remove Access-Control-Expose-Headers from the response header if the user does not configure it.

I suggest adopting a similar approach where the core plugin does not add Access-Control-Expose-Headers to the response header if the user does not configure it. This allows the upstream service to control the exposure of headers independently.

So I made changes to the code:

expose_headers = {
    description =
            "multiple header use ',' to split." ..
            "If not specified, no custom headers are exposed.",
    type = "string"
},

if conf.expose_headers ~= nil and conf.expose_headers ~= "" then
    core.response.set_header("Access-Control-Expose-Headers", conf.expose_headers)
end
  • The plugin will modify the Access-Control-Expose-Headers header and override the upstream service's configuration only when a value is explicitly set by the user.

  • If no value is set, the plugin will not modify the header, allowing the upstream service to expose its own headers as needed.

Checklist

  • [ ✔] I have explained the need for this PR and the problem it solves
  • [✔ ] I have explained the changes or the new features added to this PR
  • [✔ ] I have added tests corresponding to this change
  • [✔ ] I have updated the documentation to reflect this change
  • [✔ ] I have verified that this change is backward compatible (If not, please discuss on the APISIX mailing list first)

@shreemaan-abhishek
Copy link
Contributor

can this whole problem be avoided if configuring the value as ** is avoided?

@shreemaan-abhishek
Copy link
Contributor

you will also have to support this fix with test cases.

@VanLiuZhi
Copy link
Contributor Author

VanLiuZhi commented Apr 12, 2024

can this whole problem be avoided if configuring the value as ** is avoided?

Cannot set **. In fact, most of the core-related configurations are not supported to be set ** in the HTTP protocol. The setting of ** is handled by the APISIX code. I guess the expose_headers part was not paid attention to during development.

Our requirement is for the upstream service to customize the Access-Control-Expose-Headers, which can be read by the browser or client. To meet this requirement, the core plugin can only allow all Access-Control-Expose-Headers.

However, setting Access-Control-Expose-Headers: ** is invalid in the HTTP protocol. Setting Access-Control-Expose-Headers: * only works when credentials are not passed, so removing the response header is the best practice.

@VanLiuZhi
Copy link
Contributor Author

VanLiuZhi commented Apr 12, 2024

you will also have to support this fix with test cases.

I submitted a test case to verify whether the removal of expose_headers is effective when no value is set.

@shreemaan-abhishek
Copy link
Contributor

@ShiningRush was it intentional (by design) to allow setting ** for allow_origins?

@ShiningRush
Copy link
Contributor

@ShiningRush was it intentional (by design) to allow setting ** for allow_origins?

Yes, ** is designed for allow_origins and allow_headers first. But I don't know why the expose_header also supports this now.
I think ** is equal to empty string when it handles expose_headers, so @VanLiuZhi 's PR is LGTM.

@VanLiuZhi
Copy link
Contributor Author

@ShiningRush Yes, ** is designed for allow_origins and allow_headers first. But I don't know why the expose_header also supports this now. I think ** is equal to empty string when it handles expose_headers, so @VanLiuZhi 's PR is LGTM.

Thank you! I have noticed that since the default values were removed, many of the previous test cases have failed. Do I need to modify these test cases?

@shreemaan-abhishek
Copy link
Contributor

okay, makes sense now. Sorry for the hassle, I am not much familiar with cors 😂 . But your PR does two things:

  • removes comment about ** (but infact, user can still configure ** as the value for expose_headers but it they do it will be their problem
  • removes * as the default value.

so now the default behaviour is that if this field is not configured, Access-Control-Expose-Headers will remain unaffected.

Please consider this and update the PR title and add more details in the comment/docs about the changing behaviour.

@VanLiuZhi VanLiuZhi changed the title fix(cors): expose_headers cannot be set to ** fix(cors): when expose_headers is set to **, allowing any header becomes ineffective, which will prevent the upstream service from exposing its own headers Apr 17, 2024
@VanLiuZhi
Copy link
Contributor Author

VanLiuZhi commented Apr 17, 2024

okay, makes sense now. Sorry for the hassle, I am not much familiar with cors 😂 . But your PR does two things:

  • removes comment about ** (but infact, user can still configure ** as the value for expose_headers but it they do it will be their problem
  • removes * as the default value.

so now the default behaviour is that if this field is not configured, Access-Control-Expose-Headers will remain unaffected.

Please consider this and update the PR title and add more details in the comment/docs about the changing behaviour.

@shreemaan-abhishek 👌Thank you for your correction. I have updated the PR description and title. The documentation related to CORS has been revised.

@shreemaan-abhishek shreemaan-abhishek changed the title fix(cors): when expose_headers is set to **, allowing any header becomes ineffective, which will prevent the upstream service from exposing its own headers fix(cors): avoid overwriting Access-Control-Expose-Headers response header Apr 18, 2024
docs/en/latest/plugins/cors.md Outdated Show resolved Hide resolved
docs/zh/latest/plugins/cors.md Outdated Show resolved Hide resolved
@shreemaan-abhishek shreemaan-abhishek merged commit 53661ea into apache:master Apr 23, 2024
48 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants