diff --git a/changelog/unreleased/kong/feat-cors-skip-return-acao-when-no-origin-in-request.yml b/changelog/unreleased/kong/feat-cors-skip-return-acao-when-no-origin-in-request.yml new file mode 100644 index 000000000000..228a515c6671 --- /dev/null +++ b/changelog/unreleased/kong/feat-cors-skip-return-acao-when-no-origin-in-request.yml @@ -0,0 +1,3 @@ +message: "**CORS**: add an option to skip return ACAO when request don't have Origin Header" +type: feature +scope: Plugin \ No newline at end of file diff --git a/kong/clustering/compat/removed_fields.lua b/kong/clustering/compat/removed_fields.lua index b52e215fec11..657ca2f22088 100644 --- a/kong/clustering/compat/removed_fields.lua +++ b/kong/clustering/compat/removed_fields.lua @@ -228,7 +228,10 @@ return { "queue.concurrency_limit", }, }, - [30010000000] = { + [3010000000] = { + cors = { + "skip_cors_when_origin_is_empty", + }, session = { "hash_subject", "store_metadata", diff --git a/kong/plugins/cors/handler.lua b/kong/plugins/cors/handler.lua index 3d62be388181..456c0c248a0f 100644 --- a/kong/plugins/cors/handler.lua +++ b/kong/plugins/cors/handler.lua @@ -247,6 +247,11 @@ function CorsHandler:header_filter(conf) return end + local req_origin = kong.request.get_header("origin") + if not req_origin and conf.skip_cors_when_origin_is_empty then + return + end + local allow_all = configure_origin(conf, true) configure_credentials(conf, allow_all, true) diff --git a/kong/plugins/cors/schema.lua b/kong/plugins/cors/schema.lua index 4910a321d085..3f5fbbf5b994 100644 --- a/kong/plugins/cors/schema.lua +++ b/kong/plugins/cors/schema.lua @@ -48,6 +48,7 @@ return { { credentials = { description = "Flag to determine whether the `Access-Control-Allow-Credentials` header should be sent with `true` as the value.", type = "boolean", required = true, default = false }, }, { private_network = { description = "Flag to determine whether the `Access-Control-Allow-Private-Network` header should be sent with `true` as the value.", type = "boolean", required = true, default = false }, }, { preflight_continue = { description = "A boolean value that instructs the plugin to proxy the `OPTIONS` preflight request to the Upstream service.", type = "boolean", required = true, default = false }, }, + { skip_cors_when_origin_is_empty = { description = "A boolean value that skip cors response headers when origin header of request is empty", type = "boolean", required = true, default = false }, }, }, }, }, }, } diff --git a/spec/01-unit/19-hybrid/02-clustering_spec.lua b/spec/01-unit/19-hybrid/02-clustering_spec.lua index d2d54f10d83e..9d430d427b6c 100644 --- a/spec/01-unit/19-hybrid/02-clustering_spec.lua +++ b/spec/01-unit/19-hybrid/02-clustering_spec.lua @@ -7,6 +7,7 @@ describe("kong.clustering.compat.version", function() assert.equal(3000001000, version.string_to_number("3.0.1")) assert.equal(3000000000, version.string_to_number("3.0.0.0")) assert.equal(3000000001, version.string_to_number("3.0.0.1")) + assert.equal(3010000000, version.string_to_number("3.10.0.0")) assert.equal(333333333001, version.string_to_number("333.333.333.1")) assert.equal(333333333333, version.string_to_number("333.333.333.333")) end) diff --git a/spec/02-integration/09-hybrid_mode/09-config-compat_spec.lua b/spec/02-integration/09-hybrid_mode/09-config-compat_spec.lua index 07677fe45e8f..79e05ee24362 100644 --- a/spec/02-integration/09-hybrid_mode/09-config-compat_spec.lua +++ b/spec/02-integration/09-hybrid_mode/09-config-compat_spec.lua @@ -201,19 +201,27 @@ describe("CP/DP config compat transformations #" .. strategy, function() end) describe("compatibility test for cors plugin", function() - it("removes `config.private_network` before sending them to older(less than 3.5.0.0) DP nodes", function() + it("removes config.options before sending them to older DP nodes", function() local cors = admin.plugins:insert { name = "cors", enabled = true, config = { + -- [[ new fields 3.10.0 + skip_cors_when_origin_is_empty = false, + -- ]] -- [[ new fields 3.5.0 private_network = false -- ]] } } - assert.not_nil(cors.config.private_network) + assert.not_nil(cors.config.skip_cors_when_origin_is_empty) local expected_cors = cycle_aware_deep_copy(cors) + do_assert(uuid(), "3.10.0", expected_cors) + expected_cors.config.skip_cors_when_origin_is_empty = nil + + assert.not_nil(cors.config.private_network) + expected_cors = cycle_aware_deep_copy(expected_cors) expected_cors.config.private_network = nil do_assert(uuid(), "3.4.0", expected_cors) @@ -221,16 +229,22 @@ describe("CP/DP config compat transformations #" .. strategy, function() admin.plugins:remove({ id = cors.id }) end) - it("does not remove `config.private_network` from DP nodes that are already compatible", function() + it("does not remove config.options from DP nodes that are already compatible", function() local cors = admin.plugins:insert { name = "cors", enabled = true, config = { + -- [[ new fields 3.10.0 + skip_cors_when_origin_is_empty = false, + -- ]] -- [[ new fields 3.5.0 private_network = false -- ]] } } + do_assert(uuid(), "3.10.0", cors) + cors.config.skip_cors_when_origin_is_empty = nil + do_assert(uuid(), "3.5.0", cors) -- cleanup diff --git a/spec/03-plugins/13-cors/01-access_spec.lua b/spec/03-plugins/13-cors/01-access_spec.lua index cf6dce918179..4d46c2cc3ec0 100644 --- a/spec/03-plugins/13-cors/01-access_spec.lua +++ b/spec/03-plugins/13-cors/01-access_spec.lua @@ -291,6 +291,14 @@ for _, strategy in helpers.each_strategy() do hosts = { "cors14.test" }, }) + local route15 = bp.routes:insert({ + hosts = { "cors15.test" }, + }) + + local route16 = bp.routes:insert({ + hosts = { "cors16.test" }, + }) + local mock_upstream = bp.services:insert { host = helpers.mock_upstream_hostname, port = helpers.mock_upstream_port, @@ -464,6 +472,28 @@ for _, strategy in helpers.each_strategy() do } } + bp.plugins:insert { + name = "cors", + route = { id = route15.id }, + config = { + skip_cors_when_origin_is_empty = true, + origins = { "foo.bar" }, + exposed_headers = { "x-auth-token" }, + credentials = true + } + } + + bp.plugins:insert { + name = "cors", + route = { id = route16.id }, + config = { + skip_cors_when_origin_is_empty = false, + origins = { "foo.bar" }, + exposed_headers = { "x-auth-token" }, + credentials = true + } + } + bp.plugins:insert { name = "cors", route = { id = route_timeout.id }, @@ -1130,6 +1160,32 @@ for _, strategy in helpers.each_strategy() do assert.equal("true", res.headers["Access-Control-Allow-Credentials"]) assert.equal("disallowed-domain.test", json.headers["origin"]) end) + + it("when enable skip_cors_when_origin_is_empty, no ACAO", function() + local res = assert(proxy_client:send { + method = "GET", + headers = { + ["Host"] = "cors15.test", + } + }) + assert.res_status(200, res) + assert.is_nil(res.headers["Access-Control-Allow-Origin"]) + assert.is_nil(res.headers["Access-Control-Allow-Credentials"]) + assert.is_nil(res.headers["Access-Control-Expose-Headers"]) + end) + + it("when disable skip_cors_when_origin_is_empty, ACAO is returned", function() + local res = assert(proxy_client:send { + method = "GET", + headers = { + ["Host"] = "cors16.test", + } + }) + assert.res_status(200, res) + assert.equal("foo.bar", res.headers["Access-Control-Allow-Origin"] ) + assert.equal("true", res.headers["Access-Control-Allow-Credentials"]) + assert.equal("x-auth-token", res.headers["Access-Control-Expose-Headers"]) + end) end) end) end