Skip to content

Commit

Permalink
feat(proxy/pdk) add support for X-Forwarded-Prefix (#5620)
Browse files Browse the repository at this point in the history
### Summary

Kong currently adds these headers to upstream request:
```
X-Forwarded-For
X-Forwarded-Port
X-Forwarded-Proto
X-Forwarded-Host
X-Real-IP
```

@erikgb mentioned that he would like to have `X-Forwarded-Prefix`
added on #5602.

This PR adds that, and additionally introduces a new PDK function:
```lua
local path = kong.request.get_forwarded_path()
```

### Issues Resolved

Close #5602
  • Loading branch information
bungle authored Mar 11, 2020
1 parent c9416f5 commit 8773aab
Show file tree
Hide file tree
Showing 7 changed files with 388 additions and 108 deletions.
42 changes: 39 additions & 3 deletions kong/pdk/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ local function new(self)
local X_FORWARDED_PROTO = "X-Forwarded-Proto"
local X_FORWARDED_HOST = "X-Forwarded-Host"
local X_FORWARDED_PORT = "X-Forwarded-Port"
local X_FORWARDED_PREFIX = "X-Forwarded-Prefix"


---
Expand Down Expand Up @@ -149,7 +150,7 @@ local function new(self)
-- `X-Forwarded-Host` if it comes from a trusted source. The returned value
-- is normalized to lower-case.
--
-- Whether this function considers `X-Forwarded-Proto` or not depends on
-- Whether this function considers `X-Forwarded-Host` or not depends on
-- several Kong configuration parameters:
--
-- * [trusted\_ips](https://getkong.org/docs/latest/configuration/#trusted_ips)
Expand Down Expand Up @@ -199,9 +200,9 @@ local function new(self)
-- **Note**: we do not currently offer support for Forwarded HTTP Extension
-- (RFC 7239) since it is not supported by ngx_http_realip_module.
--
-- @function kong.request.get_forwareded_port
-- @function kong.request.get_forwarded_port
-- @phases rewrite, access, header_filter, body_filter, log, admin_api
-- @treturn number the forwared port
-- @treturn number the forwarded port
-- @usage
-- kong.request.get_forwarded_port() -- 1234
function _REQUEST.get_forwarded_port()
Expand Down Expand Up @@ -235,6 +236,41 @@ local function new(self)
end


---
-- Returns the path component of the request's URL, but also considers
-- `X-Forwarded-Prefix` if it comes from a trusted source. The value
-- is returned as a Lua string.
--
-- Whether this function considers `X-Forwarded-Prefix` or not depends on
-- several Kong configuration parameters:
--
-- * [trusted\_ips](https://getkong.org/docs/latest/configuration/#trusted_ips)
-- * [real\_ip\_header](https://getkong.org/docs/latest/configuration/#real_ip_header)
-- * [real\_ip\_recursive](https://getkong.org/docs/latest/configuration/#real_ip_recursive)
--
-- **Note**: we do not currently do any normalization on the request
-- path except return `"/"` on empty path.
--
-- @function kong.request.get_forwarded_path
-- @phases rewrite, access, header_filter, body_filter, log, admin_api
-- @treturn string the forwarded path
-- @usage
-- kong.request.get_forwarded_path() -- /path
function _REQUEST.get_forwarded_path()
check_phase(PHASES.request)

if self.ip.is_trusted(self.client.get_ip()) then
local prefix = _REQUEST.get_header(X_FORWARDED_PREFIX)
if prefix then
return prefix
end
end

local path = _REQUEST.get_path()
return path == "" and "/" or path
end


---
-- Returns the HTTP version used by the client in the request as a Lua
-- number, returning values such as `1`, `1.1`, `2.0`, or `nil` for
Expand Down
42 changes: 29 additions & 13 deletions kong/runloop/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,7 @@ return {
local forwarded_proto
local forwarded_host
local forwarded_port
local forwarded_prefix

-- X-Forwarded-* Headers Parsing
--
Expand All @@ -1041,14 +1042,27 @@ return {

local trusted_ip = kong.ip.is_trusted(realip_remote_addr)
if trusted_ip then
forwarded_proto = var.http_x_forwarded_proto or scheme
forwarded_host = var.http_x_forwarded_host or host
forwarded_port = var.http_x_forwarded_port or port
forwarded_proto = var.http_x_forwarded_proto or scheme
forwarded_host = var.http_x_forwarded_host or host
forwarded_port = var.http_x_forwarded_port or port
forwarded_prefix = var.http_x_forwarded_prefix

else
forwarded_proto = scheme
forwarded_host = host
forwarded_port = port
forwarded_proto = scheme
forwarded_host = host
forwarded_port = port
end

if not forwarded_prefix then
forwarded_prefix = var.request_uri
local p = find(forwarded_prefix, "?", 2, true)
if p then
forwarded_prefix = sub(forwarded_prefix, 1, p - 1)
end

if forwarded_prefix == "" then
forwarded_prefix = "/"
end
end

local protocols = route.protocols
Expand All @@ -1064,10 +1078,11 @@ return {
})
end

if redirect_status_code == 301 or
redirect_status_code == 302 or
redirect_status_code == 307 or
redirect_status_code == 308 then
if redirect_status_code == 301
or redirect_status_code == 302
or redirect_status_code == 307
or redirect_status_code == 308
then
header["Location"] = "https://" .. forwarded_host .. var.request_uri
return kong.response.exit(redirect_status_code)
end
Expand Down Expand Up @@ -1134,9 +1149,10 @@ return {
var.upstream_x_forwarded_for = var.remote_addr
end

var.upstream_x_forwarded_proto = forwarded_proto
var.upstream_x_forwarded_host = forwarded_host
var.upstream_x_forwarded_port = forwarded_port
var.upstream_x_forwarded_proto = forwarded_proto
var.upstream_x_forwarded_host = forwarded_host
var.upstream_x_forwarded_port = forwarded_port
var.upstream_x_forwarded_prefix = forwarded_prefix

-- At this point, the router and `balancer_setup_stage1` have been
-- executed; detect requests that need to be redirected from `proxy_pass`
Expand Down
97 changes: 51 additions & 46 deletions kong/templates/nginx_kong.lua
Original file line number Diff line number Diff line change
Expand Up @@ -123,31 +123,33 @@ server {
}
location / {
default_type '';
set $ctx_ref '';
set $upstream_te '';
set $upstream_host '';
set $upstream_upgrade '';
set $upstream_connection '';
set $upstream_scheme '';
set $upstream_uri '';
set $upstream_x_forwarded_for '';
set $upstream_x_forwarded_proto '';
set $upstream_x_forwarded_host '';
set $upstream_x_forwarded_port '';
set $kong_proxy_mode 'http';
default_type '';
set $ctx_ref '';
set $upstream_te '';
set $upstream_host '';
set $upstream_upgrade '';
set $upstream_connection '';
set $upstream_scheme '';
set $upstream_uri '';
set $upstream_x_forwarded_for '';
set $upstream_x_forwarded_proto '';
set $upstream_x_forwarded_host '';
set $upstream_x_forwarded_port '';
set $upstream_x_forwarded_prefix '';
set $kong_proxy_mode 'http';
proxy_http_version 1.1;
proxy_set_header TE $upstream_te;
proxy_set_header Host $upstream_host;
proxy_set_header Upgrade $upstream_upgrade;
proxy_set_header Connection $upstream_connection;
proxy_set_header X-Forwarded-For $upstream_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
proxy_set_header X-Forwarded-Host $upstream_x_forwarded_host;
proxy_set_header X-Forwarded-Port $upstream_x_forwarded_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header TE $upstream_te;
proxy_set_header Host $upstream_host;
proxy_set_header Upgrade $upstream_upgrade;
proxy_set_header Connection $upstream_connection;
proxy_set_header X-Forwarded-For $upstream_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
proxy_set_header X-Forwarded-Host $upstream_x_forwarded_host;
proxy_set_header X-Forwarded-Port $upstream_x_forwarded_port;
proxy_set_header X-Forwarded-Prefix $upstream_x_forwarded_prefix;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass_header Server;
proxy_pass_header Date;
proxy_ssl_name $upstream_host;
Expand All @@ -164,13 +166,14 @@ server {
default_type '';
set $kong_proxy_mode 'grpc';
grpc_set_header TE $upstream_te;
grpc_set_header Host $upstream_host;
grpc_set_header X-Forwarded-For $upstream_x_forwarded_for;
grpc_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
grpc_set_header X-Forwarded-Host $upstream_x_forwarded_host;
grpc_set_header X-Forwarded-Port $upstream_x_forwarded_port;
grpc_set_header X-Real-IP $remote_addr;
grpc_set_header TE $upstream_te;
grpc_set_header Host $upstream_host;
grpc_set_header X-Forwarded-For $upstream_x_forwarded_for;
grpc_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
grpc_set_header X-Forwarded-Host $upstream_x_forwarded_host;
grpc_set_header X-Forwarded-Port $upstream_x_forwarded_port;
grpc_set_header X-Forwarded-Prefix $upstream_x_forwarded_prefix;
grpc_set_header X-Real-IP $remote_addr;
grpc_pass_header Server;
grpc_pass_header Date;
grpc_pass grpc://kong_upstream;
Expand All @@ -181,13 +184,14 @@ server {
default_type '';
set $kong_proxy_mode 'grpc';
grpc_set_header TE $upstream_te;
grpc_set_header Host $upstream_host;
grpc_set_header X-Forwarded-For $upstream_x_forwarded_for;
grpc_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
grpc_set_header X-Forwarded-Host $upstream_x_forwarded_host;
grpc_set_header X-Forwarded-Port $upstream_x_forwarded_port;
grpc_set_header X-Real-IP $remote_addr;
grpc_set_header TE $upstream_te;
grpc_set_header Host $upstream_host;
grpc_set_header X-Forwarded-For $upstream_x_forwarded_for;
grpc_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
grpc_set_header X-Forwarded-Host $upstream_x_forwarded_host;
grpc_set_header X-Forwarded-Port $upstream_x_forwarded_port;
grpc_set_header X-Forwarded-Prefix $upstream_x_forwarded_prefix;
grpc_set_header X-Real-IP $remote_addr;
grpc_pass_header Server;
grpc_pass_header Date;
grpc_ssl_name $upstream_host;
Expand All @@ -211,15 +215,16 @@ server {
log_by_lua_block {;}
proxy_http_version 1.1;
proxy_set_header TE $upstream_te;
proxy_set_header Host $upstream_host;
proxy_set_header Upgrade $upstream_upgrade;
proxy_set_header Connection $upstream_connection;
proxy_set_header X-Forwarded-For $upstream_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
proxy_set_header X-Forwarded-Host $upstream_x_forwarded_host;
proxy_set_header X-Forwarded-Port $upstream_x_forwarded_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header TE $upstream_te;
proxy_set_header Host $upstream_host;
proxy_set_header Upgrade $upstream_upgrade;
proxy_set_header Connection $upstream_connection;
proxy_set_header X-Forwarded-For $upstream_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $upstream_x_forwarded_proto;
proxy_set_header X-Forwarded-Host $upstream_x_forwarded_host;
proxy_set_header X-Forwarded-Port $upstream_x_forwarded_port;
proxy_set_header X-Forwarded-Prefix $upstream_x_forwarded_prefix;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass_header Server;
proxy_pass_header Date;
proxy_ssl_name $upstream_host;
Expand Down
60 changes: 60 additions & 0 deletions spec/02-integration/05-proxy/03-upstream_headers_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,25 @@ for _, strategy in helpers.each_strategy() do
end)
end)

describe("X-Forwarded-Prefix", function()
it("should be added if not present in request", function()
local headers = request_headers {
["Host"] = "headers-inspect.com",
}

assert.equal("/", headers["x-forwarded-prefix"])
end)

it("should be replaced if present in request", function()
local headers = request_headers {
["Host"] = "headers-inspect.com",
["X-Forwarded-Prefix"] = "/replaced",
}

assert.equal("/", headers["x-forwarded-prefix"])
end)
end)

describe("with the downstream host preserved", function()
it("should be added if not present in request while preserving the downstream host", function()
local headers = request_headers {
Expand All @@ -296,6 +315,7 @@ for _, strategy in helpers.each_strategy() do
assert.equal("http", headers["x-forwarded-proto"])
assert.equal("preserved.com", headers["x-forwarded-host"])
assert.equal(helpers.get_proxy_port(false), tonumber(headers["x-forwarded-port"]))
assert.equal("/", headers["x-forwarded-prefix"])
end)

it("should be added if present in request while preserving the downstream host", function()
Expand All @@ -314,6 +334,7 @@ for _, strategy in helpers.each_strategy() do
assert.equal("http", headers["x-forwarded-proto"])
assert.equal("preserved.com", headers["x-forwarded-host"])
assert.equal(helpers.get_proxy_port(false), tonumber(headers["x-forwarded-port"]))
assert.equal("/", headers["x-forwarded-prefix"])
end)
end)

Expand All @@ -331,6 +352,7 @@ for _, strategy in helpers.each_strategy() do
assert.equal("http", headers["x-forwarded-proto"])
assert.equal("headers-inspect.com", headers["x-forwarded-host"])
assert.equal(helpers.get_proxy_port(false), tonumber(headers["x-forwarded-port"]))
assert.equal("/", headers["x-forwarded-prefix"])
end)

it("if present in request while discarding the downstream host", function()
Expand All @@ -351,6 +373,7 @@ for _, strategy in helpers.each_strategy() do
assert.equal("http", headers["x-forwarded-proto"])
assert.equal("headers-inspect.com", headers["x-forwarded-host"])
assert.equal(helpers.get_proxy_port(false), tonumber(headers["x-forwarded-port"]))
assert.equal("/", headers["x-forwarded-prefix"])
end)
end)

Expand Down Expand Up @@ -461,7 +484,25 @@ for _, strategy in helpers.each_strategy() do

assert.equal("80", headers["x-forwarded-port"])
end)
end)

describe("X-Forwarded-Prefix", function()
it("should be added if not present in request", function()
local headers = request_headers {
["Host"] = "headers-inspect.com",
}

assert.equal("/", headers["x-forwarded-prefix"])
end)

it("should be forwarded if present in request", function()
local headers = request_headers {
["Host"] = "headers-inspect.com",
["X-Forwarded-Prefix"] = "/original-path",
}

assert.equal("/original-path", headers["x-forwarded-prefix"])
end)
end)

end)
Expand Down Expand Up @@ -572,6 +613,25 @@ for _, strategy in helpers.each_strategy() do
assert.equal(helpers.get_proxy_port(false), tonumber(headers["x-forwarded-port"]))
end)
end)

describe("X-Forwarded-Prefix", function()
it("should be added if not present in request", function()
local headers = request_headers {
["Host"] = "headers-inspect.com",
}

assert.equal("/", headers["x-forwarded-prefix"])
end)

it("should be replaced if present in request", function()
local headers = request_headers {
["Host"] = "headers-inspect.com",
["X-Forwarded-Prefix"] = "/untrusted",
}

assert.equal("/", headers["x-forwarded-prefix"])
end)
end)
end)

describe("(using the recursive trusted configuration values)", function()
Expand Down
Loading

0 comments on commit 8773aab

Please sign in to comment.