This is an automated email from the ASF dual-hosted git repository.

baoyuan 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 2e0b6b2a6 fix: address TLS security vulnerabilities in SSL log, OIDC 
encryption, and K8s ssl_verify (#13190)
2e0b6b2a6 is described below

commit 2e0b6b2a69c66fb10b33b947dbdcbfa5699e6f75
Author: AlinsRan <[email protected]>
AuthorDate: Mon Apr 13 11:24:54 2026 +0800

    fix: address TLS security vulnerabilities in SSL log, OIDC encryption, and 
K8s ssl_verify (#13190)
---
 apisix/discovery/kubernetes/informer_factory.lua |   2 +-
 apisix/discovery/kubernetes/init.lua             |   7 +
 apisix/discovery/kubernetes/schema.lua           |  12 ++
 apisix/ssl/router/radixtree_sni.lua              |   2 -
 t/discovery/kubernetes_ssl_verify.t              | 217 +++++++++++++++++++++++
 t/kubernetes/discovery/kubernetes2.t             |   1 +
 t/plugin/openid-connect2.t                       |  80 +++++++++
 t/router/radixtree-sni3.t                        |  36 ++++
 8 files changed, 354 insertions(+), 3 deletions(-)

diff --git a/apisix/discovery/kubernetes/informer_factory.lua 
b/apisix/discovery/kubernetes/informer_factory.lua
index d31841b1e..4d650d7bb 100644
--- a/apisix/discovery/kubernetes/informer_factory.lua
+++ b/apisix/discovery/kubernetes/informer_factory.lua
@@ -273,7 +273,7 @@ local function list_watch(informer, apiserver)
         scheme = apiserver.schema,
         host = apiserver.host,
         port = apiserver.port,
-        ssl_verify = false
+        ssl_verify = apiserver.ssl_verify
     })
 
     if not ok then
diff --git a/apisix/discovery/kubernetes/init.lua 
b/apisix/discovery/kubernetes/init.lua
index 0b31c392a..a6cf828e2 100644
--- a/apisix/discovery/kubernetes/init.lua
+++ b/apisix/discovery/kubernetes/init.lua
@@ -479,6 +479,13 @@ local function get_apiserver(conf)
         return nil, "apiserver.token should set to non-empty string when 
service.schema is https"
     end
 
+    -- ssl_verify: use explicit config if set, otherwise default to false
+    if conf.service.ssl_verify ~= nil then
+        apiserver.ssl_verify = conf.service.ssl_verify
+    else
+        apiserver.ssl_verify = false
+    end
+
     return apiserver
 end
 
diff --git a/apisix/discovery/kubernetes/schema.lua 
b/apisix/discovery/kubernetes/schema.lua
index 780aad255..b3f35ae72 100644
--- a/apisix/discovery/kubernetes/schema.lua
+++ b/apisix/discovery/kubernetes/schema.lua
@@ -129,6 +129,12 @@ return {
                             oneOf = port_patterns,
                             default = "${KUBERNETES_SERVICE_PORT}",
                         },
+                        ssl_verify = {
+                            type = "boolean",
+                            description = "Verify the TLS certificate of the 
Kubernetes API " ..
+                                          "server. Defaults to false. Set to 
true to enable " ..
+                                          "certificate verification.",
+                        },
                     },
                     default = {
                         schema = "https",
@@ -190,6 +196,12 @@ return {
                                 type = "string",
                                 oneOf = port_patterns,
                             },
+                            ssl_verify = {
+                                type = "boolean",
+                                description = "Verify the TLS certificate of 
the Kubernetes " ..
+                                              "API server. Defaults to false. 
Set to true to " ..
+                                              "enable certificate 
verification.",
+                            },
                         },
                         required = { "host", "port" }
                     },
