This is an automated email from the ASF dual-hosted git repository.
nic-6443 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 6c700c5dc3 fix(traffic-split): apply tls and keepalive_pool of the
inline upstream (#14054)
6c700c5dc3 is described below
commit 6c700c5dc37420ee4e8a55c920d989cab78dd40b
Author: Nic <[email protected]>
AuthorDate: Mon Oct 26 10:20:51 2026 +0800
fix(traffic-split): apply tls and keepalive_pool of the inline upstream
(#14054)
---
apisix/init.lua | 7 +-
apisix/plugins/traffic-split.lua | 4 +-
apisix/upstream.lua | 96 +++++----
docs/en/latest/plugins/traffic-split.md | 2 +
docs/zh/latest/plugins/traffic-split.md | 2 +
t/plugin/traffic-split5.t | 363 ++++++++++++++++++++++++++++++++
6 files changed, 425 insertions(+), 49 deletions(-)
diff --git a/apisix/init.lua b/apisix/init.lua
index 4a8b71dc09..0fb6cceb61 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -611,12 +611,13 @@ end
-- into `api_ctx.upstream_ssl`. Shared by the http and stream subsystems.
-- Returns false on error (invalid/missing referenced ssl object).
local function resolve_upstream_client_cert(api_ctx)
- if not (api_ctx.matched_upstream and api_ctx.matched_upstream.tls and
- api_ctx.matched_upstream.tls.client_cert_id) then
+ -- upstream_conf is already set here only when traffic-split picked an
inline upstream
+ local up_conf = api_ctx.upstream_conf or api_ctx.matched_upstream
+ if not (up_conf and up_conf.tls and up_conf.tls.client_cert_id) then
return true
end
- local cert_id = api_ctx.matched_upstream.tls.client_cert_id
+ local cert_id = up_conf.tls.client_cert_id
local upstream_ssl = router.router_ssl.get_by_id(cert_id)
if not upstream_ssl or upstream_ssl.type ~= "client" then
local err = upstream_ssl and
diff --git a/apisix/plugins/traffic-split.lua b/apisix/plugins/traffic-split.lua
index f01f719c78..2eb492f062 100644
--- a/apisix/plugins/traffic-split.lua
+++ b/apisix/plugins/traffic-split.lua
@@ -183,7 +183,9 @@ local function set_upstream(upstream_info, ctx)
key = upstream_info.key,
nodes = new_nodes,
timeout = upstream_info.timeout,
- scheme = upstream_info.scheme
+ scheme = upstream_info.scheme,
+ tls = upstream_info.tls,
+ keepalive_pool = upstream_info.keepalive_pool,
}
local ok, err = upstream.check_schema(up_conf)
diff --git a/apisix/upstream.lua b/apisix/upstream.lua
index 9ccfeaaa66..1694e9dec0 100644
--- a/apisix/upstream.lua
+++ b/apisix/upstream.lua
@@ -329,6 +329,55 @@ local function set_stream_upstream_client_cert(api_ctx,
up_conf)
end
+local function set_http_upstream_tls(api_ctx, up_conf)
+ local scheme = up_conf.scheme
+ if scheme == "https" then
+ -- grpcs applies these in `set_grpcs_upstream_param` instead, after the
+ -- internal redirect that drops the settings made here
+ local ok, err = set_upstream_ssl_opts(up_conf)
+ if not ok then
+ return 503, err
+ end
+ end
+
+ local tls_has_cert = up_conf.tls and (up_conf.tls.client_cert or
up_conf.tls.client_cert_id)
+ if (scheme == "https" or scheme == "grpcs") and tls_has_cert then
+ local client_cert, client_key
+ if up_conf.tls.client_cert_id then
+ client_cert = api_ctx.upstream_ssl.cert
+ client_key = api_ctx.upstream_ssl.key
+ else
+ client_cert = up_conf.tls.client_cert
+ client_key = up_conf.tls.client_key
+ end
+
+ -- the sni here is just for logging
+ local sni = api_ctx.var.upstream_host
+ local cert, err = apisix_ssl.fetch_cert(sni, client_cert)
+ if not cert then
+ return 503, err
+ end
+
+ local key, err = apisix_ssl.fetch_pkey(sni, client_key)
+ if not key then
+ return 503, err
+ end
+
+ if scheme == "grpcs" then
+ api_ctx.upstream_grpcs_cert = cert
+ api_ctx.upstream_grpcs_key = key
+ else
+ local ok, err = set_upstream_tls_client_param(cert, key)
+ if not ok then
+ return 503, err
+ end
+ end
+ end
+
+ return
+end
+
+
function _M.set_by_route(route, api_ctx)
-- Ahead of the traffic-split short circuit below so its inline upstream is
-- covered too. A second handshake would send the client's ClientHello as
payload.
@@ -341,7 +390,7 @@ function _M.set_by_route(route, api_ctx)
if api_ctx.upstream_conf then
-- upstream_conf has been set by traffic-split plugin
- return
+ return set_http_upstream_tls(api_ctx, api_ctx.upstream_conf)
end
local up_conf = api_ctx.matched_upstream
@@ -449,51 +498,8 @@ function _M.set_by_route(route, api_ctx)
node_ver )
local checker = healthcheck_manager.fetch_checker(up_conf.resource_key,
resource_version)
api_ctx.up_checker = checker
- local scheme = up_conf.scheme
- if scheme == "https" then
- -- grpcs applies these in `set_grpcs_upstream_param` instead, after the
- -- internal redirect that drops the settings made here
- local ok, err = set_upstream_ssl_opts(up_conf)
- if not ok then
- return 503, err
- end
- end
-
- local tls_has_cert = up_conf.tls and (up_conf.tls.client_cert or
up_conf.tls.client_cert_id)
- if (scheme == "https" or scheme == "grpcs") and tls_has_cert then
- local client_cert, client_key
- if up_conf.tls.client_cert_id then
- client_cert = api_ctx.upstream_ssl.cert
- client_key = api_ctx.upstream_ssl.key
- else
- client_cert = up_conf.tls.client_cert
- client_key = up_conf.tls.client_key
- end
-
- -- the sni here is just for logging
- local sni = api_ctx.var.upstream_host
- local cert, err = apisix_ssl.fetch_cert(sni, client_cert)
- if not cert then
- return 503, err
- end
- local key, err = apisix_ssl.fetch_pkey(sni, client_key)
- if not key then
- return 503, err
- end
-
- if scheme == "grpcs" then
- api_ctx.upstream_grpcs_cert = cert
- api_ctx.upstream_grpcs_key = key
- else
- local ok, err = set_upstream_tls_client_param(cert, key)
- if not ok then
- return 503, err
- end
- end
- end
-
- return
+ return set_http_upstream_tls(api_ctx, up_conf)
end
diff --git a/docs/en/latest/plugins/traffic-split.md
b/docs/en/latest/plugins/traffic-split.md
index c334010706..c43024bcae 100644
--- a/docs/en/latest/plugins/traffic-split.md
+++ b/docs/en/latest/plugins/traffic-split.md
@@ -64,6 +64,8 @@ The traffic ratio between Upstream services may be less
accurate since round rob
| rules.weighted_upstreams.upstream.pass_host | string |
False | "pass" | ["pass", "node", "rewrite"] | Mode deciding how the
host name is passed. `pass` passes the client's host name to the Upstream.
`node` passes the host configured in the node of the Upstream. `rewrite` passes
the value configured in `upstream_host`. |
| rules.weighted_upstreams.upstream.name | string |
False | | | Identifier for the
Upstream for specifying service name, usage scenarios, and so on. |
| rules.weighted_upstreams.upstream.upstream_host | string |
False | | | Used when `pass_host` is
`rewrite`. Host name of the Upstream. |
+| rules.weighted_upstreams.upstream.tls | object |
False | | | TLS settings used when
connecting to the Upstream. See `tls` of [Upstream](../admin-api.md#upstream)
for the supported fields. |
+| rules.weighted_upstreams.upstream.keepalive_pool | object |
False | | | Connection pool settings
for the Upstream. See `keepalive_pool` of [Upstream](../admin-api.md#upstream)
for the supported fields. |
## Examples
diff --git a/docs/zh/latest/plugins/traffic-split.md
b/docs/zh/latest/plugins/traffic-split.md
index 547ca9d612..f28c20b257 100644
--- a/docs/zh/latest/plugins/traffic-split.md
+++ b/docs/zh/latest/plugins/traffic-split.md
@@ -64,6 +64,8 @@ import TabItem from '@theme/TabItem';
| rules.weighted_upstreams.upstream.pass_host | string | 否 | "pass" | ["pass",
"node", "rewrite"] | 决定如何传递主机名的模式。`pass` 将客户端的主机名传递给上游。`node`
传递上游节点中配置的主机。`rewrite` 传递 `upstream_host` 中配置的值。|
| rules.weighted_upstreams.upstream.name | string | 否 | | |
用于指定服务名称、使用场景等的上游标识符。|
| rules.weighted_upstreams.upstream.upstream_host | string | 否 | | | 当
`pass_host` 为 `rewrite` 时使用。上游的主机名。|
+| rules.weighted_upstreams.upstream.tls | object | 否 | | | 连接上游时使用的 TLS
配置,支持的字段参见[上游](../admin-api.md#upstream)的 `tls`。|
+| rules.weighted_upstreams.upstream.keepalive_pool | object | 否 | | |
上游的连接池配置,支持的字段参见[上游](../admin-api.md#upstream)的 `keepalive_pool`。|
## 示例
diff --git a/t/plugin/traffic-split5.t b/t/plugin/traffic-split5.t
index 01df76e3cb..d029d26032 100644
--- a/t/plugin/traffic-split5.t
+++ b/t/plugin/traffic-split5.t
@@ -634,3 +634,366 @@ nginx_config:
GET /t
--- response_body
passed.
+
+
+
+=== TEST 13: inline upstream with tls.client_cert and tls.client_key
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin")
+ local json = require("toolkit.json")
+ local data = {
+ uri = "/server_port",
+ plugins = {
+ ["traffic-split"] = {
+ rules = {
+ {
+ weighted_upstreams = {
+ {
+ upstream = {
+ scheme = "https",
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1983"] = 1
+ },
+ tls = {
+ client_cert =
t.read_file("t/certs/mtls_client.crt"),
+ client_key =
t.read_file("t/certs/mtls_client.key"),
+ }
+ },
+ weight = 1
+ }
+ }
+ }
+ }
+ }
+ },
+ upstream = {
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1980"] = 1
+ }
+ }
+ }
+ local code, body = t.test('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ json.encode(data)
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 14: hit, the client certificate is sent to the upstream
+--- upstream_server_config
+ ssl_client_certificate ../../certs/mtls_ca.crt;
+ ssl_verify_client on;
+--- request
+GET /server_port
+--- response_body chomp
+1983
+
+
+
+=== TEST 15: inline upstream with tls.client_cert_id
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin")
+ local json = require("toolkit.json")
+ local code, body = t.test('/apisix/admin/ssls/1',
+ ngx.HTTP_PUT,
+ json.encode({
+ type = "client",
+ cert = t.read_file("t/certs/mtls_client.crt"),
+ key = t.read_file("t/certs/mtls_client.key"),
+ })
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ local data = {
+ uri = "/server_port",
+ plugins = {
+ ["traffic-split"] = {
+ rules = {
+ {
+ weighted_upstreams = {
+ {
+ upstream = {
+ scheme = "https",
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1983"] = 1
+ },
+ tls = {
+ client_cert_id = 1
+ }
+ },
+ weight = 1
+ }
+ }
+ }
+ }
+ }
+ },
+ upstream = {
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1980"] = 1
+ }
+ }
+ }
+ local code, body = t.test('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ json.encode(data)
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 16: hit, the client certificate referenced by id is sent to the
upstream
+--- upstream_server_config
+ ssl_client_certificate ../../certs/mtls_ca.crt;
+ ssl_verify_client on;
+--- request
+GET /server_port
+--- response_body chomp
+1983
+
+
+
+=== TEST 17: inline upstream with tls.verify and tls.ca_certs
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin")
+ local json = require("toolkit.json")
+ local data = {
+ uri = "/server_port",
+ plugins = {
+ ["traffic-split"] = {
+ rules = {
+ {
+ weighted_upstreams = {
+ {
+ upstream = {
+ scheme = "https",
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1983"] = 1
+ },
+ tls = {
+ verify = true,
+ ca_certs =
{t.read_file("t/certs/mtls_ca.crt")}
+ }
+ },
+ weight = 1
+ }
+ }
+ }
+ }
+ }
+ },
+ upstream = {
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1980"] = 1
+ }
+ }
+ }
+ local code, body = t.test('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ json.encode(data)
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 18: hit, the upstream certificate is not signed by the configured CA
+--- request
+GET /server_port
+--- error_code: 502
+--- error_log
+upstream SSL certificate verify error
+
+
+
+=== TEST 19: inline upstream with tls.verify and the CA that signed the
upstream certificate
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin")
+ local json = require("toolkit.json")
+ local data = {
+ uri = "/server_port",
+ plugins = {
+ ["traffic-split"] = {
+ rules = {
+ {
+ weighted_upstreams = {
+ {
+ upstream = {
+ scheme = "https",
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1983"] = 1
+ },
+ pass_host = "rewrite",
+ upstream_host = "test.com",
+ tls = {
+ verify = true,
+ ca_certs =
{t.read_file("t/certs/apisix.crt")}
+ }
+ },
+ weight = 1
+ }
+ }
+ }
+ }
+ }
+ },
+ upstream = {
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1980"] = 1
+ }
+ }
+ }
+ local code, body = t.test('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ json.encode(data)
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 20: hit, the upstream certificate is verified against the configured
CA
+--- request
+GET /server_port
+--- response_body chomp
+1983
+
+
+
+=== TEST 21: inline upstream with keepalive_pool
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin")
+ local json = require("toolkit.json")
+ local data = {
+ uri = "/hello",
+ plugins = {
+ ["traffic-split"] = {
+ rules = {
+ {
+ weighted_upstreams = {
+ {
+ upstream = {
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1970"] = 1
+ },
+ keepalive_pool = {
+ size = 4,
+ idle_timeout = 8,
+ requests = 2
+ }
+ },
+ weight = 1
+ }
+ }
+ }
+ }
+ }
+ },
+ upstream = {
+ type = "roundrobin",
+ nodes = {
+ ["127.0.0.1:1980"] = 1
+ }
+ }
+ }
+ local code, body = t.test('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ json.encode(data)
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 22: hit, the connection pool follows keepalive_pool of the inline
upstream
+--- log_level: debug
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require "resty.http"
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port
+ .. "/hello"
+ for i = 1, 3 do
+ local httpc = http.new()
+ local res, err = httpc:request_uri(uri)
+ if not res then
+ ngx.say(err)
+ return
+ end
+ ngx.print(res.body)
+ end
+ }
+ }
+--- response_body
+1970
+1970
+1970
+--- grep_error_log eval
+qr/lua balancer: keepalive .*/
+--- grep_error_log_out eval
+qr/^lua balancer: keepalive create pool, name: (\S+), size: 4
+lua balancer: keepalive no free connection, cpool: \S+
+lua balancer: keepalive saving connection \S+, cpool: \S+, connections: 1
+lua balancer: keepalive reusing connection \S+, requests: 1, cpool: \S+
+lua balancer: keepalive not saving connection \S+, cpool: \S+, connections: 0
+lua balancer: keepalive free pool \S+, name: \1
+lua balancer: keepalive create pool, name: \1, size: 4
+lua balancer: keepalive no free connection, cpool: \S+
+lua balancer: keepalive saving connection \S+, cpool: \S+, connections: 1
+$/