Skip to content

Commit

Permalink
feat(conf) add port_map configuration option
Browse files Browse the repository at this point in the history
### Summary

Layer 4 port mapping (or port forwarding) is used a lot when running
Kong inside a container. Common example is exposing ports 80 and 443
externally while running Kong inside a container with internal ports
8000 and 8443. It also allows Kong to started without elevated
privileges as it is quite common that you need root-access to bind
processes to TCP port < 1024.

As this translation is done in many cases without using layer 4
proxy_protocol or layer 7 X-Forwarded-Port HTTP header, the Kong has
no knowledge about the target port that client originally connected
to.

This PR adds `port_map` configuration parameter:

```
#port_map =                      # When running Kong behind layer 4 port mapping
                                 # (or port-forwarding), e.g. with
                                 # `docker run -p 80:8000`, you can use this
                                 # parameter to let Kong know about published
                                 # port when it differs from the one Kong is
                                 # listening, e.g. port_map=80:8000, 443:8443`.`
```

This gets parsed into `kong.configuration.host_ports` table.

E.g. `KONG_PORT_MAP="80:8000,443:8443"` where the first number (`80`/`443`) before `:`
is the host published port and the second one (`8000`/`8443`) is the port that Kong is
listening translates to following `kong.configuration.host_ports`:

```lua
{
  [8000]   = 80,
  ["8000"] = "80",
  [8443]   = 443,
  ["8443"] = "443",
}
```

This PR also adds a new nginx context variable `ngx.ctx.host_port` which
contains this translated port. So instead of using `ngx.var.server_port`
the more correct one in many cases is `ngx.ctx.host_port`.

Kong PDK is also changed to take this in account when using:
```
local port = kong.request.get_forwarded_port()
```

This commit also changes the `X-Forwarded-Port` to use this. Additionally
`stream routing` by `destination port` will use this too. Basic log
serializer was changed to use `ngx.var.host_port` as well.

Currently it is only usable in proxy/stream, so things like `admin api`
is not affected by this.
  • Loading branch information
bungle committed May 7, 2020
1 parent 679e323 commit 127ee2d
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 4 deletions.
7 changes: 7 additions & 0 deletions kong.conf.default
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
# Each Kong process must have a separate
# working directory.

#port_map = # When running Kong behind layer 4 port mapping
# (or port-forwarding), e.g. with
# `docker run -p 80:8000`, you can use this
# parameter to let Kong know about published
# port when it differs from the one Kong is
# listening, e.g. port_map=80:8000, 443:8443`.`

#log_level = notice # Log level of the Nginx server. Logs are
# found at `<prefix>/logs/error.log`.