diff --git a/apisix/ssl/router/radixtree_sni.lua 
b/apisix/ssl/router/radixtree_sni.lua
index 15ea67225..07ad262ba 100644
--- a/apisix/ssl/router/radixtree_sni.lua
+++ b/apisix/ssl/router/radixtree_sni.lua
@@ -214,8 +214,6 @@ function _M.match_and_set(api_ctx, match_only, alt_sni)
         end
     end
 
-    core.log.info("debug - matched: ", 
core.json.delay_encode(api_ctx.matched_ssl, true))
-
     if match_only then
         return true
     end
diff --git a/t/discovery/kubernetes_ssl_verify.t 
b/t/discovery/kubernetes_ssl_verify.t
new file mode 100644
index 000000000..68571797e
--- /dev/null
+++ b/t/discovery/kubernetes_ssl_verify.t
@@ -0,0 +1,217 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_shuffle();
+no_root_location();
+log_level("info");
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    if (!$block->error_log && !$block->no_error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: ssl_verify is false when apiserver uses http scheme (no explicit 
config)
+--- config
+    location /t {
+        content_by_lua_block {
+            local captured_opts = {}
+
+            -- Monkeypatch resty.http to capture connect options
+            local http_orig = require("resty.http")
+            local http_new_orig = http_orig.new
+            http_orig.new = function()
+                local mock = {}
+                mock.connect = function(self, opts)
+                    captured_opts.ssl_verify = opts and opts.ssl_verify
+                    -- simulate connection refused to stop further processing
+                    return false, "connection refused"
+                end
+                return mock
+            end
+
+            local factory = 
require("apisix.discovery.kubernetes.informer_factory")
+            local informer = factory.new(nil, "v1", "Endpoints", "endpoints", 
nil)
+
+            -- apiserver.ssl_verify is set by init.lua get_apiserver; simulate 
http result
+            local apiserver = {
+                schema = "http",
+                host = "127.0.0.1",
+                port = 6445,
+                ssl_verify = false,  -- what get_apiserver would set for http 
scheme
+            }
+
+            informer:list_watch(apiserver)
+            ngx.say("ssl_verify for http scheme: ", 
tostring(captured_opts.ssl_verify))
+
+            -- restore
+            http_orig.new = http_new_orig
+        }
+    }
+--- response_body
+ssl_verify for http scheme: false
+--- no_error_log
+[alert]
+
+
+
+=== TEST 2: ssl_verify defaults to false when apiserver uses https scheme (no 
explicit config)
+--- config
+    location /t {
+        content_by_lua_block {
+            local captured_opts = {}
+
+            -- Monkeypatch resty.http to capture connect options
+            local http_orig = require("resty.http")
+            local http_new_orig = http_orig.new
+            http_orig.new = function()
+                local mock = {}
+                mock.connect = function(self, opts)
+                    captured_opts.ssl_verify = opts and opts.ssl_verify
+                    -- simulate connection refused to stop further processing
+                    return false, "connection refused"
+                end
+                return mock
+            end
+
+            local factory = 
require("apisix.discovery.kubernetes.informer_factory")
+            local informer = factory.new(nil, "v1", "Endpoints", "endpoints", 
nil)
+
+            -- apiserver.ssl_verify is set by init.lua get_apiserver; default 
is false
+            local apiserver = {
+                schema = "https",
+                host = "127.0.0.1",
+                port = 6443,
+                ssl_verify = false,  -- default when no explicit config, even 
for https
+            }
+
+            informer:list_watch(apiserver)
+            ngx.say("ssl_verify for https scheme (no config): ", 
tostring(captured_opts.ssl_verify))
+
+            -- restore
+            http_orig.new = http_new_orig
+        }
+    }
+--- response_body
+ssl_verify for https scheme (no config): false
+--- no_error_log
+[alert]
+
+
+
+=== TEST 3: explicit ssl_verify=true enables certificate verification for https
+--- config
+    location /t {
+        content_by_lua_block {
+            local captured_opts = {}
+
+            -- Monkeypatch resty.http to capture connect options
+            local http_orig = require("resty.http")
+            local http_new_orig = http_orig.new
+            http_orig.new = function()
+                local mock = {}
+                mock.connect = function(self, opts)
+                    captured_opts.ssl_verify = opts and opts.ssl_verify
+                    return false, "connection refused"
+                end
+                return mock
+            end
+
+            local factory = 
require("apisix.discovery.kubernetes.informer_factory")
+            local informer = factory.new(nil, "v1", "Endpoints", "endpoints", 
nil)
+
+            -- Simulate get_apiserver with explicit ssl_verify=true (user opts 
in)
+            local apiserver = {
+                schema = "https",
+                host = "127.0.0.1",
+                port = 6443,
+                ssl_verify = true,  -- explicit opt-in for certificate 
verification
+            }
+
+            informer:list_watch(apiserver)
+            ngx.say("explicit ssl_verify=true respected: ", 
tostring(captured_opts.ssl_verify == true))
+
+            -- restore
+            http_orig.new = http_new_orig
+        }
+    }
+--- response_body
+explicit ssl_verify=true respected: true
+--- no_error_log
+[alert]
+
+
+
+=== TEST 4: get_apiserver defaults ssl_verify to false when service.ssl_verify 
is not configured
+--- config
+    location /t {
+        content_by_lua_block {
+            -- Verify the contract of get_apiserver() in init.lua:
+            -- when conf.service.ssl_verify is nil (not configured), 
ssl_verify must be false.
+            -- This is the backward-compatible default — NOT derived from the 
scheme.
+            --
+            -- The logic being tested (init.lua):
+            --   if conf.service.ssl_verify ~= nil then
+            --       apiserver.ssl_verify = conf.service.ssl_verify
+            --   else
+            --       apiserver.ssl_verify = false   <-- must be false, not 
(schema == "https")
+            --   end
+
+            local function compute_ssl_verify(service_conf)
+                if service_conf.ssl_verify ~= nil then
+                    return service_conf.ssl_verify
+                else
+                    return false
+                end
+            end
+
+            -- Case 1: https with no ssl_verify set -> must be false
+            local result1 = compute_ssl_verify({ schema = "https", host = 
"127.0.0.1", port = "6443" })
+            ngx.say("https, no ssl_verify -> false: ", tostring(result1 == 
false))
+
+            -- Case 2: http with no ssl_verify set -> must be false
+            local result2 = compute_ssl_verify({ schema = "http", host = 
"127.0.0.1", port = "6445" })
+            ngx.say("http, no ssl_verify -> false: ", tostring(result2 == 
false))
+
+            -- Case 3: explicit ssl_verify=true overrides default
+            local result3 = compute_ssl_verify({ schema = "https", host = 
"127.0.0.1", port = "6443", ssl_verify = true })
+            ngx.say("explicit ssl_verify=true -> true: ", tostring(result3 == 
true))
+
+            -- Case 4: explicit ssl_verify=false is preserved
+            local result4 = compute_ssl_verify({ schema = "https", host = 
"127.0.0.1", port = "6443", ssl_verify = false })
+            ngx.say("explicit ssl_verify=false -> false: ", tostring(result4 
== false))
+        }
+    }
+--- response_body
+https, no ssl_verify -> false: true
+http, no ssl_verify -> false: true
+explicit ssl_verify=true -> true: true
+explicit ssl_verify=false -> false: true
diff --git a/t/kubernetes/discovery/kubernetes2.t 
b/t/kubernetes/discovery/kubernetes2.t
index 9ec58f50c..41a3819d4 100644
--- a/t/kubernetes/discovery/kubernetes2.t
+++ b/t/kubernetes/discovery/kubernetes2.t
@@ -32,6 +32,7 @@ discovery:
       service:
         host: "127.0.0.1"
         port: "6443"
+        ssl_verify: false
       client:
         token_file: "/tmp/var/run/secrets/kubernetes.io/serviceaccount/token"
     - id: second
diff --git a/t/plugin/openid-connect2.t b/t/plugin/openid-connect2.t
index 44a4c63dc..e4760a11d 100644
--- a/t/plugin/openid-connect2.t
+++ b/t/plugin/openid-connect2.t
@@ -1001,3 +1001,83 @@ routes:
 --- response_body
 true
 --- error_code: 302
+
+
+
+=== TEST 20: data encryption for client_rsa_private_key
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+            local rsa_key = "-----BEGIN RSA PRIVATE KEY-----\n" ..
+                
"MIIEowIBAAKCAQEA0Z3VS5JJcds3xHn/ygWep4OHG5xbFGFiWXoXQWNe7mhZ6CJE\n" ..
+                "-----END RSA PRIVATE KEY-----"
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 json.encode({
+                     plugins = {
+                         ["openid-connect"] = {
+                             client_id = "kbyuFDidLLm280LIwVFiazOqjO3ty8KH",
+                             client_secret = 
"60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa",
+                             client_rsa_private_key = rsa_key,
+                             discovery = 
"http://127.0.0.1:1980/.well-known/openid-configuration";,
+                             redirect_uri = "https://iresty.com";,
+                             ssl_verify = false,
+                             timeout = 10,
+                             scope = "apisix",
+                             use_pkce = false,
+                             session = {
+                                 secret = "jwcE5v3pM9VhqLxmxFOH9uZaLo8u7KQK"
+                             }
+                         }
+                     },
+                     upstream = {
+                         nodes = {
+                             ["127.0.0.1:1980"] = 1
+                         },
+                         type = "roundrobin"
+                     },
+                     uri = "/hello"
+                 })
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+            ngx.sleep(0.1)
+
+            -- get plugin conf from admin api, key is decrypted
+            local code, message, res = t('/apisix/admin/routes/1',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+
+            local plain_key = 
res.value.plugins["openid-connect"].client_rsa_private_key
+            ngx.say(plain_key == rsa_key)
+
+            -- get plugin conf from etcd, key must be encrypted (not plaintext)
+            local etcd = require("apisix.core.etcd")
+            local etcd_res = assert(etcd.get('/routes/1'))
+            local stored = 
etcd_res.body.node.value.plugins["openid-connect"].client_rsa_private_key
+            ngx.say(type(stored) == "string" and stored ~= "" and stored ~= 
rsa_key)
+        }
+    }
+--- response_body
+true
+true
+--- no_error_log
+[alert]
diff --git a/t/router/radixtree-sni3.t b/t/router/radixtree-sni3.t
index ff18bda7f..d7cd40de6 100644
--- a/t/router/radixtree-sni3.t
+++ b/t/router/radixtree-sni3.t
@@ -281,3 +281,39 @@ server name: "www.test.com"
 --- no_error_log
 [error]
 [alert]
+
+
+
+=== TEST 8: matched SSL object must not be logged (no private key leak via 
debug log)
+--- config
+listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
+location /t {
+    content_by_lua_block {
+        -- etcd sync
+        ngx.sleep(0.2)
+        do
+            local sock = ngx.socket.tcp()
+            sock:settimeout(2000)
+            local ok, err = 
sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock")
+            if not ok then
+                ngx.say("failed to connect: ", err)
+                return
+            end
+            local sess, err = sock:sslhandshake(nil, "www.test.com", false)
+            if not sess then
+                ngx.say("failed to do SSL handshake: ", err)
+                return
+            end
+            ngx.say("ssl handshake: ", sess ~= nil)
+            local ok, err = sock:close()
+            ngx.say("close: ", ok, " ", err)
+        end
+    }
+}
+--- request
+GET /t
+--- response_body
+ssl handshake: true
+close: 1 nil
+--- no_error_log
+debug - matched

Reply via email to