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
+$/

Reply via email to