Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(balancer): support upstream host_header and router preserve_host config for stream tls proxy #11244

Merged
merged 24 commits into from
Jul 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@

- Enable `expressions` and `traditional_compatible` router flavor in stream subsystem.
[#11071](https://github.com/Kong/kong/pull/11071)
- Make upstream `host_header` and router `preserve_host` config work in stream tls proxy.
oowl marked this conversation as resolved.
Show resolved Hide resolved
[#11244](https://github.com/Kong/kong/pull/11244)

#### Admin API

Expand Down
1 change: 1 addition & 0 deletions kong/router/atc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,7 @@ function _M:select(_, _, _, scheme,
port = service_port,
},
upstream_scheme = service_protocol,
upstream_host = matched_route.preserve_host and sni or nil,
}
end

Expand Down
4 changes: 4 additions & 0 deletions kong/router/traditional.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,10 @@ local function find_match(ctx)
end
end

if matched_route.preserve_host and upstream_host == nil then
upstream_host = ctx.sni
end

return {
route = matched_route.route,
service = matched_route.service,
Expand Down
7 changes: 6 additions & 1 deletion kong/runloop/balancer/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ local table_concat = table.concat
local run_hook = hooks.run_hook
local var = ngx.var
local get_updated_now_ms = utils.get_updated_now_ms
local is_http_module = ngx.config.subsystem == "http"

local CRIT = ngx.CRIT
local ERR = ngx.ERR
Expand Down Expand Up @@ -472,7 +473,11 @@ local function set_host_header(balancer_data, upstream_scheme, upstream_host, is

var.upstream_host = new_upstream_host

if is_balancer_phase then
-- stream module does not support ngx.balancer.recreate_request
-- and we do not need to recreate the request in balancer_by_lua
-- some nginx proxy variables will compile when init upstream ssl connection
-- https://github.com/nginx/nginx/blob/master/src/stream/ngx_stream_proxy_module.c#L1070
if is_balancer_phase and is_http_module then
return recreate_request()
end
end
Expand Down
25 changes: 24 additions & 1 deletion kong/runloop/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1140,13 +1140,36 @@ return {
upstream_url_t.host,
upstream_url_t.port,
service, route)
var.upstream_host = upstream_url_t.host
if match_t.upstream_host then
var.upstream_host = match_t.upstream_host
end
end,
after = function(ctx)
local upstream_scheme = var.upstream_scheme

local balancer_data = ctx.balancer_data
balancer_data.scheme = upstream_scheme -- COMPAT: pdk

-- The content of var.upstream_host is only set by the router if
-- preserve_host is true
--
-- We can't rely on var.upstream_host for balancer retries inside
-- `set_host_header` because it would never be empty after the first -- balancer try
local upstream_host = var.upstream_host
oowl marked this conversation as resolved.
Show resolved Hide resolved
if upstream_host ~= nil and upstream_host ~= "" then
balancer_data.preserve_host = true
end

local ok, err, errcode = balancer_execute(ctx)
if not ok then
return kong.response.error(errcode, err)
end

local ok, err = balancer.set_host_header(balancer_data, upstream_scheme, upstream_host)
if not ok then
log(ERR, "failed to set balancer Host header: ", err)
return exit(500)
end
end
},
rewrite = {
Expand Down
161 changes: 161 additions & 0 deletions spec/02-integration/05-proxy/31-stream_tls_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
local helpers = require "spec.helpers"

for _, flavor in ipairs({ "traditional", "traditional_compatible" }) do
for _, strategy in helpers.each_strategy({"postgres"}) do
describe("#stream Proxying [#" .. strategy .. "] [#" .. flavor .. "]", function()
local bp
local admin_client

before_each(function()
bp = helpers.get_db_utils(strategy, {
"routes",
"services",
"upstreams",
"plugins",
}, {
"logger",
})

local upstream_srv = bp.upstreams:insert({
name = "upstream_srv",
})

bp.targets:insert {
target = helpers.mock_upstream_host .. ":" ..
helpers.mock_upstream_stream_ssl_port,
upstream = { id = upstream_srv.id },
}

local tls_srv = bp.services:insert({
name = "tls",
url = "tls://upstream_srv",
})

bp.routes:insert {
name = "routes_stream",
destinations = {
{
port = 19443,
},
},
protocols = {
"tls",
},
service = tls_srv,
}

bp.plugins:insert {
name = "logger",
}

assert(helpers.start_kong({
database = strategy,
nginx_conf = "spec/fixtures/custom_nginx.template",
plugins = "logger",
stream_listen = helpers.get_proxy_ip(false) .. ":19000,"
.. helpers.get_proxy_ip(false) .. ":19001,"
.. helpers.get_proxy_ip(false) .. ":19002,"
.. helpers.get_proxy_ip(false) .. ":19003,"
.. helpers.get_proxy_ip(false) .. ":19443 ssl",
proxy_stream_error_log = "/tmp/error.log",
router_flavor = flavor,
}))
admin_client = helpers.http_client("127.0.0.1", 9001)
end)

after_each(function()
admin_client:close()
helpers.stop_kong()
end)

it("tls not set host_header", function()
local tcp = ngx.socket.tcp()
assert(tcp:connect(helpers.get_proxy_ip(true), 19443))
assert(tcp:sslhandshake(nil, "ssl-hello.com", false))
assert(tcp:send("get_sni\n"))
local body = assert(tcp:receive("*a"))
assert.equal("nil\n", body)
tcp:close()
end)

it("tls set preserve_host", function()
local res = assert(admin_client:send {
method = "PATCH",
path = "/routes/routes_stream",
body = {
preserve_host = true,
},
headers = {
["Content-Type"] = "application/json"
}
})
assert.res_status(200, res)
local opt = {
stream_enabled = true,
stream_ip = "127.0.0.1",
stream_port = 19003,
timeout = 60,
}
helpers.wait_for_all_config_update(opt)

local tcp = ngx.socket.tcp()
assert(tcp:connect(helpers.get_proxy_ip(true), 19443))
assert(tcp:sslhandshake(nil, "ssl-hello.com", false))
assert(tcp:send("get_sni\n"))
local body = assert(tcp:receive("*a"))
assert.equal("ssl-hello.com\n", body)
tcp:close()
end)

it("tls set host_header", function()
-- clear preserve_host
local res = assert(admin_client:send {
method = "PATCH",
path = "/routes/routes_stream",
body = {
preserve_host = false,
},
headers = {
["Content-Type"] = "application/json"
}
})
assert.res_status(200, res)

local opt = {
stream_enabled = true,
stream_port = 19003
}
helpers.wait_for_all_config_update(opt)

local tcp = ngx.socket.tcp()
assert(tcp:connect(helpers.get_proxy_ip(true), 19443))
assert(tcp:sslhandshake(nil, "ssl-hello.com", false))
assert(tcp:send("get_sni\n"))
local body = assert(tcp:receive("*a"))
assert.equal("nil\n", body)
tcp:close()

local res = assert(admin_client:send {
method = "PATCH",
path = "/upstreams/upstream_srv",
body = {
host_header = "ssl-hello.com"
},
headers = {
["Content-Type"] = "application/json"
}
})
assert.res_status(200, res)
helpers.wait_for_all_config_update(opt)

local tcp = ngx.socket.tcp()
assert(tcp:connect(helpers.get_proxy_ip(true), 19443))
assert(tcp:sslhandshake(nil, "ssl-hello.com", false))
assert(tcp:send("get_sni\n"))
local body = assert(tcp:receive("*a"))
assert.equal("ssl-hello.com\n", body)
tcp:close()
end)
end)
end
end
6 changes: 6 additions & 0 deletions spec/fixtures/custom_nginx.template
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,12 @@ server {
local sock = assert(ngx.req.socket())
local data = sock:receive() -- read a line from downstream

if string.find(data, "get_sni") then
sock:send(ngx.var.ssl_server_name)
sock:send("\n")
return
end

if ngx.var.protocol == "TCP" then
ngx.say(data)

Expand Down
Loading