apisix icon indicating copy to clipboard operation
apisix copied to clipboard

bug: plugin:cors: config expose_headers using "**" has no handle when allow_credential set true

Open nthsky opened this issue 2 years ago • 25 comments

Current Behavior

Background: plugin cors enabled, allow_credential setting true Goal: expose all headers to frontend detail: According to the documention, the config expose_headers of plugin cors set to '**', but it does not work. Then I read the code of cors plugin finding no handle with '**' of expose_headers

https://github.com/apache/apisix/blob/master/apisix/plugins/cors.lua#L186-L241

Expected Behavior

expose all headers

Error Logs

No response

Steps to Reproduce

enable plugin cors with allow_credential config true and expose_headers true

Environment

  • APISIX version (run apisix version): apache/apisix:2.14.1-alpine
  • Operating system (run uname -a): Ubuntu 20.04.3 with docker
  • OpenResty / Nginx version (run openresty -V or nginx -V): nginx version: openresty/1.19.9.1

theoretically all version

nthsky avatar Jun 23 '22 07:06 nthsky

We may check if the conf.expose_headers exists then set this header.

tokers avatar Jun 23 '22 13:06 tokers

if we set Access-Control-Allow-Credentials to true, we cannot use '*' to allow all for the other attributes, like Access-Control-Expose-Headers?

so response headers like:

Access-Control-Expose-Headers: *
Access-Control-Allow-Credentials: true

is not available.

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

tzssangglass avatar Jun 24 '22 05:06 tzssangglass

@tzssangglass I think @nthsky wants to hide the Access-Control-Allow-Credentials header?

tokers avatar Jun 24 '22 10:06 tokers

@tzssangglass @tokers I use '**' not '*' recording to the document to expose all headers.(Sorry I may not have described it clearly before, the '**' is rendered in bold) The problem is it doesn't work. And I read the code of plugin finding the plugin does not handle '**' of expose_headers. I wanna know whether it's the document's problem(the plugin will not handle '**'). Or maybe it will be implemented in some version?

nthsky avatar Jun 27 '22 06:06 nthsky

@nthsky This is really supported and ** is just treated as normal values (at the code base level).

Could you provide the reproduce steps?

tokers avatar Jun 27 '22 09:06 tokers

I mean it's being treated as a normal value now. But I think it's expected to expose all headers when using '**', just like the other options, for example: reproduce steps:

  1. enable then cors plugin
  2. plugin config is like:
"cors": {
      "allow_credential": true,
      "allow_headers": "**",
      "allow_methods": "**",
      "allow_origins": "**",
      "expose_headers": "**",
      "disable": false,
      "max_age": 5
    }
  1. start a http server and a frontend web service, here I use gin for backend and axios for frontend. And the http server is behind apisix on port 9080

backend code:

	r.POST("/t", func(c *gin.Context) {
		c.Header("cool", "cooler")
		resp := make(gin.H)
		for k, v := range c.Request.Header {
			resp[k] = v
		}
		c.JSON(200, resp)
	})

frontend code:

    let url = "http://localhost:9080/t"
    axios({
        method: "post",
        url: url,
        withCredentials: true,
        headers: {
            "Authorization": "xxxxxxxx",
        },
    }).then(res => {
        console.log(res)
    })
  1. result is like:
{
  "data": {
  "Authorization": ["xxxxx"]
  ......
  },
  "headers": {
      "content-length": "1065",
      "content-type": "application/json; charset=utf-8"
  }
}

recording to the result, the request header authorization is passed through when allow_headers is '**' but the response header cool is not exposed though the expose_headers is set to '**'. Recoding to the document I think It is expected to exposed all headers when expose_headers is '**'.

nthsky avatar Jul 07 '22 04:07 nthsky

Recoding to the document I think It is expected to exposed all headers when expose_headers is '**'.

Are you saying that the backend should be able to get the header cool: cooler in the client request under the condition "expose_headers": "**"?

My reproduction steps are as follows

  1. upstream is OpenResty, conf:
master_process on;

worker_processes 2;

error_log logs/error.log warn;
pid logs/nginx.pid;

worker_rlimit_nofile 20480;

events {
    accept_mutex off;
    worker_connections 10620;
}

worker_shutdown_timeout 3;

http {
    server {
        listen 1980;

        access_log off;
        location / {
            content_by_lua_block {
                local get_headers = ngx.req.get_headers
                for k, v in pairs(get_headers(100)) do
                   ngx.say(k .. ": " .. v)
                end
            }
        }
    }
}