Expand Down
29 changes: 29 additions & 0 deletions kong/conf_loader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ local PREFIX_PATHS = {
-- `array`: a comma-separated list
local CONF_INFERENCES = {
-- forced string inferences (or else are retrieved as numbers)
port_map = { typ = "array" },
proxy_listen = { typ = "array" },
admin_listen = { typ = "array" },
status_listen = { typ = "array" },
Expand Down Expand Up @@ -571,6 +572,34 @@ local function check_and_infer(conf, opts)
-- custom validations
---------------------

conf.host_ports = {}
if conf.port_map then
local MIN_PORT = 1
local MAX_PORT = 65535

for _, ports in ipairs(conf.port_map) do
local colpos = string.find(ports, ":", nil, true)
if not colpos then
errors[#errors + 1] = "invalid port mapping (`port_map`): " .. ports

else
local host_port_str = string.sub(ports, 1, colpos - 1)
local host_port_num = tonumber(host_port_str, 10)
local kong_port_str = string.sub(ports, colpos + 1)
local kong_port_num = tonumber(kong_port_str, 10)

if (host_port_num and host_port_num >= MIN_PORT and host_port_num <= MAX_PORT)
and (kong_port_num and kong_port_num >= MIN_PORT and kong_port_num <= MAX_PORT)
then
conf.host_ports[kong_port_num] = host_port_num
conf.host_ports[kong_port_str] = host_port_str
else
errors[#errors + 1] = "invalid port mapping (`port_map`): " .. ports
end
end
end
end

if conf.database == "cassandra" then
if string.find(conf.cassandra_lb_policy, "DCAware", nil, true)
and not conf.cassandra_local_datacenter
Expand Down
9 changes: 8 additions & 1 deletion kong/pdk/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ cjson.decode_array_with_array_mt(true)
local function new(self)
local _REQUEST = {}

local HOST_PORTS = self.configuration.host_ports or {}

local MIN_HEADERS = 1
local MAX_HEADERS_DEFAULT = 100
Expand Down Expand Up @@ -232,7 +233,13 @@ local function new(self)
end
end

return _REQUEST.get_port()
local host_port = ngx.ctx.host_port
if host_port then
return host_port
end

local port = _REQUEST.get_port()
return HOST_PORTS[port] or port
end


Expand Down
4 changes: 3 additions & 1 deletion kong/plugins/log-serializers/basic.lua
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,12 @@ function _M.serialize(ngx, kong)
end
end

local host_port = ctx.host_port or var.server_port

return {
request = {
uri = request_uri,
url = var.scheme .. "://" .. var.host .. ":" .. var.server_port .. request_uri,
url = var.scheme .. "://" .. var.host .. ":" .. host_port .. request_uri,
querystring = kong.request.get_query(), -- parameters, as a table
method = kong.request.get_method(), -- http method
headers = req_headers,
Expand Down
3 changes: 2 additions & 1 deletion kong/router.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1755,7 +1755,8 @@ function _M.new(routes)
local src_ip = var.remote_addr
local src_port = tonumber(var.remote_port, 10)
local dst_ip = var.server_addr
local dst_port = tonumber(var.server_port, 10)
local dst_port = tonumber(ctx.host_port, 10)
or tonumber(var.server_port, 10)
-- error value for non-TLS connections ignored intentionally
local sni, _ = server_name()

Expand Down
14 changes: 13 additions & 1 deletion kong/runloop/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ local COMMA = byte(",")
local SPACE = byte(" ")


local HOST_PORTS = {}


local SUBSYSTEMS = constants.PROTOCOLS_WITH_SUBSYSTEM
local EMPTY_T = {}
local TTL_ZERO = { ttl = 0 }
Expand Down Expand Up @@ -871,6 +874,10 @@ return {

init_worker = {
before = function()
if kong.configuration.host_ports then
HOST_PORTS = kong.configuration.host_ports
end

if kong.configuration.anonymous_reports then
reports.configure_ping(kong.configuration)
reports.add_ping_value("database_version", kong.db.infos.db_ver)
Expand Down Expand Up @@ -953,6 +960,8 @@ return {
},
preread = {
before = function(ctx)
ctx.host_port = HOST_PORTS[var.server_port] or var.server_port

local router = get_updated_router()

local match_t = router.exec()
Expand Down Expand Up @@ -986,6 +995,8 @@ return {
},
rewrite = {
before = function(ctx)
ctx.host_port = HOST_PORTS[var.server_port] or var.server_port

-- special handling for proxy-authorization and te headers in case
-- the plugin(s) want to specify them (store the original)
ctx.http_proxy_authorization = var.http_proxy_authorization
Expand All @@ -1012,7 +1023,8 @@ return {
local http_version = ngx.req.http_version()
local scheme = var.scheme
local host = var.host
local port = tonumber(var.server_port, 10)
local port = tonumber(ctx.host_port, 10)
or tonumber(var.server_port, 10)
local content_type = var.content_type

local route = match_t.route
Expand Down
2 changes: 2 additions & 0 deletions kong/templates/kong_defaults.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
return [[
prefix = /usr/local/kong/
port_map = NONE
host_ports = NONE
log_level = notice
proxy_access_log = logs/access.log
proxy_error_log = logs/error.log
Expand Down

0 comments on commit 127ee2d

Please sign in to comment.