Skip to content

Commit

Permalink
feat(gui): add a new feature to support multiple domains for KM GUI (#…
Browse files Browse the repository at this point in the history
…13664)

1. By default, set the origin retrieved from the request header or '*'.
2. If the `admin_gui_url` has more than one domain, the origin from the request header must match one of the `admin_gui_url`. Otherwise, It can't access the Admin API via the Kong Manager. 
For example: 
`admin_gui_url=http://example.com,http://km.konghq.com/manager`
These addresses access Kong Manager works well. http://example.com and http://km.konghq.com/manager.
Others can't.

EE's PR is here: Kong/kong-ee#10260

https://konghq.atlassian.net/browse/KM-516
  • Loading branch information
raoxiaoyan authored Oct 14, 2024
1 parent c64583b commit 0b0cbed
Show file tree
Hide file tree
Showing 9 changed files with 51 additions and 27 deletions.
4 changes: 4 additions & 0 deletions changelog/unreleased/kong/add_multiple_domain_for_gui.yml
Original file line number Diff line number Diff line change
@@ -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"
11 changes: 4 additions & 7 deletions kong.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -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):
#
# `<scheme>://<IP / HOSTNAME>(:<PORT>)`
# `<scheme>://<IP / HOSTNAME>[:<PORT>][<PATH>][, <scheme>://<IP / HOSTNAME>[:<PORT>][<PATH>]]`
#
# 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
#
Expand Down
1 change: 0 additions & 1 deletion kong/admin_gui/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
23 changes: 19 additions & 4 deletions kong/api/api_helpers.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions kong/conf_loader/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
10 changes: 7 additions & 3 deletions kong/conf_loader/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 12 additions & 3 deletions spec/01-unit/03-conf_loader_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -323,23 +323,32 @@ 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",
})
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",
})
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
Expand Down
4 changes: 0 additions & 4 deletions spec/01-unit/29-admin_gui/02-admin_gui_template_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions spec/02-integration/02-cmd/03-reload_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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)

1 comment on commit 0b0cbed

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bazel Build

Docker image available kong/kong:0b0cbed829713e6c151c9313051030eb2b7160d0
Artifacts available https://github.com/Kong/kong/actions/runs/11319836930

Please sign in to comment.