this prints all the headers of the request.

  1. config route on APISIX
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "plugins": {
        "cors": {
            "allow_origins": "**",
            "allow_methods": "**",
            "allow_headers": "**",
            "expose_headers": "**",
            "madx_age": 5,
            "allow_credential": true
        }
    },
    "uri": "/hello",
    "upstream": {
        "scheme": "grpc",
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:1980": 1
        }
    }
}'

  1. test
curl http://127.0.0.1:1980/get -H "Foo: Bar" -H "Authorization: Basic Zm9vOmZvbwo="
host: 127.0.0.1:1980
accept: */*
foo: Bar
user-agent: curl/7.29.0
authorization: Basic Zm9vOmZvbwo

Foo:Bar has been passed to the backend.

tzssangglass avatar Jul 08 '22 03:07 tzssangglass

The Access-Control-Expose-Headers response header allows a server to indicate which response headers should be made available to scripts running in the browser, in response to a cross-origin request.

The header cool: cooler is response from backend to frontend .I mean the header cool: cooler should be available to the frontend.

nthsky avatar Jul 08 '22 04:07 nthsky

The header cool: cooler is response from backend to frontend .I mean the header cool: cooler should be available to the frontend.

the backend server should set cool: cooler ?

tzssangglass avatar Jul 09 '22 08:07 tzssangglass

backend code:

	r.POST("/t", func(c *gin.Context) {
		c.Header("cool", "cooler")
		resp := make(gin.H)
		for k, v := range c.Request.Header {
			resp[k] = v
		}
		c.JSON(200, resp)
	})

backend server has set cool: cooler

nthsky avatar Jul 10 '22 03:07 nthsky

  1. upstream conf
master_process on;

worker_processes 2;

error_log logs/error.log warn;
pid logs/nginx.pid;

worker_rlimit_nofile 20480;

events {
    accept_mutex off;
    worker_connections 10620;
}

worker_shutdown_timeout 3;

http {
    server {
        listen 1980;

        access_log off;
        location / {
            content_by_lua_block {
                ngx.header["Foo"] = "Bar"
            }
        }
    }
}

test upstream

$ curl http://127.0.0.1:1980/hello -i
HTTP/1.1 200 OK
Server: openresty/1.21.4.1
Date: Mon, 11 Jul 2022 02:01:35 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
Foo: Bar
  1. route
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "plugins": {
        "cors": {
            "allow_origins": "**",
            "allow_methods": "**",
            "allow_headers": "**",
            "expose_headers": "**",
            "madx_age": 5,
            "allow_credential": true
        }
    },
    "uri": "/hello",
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:1980": 1
        }
    }
}'
  1. test cors plugin
curl http://127.0.0.1:9080/hello -i
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Date: Mon, 11 Jul 2022 02:02:26 GMT
Foo: Bar
Server: APISIX/2.14.1
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE
Access-Control-Max-Age: 5
Access-Control-Expose-Headers: **
Access-Control-Allow-Credentials: true

get Foo: Bar response header

tzssangglass avatar Jul 11 '22 02:07 tzssangglass

It's the cross domain problem and curl can not explain problem. You can try following code from origin other than 127.0.0.1 in the browser console

fetch("http://127.0.0.1:9080/hello").then(resp => console.log(Array.from(resp.headers.keys())))

The header Foo: Bar could not be get from the script. You will only get content-type and content-length header in this case.

Only if Access-Control-Expose-Headers is set specially Foo, then the foo header can be get. So I said the ** does not work.

nthsky avatar Jul 11 '22 06:07 nthsky

Looks like this is not related to APISIX but the handling of CORS in the broswer.

tokers avatar Jul 11 '22 09:07 tokers

I can't find the explanation about ** easily. Do you have any materials?

tokers avatar Jul 11 '22 09:07 tokers

Only if Access-Control-Expose-Headers is set specially Foo, then the foo header can be get. So I said the ** does not work.

Looks like that in this case, we should make Access-Control-Expose-Headers return * instead of **, than it would work.

tzssangglass avatar Jul 11 '22 13:07 tzssangglass

Only if Access-Control-Expose-Headers is set specially Foo, then the foo header can be get. So I said the ** does not work.

Looks like that in this case, we should make Access-Control-Expose-Headers return * instead of **, than it would work.

I'm afraid not, since allow_credential is true, browser might suffer from another problem.

tokers avatar Jul 12 '22 01:07 tokers

I can't find the explanation about ** easily. Do you have any materials?

https://apisix.apache.org/docs/apisix/plugins/cors

exposed_headers

Headers in the response allowed when accessing a cross-origin resource. Use , to add multiple headers. If allow_credential is set to false, you can enable CORS for all response headers by using *. If allow_credential is set to true, you can forcefully allow CORS on all response headers by using ** but it will pose some security issues.

nthsky avatar Jul 12 '22 02:07 nthsky

Looks like this is not related to APISIX but the handling of CORS in the broswer.

It's work cors plugin to do, isn't it? The official document above said but I found no handling.

nthsky avatar Jul 12 '22 02:07 nthsky

curl http://127.0.0.1:9080/hello -i
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Date: Mon, 11 Jul 2022 02:02:26 GMT
Foo: Bar
Server: APISIX/2.14.1
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS,CONNECT,TRACE
Access-Control-Max-Age: 5
Access-Control-Expose-Headers: **
Access-Control-Allow-Credentials: true

if here Access-Control-Expose-Headers is * not **, would the browser get Foo: bar header?

tzssangglass avatar Jul 12 '22 02:07 tzssangglass

https://github.com/apache/apisix/blob/master/apisix/plugins/cors.lua#L167-L172

when allow-credentials is true, it is not allowed to set other field to *

nthsky avatar Jul 12 '22 09:07 nthsky

https://github.com/apache/apisix/blob/master/apisix/plugins/cors.lua#L167-L172

when allow-credentials is true, it is not allowed to set other field to *

ok, I get it. So far, APISIX returns the header Foo:Bar, but it is not available in the browser, I don't think this is due to APISIX but browser.

tzssangglass avatar Jul 12 '22 10:07 tzssangglass

https://github.com/apache/apisix/blob/master/apisix/plugins/cors.lua#L167-L172

when allow-credentials is true, it is not allowed to set other field to *

ok, I get it. So far, APISIX returns the header Foo:Bar, but it is not available in the browser, I don't think this is due to APISIX but browser.

Yes, but we need to know the exact meaning for ** and why it's not effective.

tokers avatar Jul 12 '22 10:07 tokers

https://github.com/apache/apisix/blob/master/apisix/plugins/cors.lua#L167-L172

when allow-credentials is true, it is not allowed to set other field to *

ok, I get it. So far, APISIX returns the header Foo:Bar, but it is not available in the browser, I don't think this is due to APISIX but browser.

But the cross origin policy is just what browser does and what cors plugin need to resolve. I think so.

nthsky avatar Jul 12 '22 10:07 nthsky

https://github.com/apache/apisix/blob/master/apisix/plugins/cors.lua#L167-L172

when allow-credentials is true, it is not allowed to set other field to *

ok, I get it. So far, APISIX returns the header Foo:Bar, but it is not available in the browser, I don't think this is due to APISIX but browser.

Yes, but we need to know the exact meaning for ** and why it's not effective.

According to the plugin's code, ** is special handling of the plugin. For example, ** for allow origin put the origin host to the access-control-allow-origin. Other config is similar but just not handle the expose_headers.

nthsky avatar Jul 12 '22 10:07 nthsky

For example, ** for allow origin put the origin host to the access-control-allow-origin. Other config is similar but just not handle the expose_headers.

when allow_credential is true,

  1. if allow_headers is **, Access-Control-Allow-Origin would be ignored in response header;
  2. if expose_headers is **, Access-Control-Expose-Headers would be **, should we make Access-Control-Expose-Headers ignored as Access-Control-Allow-Origin?

I'm not sure, it's just an idea, you can try it on the browser.

tzssangglass avatar Jul 12 '22 10:07 tzssangglass

This issue has been marked as stale due to 350 days of inactivity. It will be closed in 2 weeks if no further activity occurs. If this issue is still relevant, please simply write any comment. Even if closed, you can still revive the issue at any time or discuss it on the [email protected] list. Thank you for your contributions.

github-actions[bot] avatar Jun 28 '23 10:06 github-actions[bot]

This issue has been closed due to lack of activity. If you think that is incorrect, or the issue requires additional review, you can revive the issue at any time.

github-actions[bot] avatar Jul 13 '23 10:07 github-actions[bot]