From b40e8dfc2504890c1d0e3c573601d60bd1c02191 Mon Sep 17 00:00:00 2001 From: lhanjian Date: Tue, 14 Jan 2025 16:24:26 +0800 Subject: [PATCH 1/7] update --- kong/plugins/cors/handler.lua | 15 ++++++++++++--- kong/plugins/cors/schema.lua | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/kong/plugins/cors/handler.lua b/kong/plugins/cors/handler.lua index 3d62be38818..f0379f49524 100644 --- a/kong/plugins/cors/handler.lua +++ b/kong/plugins/cors/handler.lua @@ -65,6 +65,11 @@ local function configure_origin(conf, header_filter) local n_origins = conf.origins ~= nil and #conf.origins or 0 local set_header = kong.response.set_header + local req_origin = kong.request.get_header("origin") + if not req_origin and conf.skip_cors_when_origin_is_empty then + return false + end + if n_origins == 0 or (n_origins == 1 and conf.origins[1] == "*") then set_header("Access-Control-Allow-Origin", "*") return true @@ -89,7 +94,6 @@ local function configure_origin(conf, header_filter) end end - local req_origin = kong.request.get_header("origin") if req_origin then local cached_domains = config_cache[conf] if not cached_domains then @@ -174,6 +178,11 @@ local function configure_credentials(conf, allow_all, header_filter) 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 + if not allow_all then set_header("Access-Control-Allow-Credentials", true) return @@ -181,7 +190,6 @@ local function configure_credentials(conf, allow_all, header_filter) -- Access-Control-Allow-Origin is '*', must change it because ACAC cannot -- be 'true' if ACAO is '*'. - local req_origin = kong.request.get_header("origin") if req_origin then add_vary_header(header_filter) set_header("Access-Control-Allow-Origin", req_origin) @@ -250,7 +258,8 @@ function CorsHandler:header_filter(conf) local allow_all = configure_origin(conf, true) configure_credentials(conf, allow_all, true) - if conf.exposed_headers and #conf.exposed_headers > 0 then + local req_origin = kong.request.get_header("origin") + if req_origin and conf.exposed_headers and #conf.exposed_headers > 0 then kong.response.set_header("Access-Control-Expose-Headers", concat(conf.exposed_headers, ",")) end diff --git a/kong/plugins/cors/schema.lua b/kong/plugins/cors/schema.lua index 4910a321d08..3f5fbbf5b99 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 }, }, }, }, }, }, } From bf6672223e1f5c8f67a5c44684b94dade20c19a2 Mon Sep 17 00:00:00 2001 From: lhanjian Date: Wed, 15 Jan 2025 10:53:37 +0800 Subject: [PATCH 2/7] update --- kong/plugins/cors/handler.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kong/plugins/cors/handler.lua b/kong/plugins/cors/handler.lua index f0379f49524..ff2e9d40de1 100644 --- a/kong/plugins/cors/handler.lua +++ b/kong/plugins/cors/handler.lua @@ -259,7 +259,10 @@ function CorsHandler:header_filter(conf) configure_credentials(conf, allow_all, true) local req_origin = kong.request.get_header("origin") - if req_origin and conf.exposed_headers and #conf.exposed_headers > 0 then + if not req_origin and conf.skip_cors_when_origin_is_empty then + return + end + if conf.exposed_headers and #conf.exposed_headers > 0 then kong.response.set_header("Access-Control-Expose-Headers", concat(conf.exposed_headers, ",")) end From f5369a91c48abb7a00972f8915ab7022dbb06487 Mon Sep 17 00:00:00 2001 From: lhanjian Date: Wed, 15 Jan 2025 15:38:53 +0800 Subject: [PATCH 3/7] update --- spec/03-plugins/13-cors/01-access_spec.lua | 56 ++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/spec/03-plugins/13-cors/01-access_spec.lua b/spec/03-plugins/13-cors/01-access_spec.lua index cf6dce91817..4d46c2cc3ec 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 From 290d4a19136c0336574ebbcbc4364fc63ba08806 Mon Sep 17 00:00:00 2001 From: lhanjian Date: Wed, 15 Jan 2025 15:55:58 +0800 Subject: [PATCH 4/7] update --- .../feat-cors-skip-return-acao-when-no-origin-in-request.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/unreleased/kong/feat-cors-skip-return-acao-when-no-origin-in-request.yml 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 00000000000..228a515c667 --- /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 From e6733e593e225590a21188a75c8394fb856e39c8 Mon Sep 17 00:00:00 2001 From: lhanjian Date: Thu, 16 Jan 2025 11:20:26 +0800 Subject: [PATCH 5/7] update --- kong/plugins/cors/handler.lua | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/kong/plugins/cors/handler.lua b/kong/plugins/cors/handler.lua index ff2e9d40de1..456c0c248a0 100644 --- a/kong/plugins/cors/handler.lua +++ b/kong/plugins/cors/handler.lua @@ -65,11 +65,6 @@ local function configure_origin(conf, header_filter) local n_origins = conf.origins ~= nil and #conf.origins or 0 local set_header = kong.response.set_header - local req_origin = kong.request.get_header("origin") - if not req_origin and conf.skip_cors_when_origin_is_empty then - return false - end - if n_origins == 0 or (n_origins == 1 and conf.origins[1] == "*") then set_header("Access-Control-Allow-Origin", "*") return true @@ -94,6 +89,7 @@ local function configure_origin(conf, header_filter) end end + local req_origin = kong.request.get_header("origin") if req_origin then local cached_domains = config_cache[conf] if not cached_domains then @@ -178,11 +174,6 @@ local function configure_credentials(conf, allow_all, header_filter) 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 - if not allow_all then set_header("Access-Control-Allow-Credentials", true) return @@ -190,6 +181,7 @@ local function configure_credentials(conf, allow_all, header_filter) -- Access-Control-Allow-Origin is '*', must change it because ACAC cannot -- be 'true' if ACAO is '*'. + local req_origin = kong.request.get_header("origin") if req_origin then add_vary_header(header_filter) set_header("Access-Control-Allow-Origin", req_origin) @@ -255,13 +247,14 @@ function CorsHandler:header_filter(conf) return end - local allow_all = configure_origin(conf, true) - configure_credentials(conf, allow_all, true) - 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) + if conf.exposed_headers and #conf.exposed_headers > 0 then kong.response.set_header("Access-Control-Expose-Headers", concat(conf.exposed_headers, ",")) From 22a59c75751d71fec8aaa02ef042d18d1b694dd0 Mon Sep 17 00:00:00 2001 From: lhanjian Date: Thu, 16 Jan 2025 16:52:48 +0800 Subject: [PATCH 6/7] update --- kong/clustering/compat/removed_fields.lua | 3 +++ .../09-hybrid_mode/09-config-compat_spec.lua | 20 ++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/kong/clustering/compat/removed_fields.lua b/kong/clustering/compat/removed_fields.lua index 77284a91011..9a63e490929 100644 --- a/kong/clustering/compat/removed_fields.lua +++ b/kong/clustering/compat/removed_fields.lua @@ -230,6 +230,9 @@ return { }, -- Any dataplane older than 3.10.0 [3010000000] = { + cors = { + "skip_cors_when_origin_is_empty", + }, session = { "hash_subject", "store_metadata", 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 07677fe45e8..79e05ee2436 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 From b353adf3a7e33371642975d5e0953d3806f2308a Mon Sep 17 00:00:00 2001 From: lhanjian Date: Tue, 21 Jan 2025 15:25:07 +0800 Subject: [PATCH 7/7] update --- kong/clustering/compat/removed_fields.lua | 2 +- kong/plugins/cors/handler.lua | 2 +- kong/plugins/cors/schema.lua | 2 +- .../09-hybrid_mode/09-config-compat_spec.lua | 10 +++++----- spec/03-plugins/13-cors/01-access_spec.lua | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/kong/clustering/compat/removed_fields.lua b/kong/clustering/compat/removed_fields.lua index 9a63e490929..c5a2af2c7f3 100644 --- a/kong/clustering/compat/removed_fields.lua +++ b/kong/clustering/compat/removed_fields.lua @@ -231,7 +231,7 @@ return { -- Any dataplane older than 3.10.0 [3010000000] = { cors = { - "skip_cors_when_origin_is_empty", + "allow_origin_absent", }, session = { "hash_subject", diff --git a/kong/plugins/cors/handler.lua b/kong/plugins/cors/handler.lua index 456c0c248a0..f1b111dcb0a 100644 --- a/kong/plugins/cors/handler.lua +++ b/kong/plugins/cors/handler.lua @@ -248,7 +248,7 @@ function CorsHandler:header_filter(conf) end local req_origin = kong.request.get_header("origin") - if not req_origin and conf.skip_cors_when_origin_is_empty then + if not req_origin and not conf.allow_origin_absent then return end diff --git a/kong/plugins/cors/schema.lua b/kong/plugins/cors/schema.lua index 3f5fbbf5b99..1334f789feb 100644 --- a/kong/plugins/cors/schema.lua +++ b/kong/plugins/cors/schema.lua @@ -48,7 +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 }, }, + { allow_origin_absent = { description = "A boolean value that skip cors response headers when origin header of request is empty", type = "boolean", required = true, default = true }, }, }, }, }, }, } 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 79e05ee2436..805f0e1e66d 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 @@ -207,7 +207,7 @@ describe("CP/DP config compat transformations #" .. strategy, function() enabled = true, config = { -- [[ new fields 3.10.0 - skip_cors_when_origin_is_empty = false, + allow_origin_absent = true, -- ]] -- [[ new fields 3.5.0 private_network = false @@ -215,10 +215,10 @@ describe("CP/DP config compat transformations #" .. strategy, function() } } - assert.not_nil(cors.config.skip_cors_when_origin_is_empty) + assert.not_nil(cors.config.allow_origin_absent) 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 + expected_cors.config.allow_origin_absent = nil assert.not_nil(cors.config.private_network) expected_cors = cycle_aware_deep_copy(expected_cors) @@ -235,7 +235,7 @@ describe("CP/DP config compat transformations #" .. strategy, function() enabled = true, config = { -- [[ new fields 3.10.0 - skip_cors_when_origin_is_empty = false, + allow_origin_absent = true, -- ]] -- [[ new fields 3.5.0 private_network = false @@ -243,7 +243,7 @@ describe("CP/DP config compat transformations #" .. strategy, function() } } do_assert(uuid(), "3.10.0", cors) - cors.config.skip_cors_when_origin_is_empty = nil + cors.config.allow_origin_absent = nil do_assert(uuid(), "3.5.0", cors) diff --git a/spec/03-plugins/13-cors/01-access_spec.lua b/spec/03-plugins/13-cors/01-access_spec.lua index 4d46c2cc3ec..141d6c4d543 100644 --- a/spec/03-plugins/13-cors/01-access_spec.lua +++ b/spec/03-plugins/13-cors/01-access_spec.lua @@ -476,7 +476,7 @@ for _, strategy in helpers.each_strategy() do name = "cors", route = { id = route15.id }, config = { - skip_cors_when_origin_is_empty = true, + allow_origin_absent = false, origins = { "foo.bar" }, exposed_headers = { "x-auth-token" }, credentials = true @@ -487,7 +487,7 @@ for _, strategy in helpers.each_strategy() do name = "cors", route = { id = route16.id }, config = { - skip_cors_when_origin_is_empty = false, + allow_origin_absent = true, origins = { "foo.bar" }, exposed_headers = { "x-auth-token" }, credentials = true @@ -1161,7 +1161,7 @@ for _, strategy in helpers.each_strategy() do assert.equal("disallowed-domain.test", json.headers["origin"]) end) - it("when enable skip_cors_when_origin_is_empty, no ACAO", function() + it("when disable allow_origin_absent, no ACAO", function() local res = assert(proxy_client:send { method = "GET", headers = { @@ -1174,7 +1174,7 @@ for _, strategy in helpers.each_strategy() do assert.is_nil(res.headers["Access-Control-Expose-Headers"]) end) - it("when disable skip_cors_when_origin_is_empty, ACAO is returned", function() + it("when enable allow_origin_absent, ACAO is returned", function() local res = assert(proxy_client:send { method = "GET", headers = {