diff --git a/changelog/unreleased/kong/add_multiple_domain_for_gui.yml b/changelog/unreleased/kong/add_multiple_domain_for_gui.yml new file mode 100644 index 000000000000..c2b6830ba6fb --- /dev/null +++ b/changelog/unreleased/kong/add_multiple_domain_for_gui.yml @@ -0,0 +1,4 @@ +message: | + Added a new feature for Kong Manager that supports multiple domains, enabling dynamic cross-origin access for Admin API requests. +type: feature +scope: "Core" \ No newline at end of file diff --git a/kong.conf.default b/kong.conf.default index 37af25498a01..66de2912a263 100644 --- a/kong.conf.default +++ b/kong.conf.default @@ -1994,21 +1994,18 @@ #admin_gui_url = # Kong Manager URL # - # The lookup, or balancer, address for Kong Manager. + # Comma-separated list of addresses (the lookup or balancer) for Kong Manager. # - # Accepted format (items in parentheses are optional): + # Accepted format (items in square brackets are optional): # - # `://(:)` + # `://[:][][, ://[:][]]` # # Examples: # # - `http://127.0.0.1:8003` # - `https://kong-admin.test` # - `http://dev-machine` - # - # By default, Kong Manager will use the window request - # host and append the resolved listener port depending - # on the requested protocol. + # - `http://127.0.0.1:8003, https://exmple.com/manager` #admin_gui_path = / # Kong Manager base path # diff --git a/kong/admin_gui/init.lua b/kong/admin_gui/init.lua index f1c32500b620..6b02fb96f613 100644 --- a/kong/admin_gui/init.lua +++ b/kong/admin_gui/init.lua @@ -17,7 +17,6 @@ function _M.generate_kconfig(kong_config) local api_ssl_port = api_ssl_listen and api_ssl_listen.port local configs = { - ADMIN_GUI_URL = prepare_variable(kong_config.admin_gui_url), ADMIN_GUI_PATH = prepare_variable(kong_config.admin_gui_path), ADMIN_API_URL = prepare_variable(kong_config.admin_gui_api_url), ADMIN_API_PORT = prepare_variable(api_port), diff --git a/kong/api/api_helpers.lua b/kong/api/api_helpers.lua index 62d51a859f91..34ff0feba5ab 100644 --- a/kong/api/api_helpers.lua +++ b/kong/api/api_helpers.lua @@ -271,13 +271,28 @@ function _M.before_filter(self) end function _M.cors_filter(self) - local origin = self.req.headers["Origin"] + local allowed_origins = kong.configuration.admin_gui_origin - if kong.configuration.admin_gui_origin then - origin = kong.configuration.admin_gui_origin + local function is_origin_allowed(req_origin) + for _, allowed_origin in ipairs(allowed_origins) do + if req_origin == allowed_origin then + return true + end + end + return false + end + + local req_origin = self.req.headers["Origin"] + + if allowed_origins and #allowed_origins > 0 then + if not is_origin_allowed(req_origin) then + req_origin = allowed_origins[1] + end + else + req_origin = req_origin or "*" end - ngx.header["Access-Control-Allow-Origin"] = origin or "*" + ngx.header["Access-Control-Allow-Origin"] = req_origin ngx.header["Access-Control-Allow-Credentials"] = "true" if ngx.req.get_method() == "OPTIONS" then diff --git a/kong/conf_loader/constants.lua b/kong/conf_loader/constants.lua index 4e416d4da361..21326a588e3e 100644 --- a/kong/conf_loader/constants.lua +++ b/kong/conf_loader/constants.lua @@ -562,9 +562,9 @@ local CONF_PARSERS = { error_template_xml = { typ = "string" }, error_template_plain = { typ = "string" }, - admin_gui_url = {typ = "string"}, - admin_gui_path = {typ = "string"}, - admin_gui_api_url = {typ = "string"}, + admin_gui_url = { typ = "array" }, + admin_gui_path = { typ = "string" }, + admin_gui_api_url = { typ = "string" }, request_debug = { typ = "boolean" }, request_debug_token = { typ = "string" }, diff --git a/kong/conf_loader/init.lua b/kong/conf_loader/init.lua index f1deaf9ef21d..96ff04522ac2 100644 --- a/kong/conf_loader/init.lua +++ b/kong/conf_loader/init.lua @@ -935,9 +935,13 @@ local function load(path, custom_conf, opts) -- to make it suitable to be used as an origin in headers, we need to -- parse and reconstruct the admin_gui_url to ensure it only contains -- the scheme, host, and port - if conf.admin_gui_url then - local parsed_url = socket_url.parse(conf.admin_gui_url) - conf.admin_gui_origin = parsed_url.scheme .. "://" .. parsed_url.authority + if conf.admin_gui_url and #conf.admin_gui_url > 0 then + local admin_gui_origin = {} + for _, url in ipairs(conf.admin_gui_url) do + local parsed_url = socket_url.parse(url) + table.insert(admin_gui_origin, parsed_url.scheme .. "://" .. parsed_url.authority) + end + conf.admin_gui_origin = admin_gui_origin end -- hybrid mode HTTP tunneling (CONNECT) proxy inside HTTPS diff --git a/spec/01-unit/03-conf_loader_spec.lua b/spec/01-unit/03-conf_loader_spec.lua index 9827fcef10e2..be5ca97e17ee 100644 --- a/spec/01-unit/03-conf_loader_spec.lua +++ b/spec/01-unit/03-conf_loader_spec.lua @@ -323,7 +323,7 @@ describe("Configuration loader", function() assert.is_nil(errors) assert.is_not_nil(conf) assert.is_not_nil(conf.admin_gui_origin) - assert.equal("http://localhost:8002", conf.admin_gui_origin) + assert.same({ "http://localhost:8002" }, conf.admin_gui_origin) conf, _, errors = conf_loader(nil, { admin_gui_url = "https://localhost:8002", @@ -331,7 +331,7 @@ describe("Configuration loader", function() assert.is_nil(errors) assert.is_not_nil(conf) assert.is_not_nil(conf.admin_gui_origin) - assert.equal("https://localhost:8002", conf.admin_gui_origin) + assert.same({ "https://localhost:8002" }, conf.admin_gui_origin) conf, _, errors = conf_loader(nil, { admin_gui_url = "http://localhost:8002/manager", @@ -339,7 +339,16 @@ describe("Configuration loader", function() assert.is_nil(errors) assert.is_not_nil(conf) assert.is_not_nil(conf.admin_gui_origin) - assert.equal("http://localhost:8002", conf.admin_gui_origin) + assert.same({ "http://localhost:8002" }, conf.admin_gui_origin) + + conf, _, errors = conf_loader(nil, { + admin_gui_url = "http://localhost:8002/manager, https://localhost:8445/manager", + }) + assert.is_nil(errors) + assert.is_not_nil(conf) + assert.is_not_nil(conf.admin_gui_origin) + assert.is_table(conf.admin_gui_origin) + assert.same({ "http://localhost:8002", "https://localhost:8445" }, conf.admin_gui_origin) end) it("strips comments ending settings", function() local _os_getenv = os.getenv diff --git a/spec/01-unit/29-admin_gui/02-admin_gui_template_spec.lua b/spec/01-unit/29-admin_gui/02-admin_gui_template_spec.lua index de4c337fda36..7e44dc3abe2e 100644 --- a/spec/01-unit/29-admin_gui/02-admin_gui_template_spec.lua +++ b/spec/01-unit/29-admin_gui/02-admin_gui_template_spec.lua @@ -64,7 +64,6 @@ describe("admin_gui template", function() it("should generates the appropriate kconfig", function() local kconfig_content = admin_gui.generate_kconfig(conf) - assert.matches("'ADMIN_GUI_URL': 'http://0.0.0.0:8002'", kconfig_content, nil, true) assert.matches("'ADMIN_GUI_PATH': '/manager'", kconfig_content, nil, true) assert.matches("'ADMIN_API_URL': 'https://admin-reference.kong-cloud.test'", kconfig_content, nil, true) assert.matches("'ADMIN_API_PORT': '8001'", kconfig_content, nil, true) @@ -83,7 +82,6 @@ describe("admin_gui template", function() local new_content = admin_gui.generate_kconfig(new_conf) -- test configuration values against template - assert.matches("'ADMIN_GUI_URL': 'http://admin-test.example.com'", new_content, nil, true) assert.matches("'ADMIN_GUI_PATH': '/manager'", new_content, nil, true) assert.matches("'ADMIN_API_URL': 'http://localhost:8001'", new_content, nil, true) assert.matches("'ADMIN_API_PORT': '8001'", new_content, nil, true) @@ -146,7 +144,6 @@ describe("admin_gui template", function() it("should generates the appropriate kconfig", function() local kconfig_content = admin_gui.generate_kconfig(conf) - assert.matches("'ADMIN_GUI_URL': 'http://0.0.0.0:8002'", kconfig_content, nil, true) assert.matches("'ADMIN_API_URL': '0.0.0.0:8001'", kconfig_content, nil, true) assert.matches("'ADMIN_API_PORT': '8001'", kconfig_content, nil, true) assert.matches("'ADMIN_API_SSL_PORT': '8444'", kconfig_content, nil, true) @@ -164,7 +161,6 @@ describe("admin_gui template", function() local new_content = admin_gui.generate_kconfig(new_conf) -- test configuration values against template - assert.matches("'ADMIN_GUI_URL': 'http://admin-test.example.com'", new_content, nil, true) assert.matches("'ADMIN_API_URL': '0.0.0.0:8001'", new_content, nil, true) assert.matches("'ADMIN_API_PORT': '8001'", new_content, nil, true) assert.matches("'ADMIN_API_SSL_PORT': '8444'", new_content, nil, true) diff --git a/spec/02-integration/02-cmd/03-reload_spec.lua b/spec/02-integration/02-cmd/03-reload_spec.lua index 364a3f576599..218ec0dcdd33 100644 --- a/spec/02-integration/02-cmd/03-reload_spec.lua +++ b/spec/02-integration/02-cmd/03-reload_spec.lua @@ -739,7 +739,7 @@ describe("Admin GUI config", function () path = "/kconfig.js", }) res = assert.res_status(200, res) - assert.matches("'ADMIN_GUI_URL': 'http://test1.example.com'", res, nil, true) + assert.matches("'ADMIN_GUI_PATH': '/'", res, nil, true) client:close() @@ -764,7 +764,7 @@ describe("Admin GUI config", function () path = "/manager/kconfig.js", }) res = assert.res_status(200, res) - assert.matches("'ADMIN_GUI_URL': 'http://test2.example.com'", res, nil, true) + assert.matches("'ADMIN_GUI_PATH': '/manager'", res, nil, true) client:close() end) end)