You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2023/03/02 09:03:45 UTC

[apisix] branch release/2.15 updated (a9ac45e34 -> 518410e9b)

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

spacewander pushed a change to branch release/2.15
in repository https://gitbox.apache.org/repos/asf/apisix.git


    from a9ac45e34 feat: release 2.15.2 (#8754)
     new ca0c7d9da fix(proxy-rewrite): escape args part if it's not from user conf (#8888)
     new 518410e9b fix: handle host & SNI mismatch in mTLS (#8967)

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/centos7-ci.yml    |   2 +-
 .github/workflows/fuzzing-ci.yaml   |   1 +
 apisix/core/utils.lua               |   8 +-
 apisix/init.lua                     |  51 ++++++++-
 apisix/plugins/proxy-rewrite.lua    |  27 ++++-
 apisix/ssl/router/radixtree_sni.lua |  24 +++--
 docs/en/latest/mtls.md              |   2 +
 docs/zh/latest/mtls.md              |   2 +
 t/node/client-mtls-openresty.t      | 161 +++++++++++++++++++++++++++-
 t/node/client-mtls.t                | 203 ++++++++++++++++++++++++++++++++++++
 t/plugin/proxy-rewrite3.t           | 168 ++++++++++++++++++++++++++++-
 11 files changed, 625 insertions(+), 24 deletions(-)


[apisix] 01/02: fix(proxy-rewrite): escape args part if it's not from user conf (#8888)

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch release/2.15
in repository https://gitbox.apache.org/repos/asf/apisix.git

commit ca0c7d9da8e4b0241720a5fc46baa5baad826f74
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Fri Feb 24 09:31:08 2023 +0800

    fix(proxy-rewrite): escape args part if it's not from user conf (#8888)
---
 apisix/core/utils.lua            |   8 +-
 apisix/plugins/proxy-rewrite.lua |  27 +++++--
 t/plugin/proxy-rewrite3.t        | 168 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 193 insertions(+), 10 deletions(-)

diff --git a/apisix/core/utils.lua b/apisix/core/utils.lua
index f72996b78..01c8b34c8 100644
--- a/apisix/core/utils.lua
+++ b/apisix/core/utils.lua
@@ -293,6 +293,7 @@ do
     local _ctx
     local n_resolved
     local pat = [[(?<!\\)\$\{?(\w+)\}?]]
+    local _escaper
 
     local function resolve(m)
         local v = _ctx[m[1]]
@@ -300,10 +301,13 @@ do
             return ""
         end
         n_resolved = n_resolved + 1
+        if _escaper then
+            return _escaper(tostring(v))
+        end
         return tostring(v)
     end
 
-    function resolve_var(tpl, ctx)
+    function resolve_var(tpl, ctx, escaper)
         n_resolved = 0
         if not tpl then
             return tpl, nil, n_resolved
@@ -316,8 +320,10 @@ do
 
         -- avoid creating temporary function
         _ctx = ctx
+        _escaper = escaper
         local res, _, err = re_gsub(tpl, pat, resolve, "jo")
         _ctx = nil
+        _escaper = nil
         if not res then
             return nil, err
         end
diff --git a/apisix/plugins/proxy-rewrite.lua b/apisix/plugins/proxy-rewrite.lua
index f53918003..9ef3b5f9f 100644
--- a/apisix/plugins/proxy-rewrite.lua
+++ b/apisix/plugins/proxy-rewrite.lua
@@ -165,13 +165,24 @@ function _M.rewrite(conf, ctx)
         ctx.upstream_scheme = conf["scheme"]
     end
 
+
+    local function escape_separator(s)
+        return re_sub(s, [[\?]], "%3F", "jo")
+    end
+
     local upstream_uri = ctx.var.uri
+    local separator_escaped = false
     if conf.use_real_request_uri_unsafe then
         upstream_uri = ctx.var.real_request_uri
     elseif conf.uri ~= nil then
-        upstream_uri = core.utils.resolve_var(conf.uri, ctx.var)
+        separator_escaped = true
+        upstream_uri = core.utils.resolve_var(conf.uri, ctx.var, escape_separator)
     elseif conf.regex_uri ~= nil then
-        local uri, _, err = re_sub(ctx.var.uri, conf.regex_uri[1],
+        if not str_find(upstream_uri, "?") then
+            separator_escaped = true
+        end
+
+        local uri, _, err = re_sub(upstream_uri, conf.regex_uri[1],
                                    conf.regex_uri[2], "jo")
         if uri then
             upstream_uri = uri
@@ -185,11 +196,17 @@ function _M.rewrite(conf, ctx)
     end
 
     if not conf.use_real_request_uri_unsafe then
-        local index = str_find(upstream_uri, "?")
+        local index
+        if separator_escaped then
+            index = str_find(upstream_uri, "?")
+        end
+
         if index then
-            upstream_uri = core.utils.uri_safe_encode(sub_str(upstream_uri, 1, index-1)) ..
-                           sub_str(upstream_uri, index)
+            upstream_uri = core.utils.uri_safe_encode(sub_str(upstream_uri, 1, index - 1)) ..
+                sub_str(upstream_uri, index)
         else
+            -- The '?' may come from client request '%3f' when we use ngx.var.uri directly or
+            -- via regex_uri
             upstream_uri = core.utils.uri_safe_encode(upstream_uri)
         end
 
diff --git a/t/plugin/proxy-rewrite3.t b/t/plugin/proxy-rewrite3.t
index 7501abd1d..0bedf09d4 100644
--- a/t/plugin/proxy-rewrite3.t
+++ b/t/plugin/proxy-rewrite3.t
@@ -327,8 +327,6 @@ ngx.var.request_uri: /print_uri_detailed
 GET /t
 --- response_body
 passed
---- no_error_log
-[error]
 
 
 
@@ -339,5 +337,167 @@ GET /echo HTTP/1.1
 X-Forwarded-Host: apisix.ai
 --- response_headers
 X-Forwarded-Host: test.com
---- no_error_log
-[error]
+
+
+
+=== TEST 14: set route
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                      "plugins": {
+                          "proxy-rewrite": {
+                              "host": "test.xxxx.com"
+                          }
+                      },
+                      "upstream": {
+                          "nodes": {
+                              "127.0.0.1:8125": 1
+                          },
+                          "type": "roundrobin"
+                      },
+                      "uri": "/hello*"
+                 }]]
+                 )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 15: hit with CRLF
+--- request
+GET /hello%3f0z=700%26a=c%20HTTP/1.1%0D%0AHost:google.com%0d%0a%0d%0a
+--- http_config
+    server {
+        listen 8125;
+        location / {
+            content_by_lua_block {
+                ngx.say(ngx.var.host)
+                ngx.say(ngx.var.request_uri)
+            }
+        }
+    }
+--- response_body
+test.xxxx.com
+/hello%3F0z=700&a=c%20HTTP/1.1%0D%0AHost:google.com%0D%0A%0D%0A
+
+
+
+=== TEST 16: set route with uri
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                      "plugins": {
+                          "proxy-rewrite": {
+                              "uri": "/$uri/remain",
+                              "host": "test.xxxx.com"
+                          }
+                      },
+                      "upstream": {
+                          "nodes": {
+                              "127.0.0.1:8125": 1
+                          },
+                          "type": "roundrobin"
+                      },
+                      "uri": "/hello*"
+                 }]]
+                 )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 17: hit with CRLF
+--- request
+GET /hello%3f0z=700%26a=c%20HTTP/1.1%0D%0AHost:google.com%0d%0a%0d%0a
+--- http_config
+    server {
+        listen 8125;
+        location / {
+            content_by_lua_block {
+                ngx.say(ngx.var.host)
+                ngx.say(ngx.var.request_uri)
+            }
+        }
+    }
+--- response_body
+test.xxxx.com
+//hello%253F0z=700&a=c%20HTTP/1.1%0D%0AHost:google.com%0D%0A%0D%0A/remain
+
+
+
+=== TEST 18: regex_uri with args
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                      "plugins": {
+                          "proxy-rewrite": {
+                              "regex_uri": ["^/test/(.*)/(.*)/(.*)", "/$1_$2_$3?a=c"]
+                          }
+                      },
+                      "upstream": {
+                          "nodes": {
+                              "127.0.0.1:8125": 1
+                          },
+                          "type": "roundrobin"
+                      },
+                      "uri": "/test/*"
+                 }]]
+                 )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 19: hit
+--- request
+GET /test/plugin/proxy/rewrite HTTP/1.1
+--- http_config
+    server {
+        listen 8125;
+        location / {
+            content_by_lua_block {
+                ngx.say(ngx.var.request_uri)
+            }
+        }
+    }
+--- response_body
+/plugin_proxy_rewrite?a=c


[apisix] 02/02: fix: handle host & SNI mismatch in mTLS (#8967)

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch release/2.15
in repository https://gitbox.apache.org/repos/asf/apisix.git

commit 518410e9bbb9c6a2ea861104ec116c396103c3dc
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Wed Mar 1 17:49:21 2023 +0800

    fix: handle host & SNI mismatch in mTLS (#8967)
---
 .github/workflows/centos7-ci.yml    |   2 +-
 .github/workflows/fuzzing-ci.yaml   |   1 +
 apisix/init.lua                     |  51 ++++++++-
 apisix/ssl/router/radixtree_sni.lua |  24 +++--
 docs/en/latest/mtls.md              |   2 +
 docs/zh/latest/mtls.md              |   2 +
 t/node/client-mtls-openresty.t      | 161 +++++++++++++++++++++++++++-
 t/node/client-mtls.t                | 203 ++++++++++++++++++++++++++++++++++++
 8 files changed, 432 insertions(+), 14 deletions(-)

diff --git a/.github/workflows/centos7-ci.yml b/.github/workflows/centos7-ci.yml
index 920e17522..b7ba38caf 100644
--- a/.github/workflows/centos7-ci.yml
+++ b/.github/workflows/centos7-ci.yml
@@ -90,7 +90,7 @@ jobs:
       env:
         TEST_FILE_SUB_DIR: ${{ matrix.test_dir }}
       run: |
-        docker run -itd -v /home/runner/work/apisix/apisix:/apisix --env TEST_FILE_SUB_DIR="$TEST_FILE_SUB_DIR" --name centos7Instance --net="host" --dns 8.8.8.8 --dns-search apache.org docker.io/centos:7 /bin/bash
+        docker run -itd -v ${{ github.workspace }}:/apisix --env TEST_FILE_SUB_DIR="$TEST_FILE_SUB_DIR" --name centos7Instance --net="host" --dns 8.8.8.8 --dns-search apache.org docker.io/centos:7 /bin/bash
         # docker exec centos7Instance bash -c "cp -r /tmp/apisix ./"
 
     - name: Start CI env (FIRST_TEST)
diff --git a/.github/workflows/fuzzing-ci.yaml b/.github/workflows/fuzzing-ci.yaml
index 757bb93b7..f08b42cc1 100644
--- a/.github/workflows/fuzzing-ci.yaml
+++ b/.github/workflows/fuzzing-ci.yaml
@@ -69,6 +69,7 @@ jobs:
 
     - name: run simpleroute test
       run: |
+        export APISIX_FUZZING_PWD=$PWD
         python $PWD/t/fuzzing/simpleroute_test.py
 
     - name: run serverless route test
diff --git a/apisix/init.lua b/apisix/init.lua
index 96068894f..0d69f91d4 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -267,6 +267,51 @@ local function verify_tls_client(ctx)
 end
 
 
+local function verify_https_client(ctx)
+    local scheme = ctx.var.scheme
+    if scheme ~= "https" then
+        return true
+    end
+
+    local host = ctx.var.host
+    local matched = router.router_ssl.match_and_set(ctx, true, host)
+    if not matched then
+        return true
+    end
+
+    local matched_ssl = ctx.matched_ssl
+    if matched_ssl.value.client and apisix_ssl.support_client_verification() then
+        local verified = apisix_base_flags.client_cert_verified_in_handshake
+        if not verified then
+            -- vanilla OpenResty requires to check the verification result
+            local res = ctx.var.ssl_client_verify
+            if res ~= "SUCCESS" then
+                if res == "NONE" then
+                    core.log.error("client certificate was not present")
+                else
+                    core.log.error("client certificate verification is not passed: ", res)
+                end
+
+                return false
+            end
+        end
+
+        local sni = apisix_ssl.server_name()
+        if sni ~= host then
+            -- There is a case that the user configures a SSL object with `*.domain`,
+            -- and the client accesses with SNI `a.domain` but uses Host `b.domain`.
+            -- This case is complex and we choose to restrict the access until there
+            -- is a stronge demand in real world.
+            core.log.error("client certificate verified with SNI ", sni,
+                           ", but the host is ", host)
+            return false
+        end
+    end
+
+    return true
+end
+
+
 local function normalize_uri_like_servlet(uri)
     local found = core.string.find(uri, ';')
     if not found then
@@ -329,12 +374,12 @@ function _M.http_access_phase()
     local api_ctx = core.tablepool.fetch("api_ctx", 0, 32)
     ngx_ctx.api_ctx = api_ctx
 
-    if not verify_tls_client(api_ctx) then
+    core.ctx.set_vars_meta(api_ctx)
+
+    if not verify_https_client(api_ctx) then
         return core.response.exit(400)
     end
 
-    core.ctx.set_vars_meta(api_ctx)
-
     debug.dynamic_debug(api_ctx)
 
     local uri = api_ctx.var.uri
diff --git a/apisix/ssl/router/radixtree_sni.lua b/apisix/ssl/router/radixtree_sni.lua
index 891d8d21d..2b4ec81e4 100644
--- a/apisix/ssl/router/radixtree_sni.lua
+++ b/apisix/ssl/router/radixtree_sni.lua
@@ -118,7 +118,7 @@ local function set_pem_ssl_key(sni, cert, pkey)
 end
 
 
-function _M.match_and_set(api_ctx, match_only)
+function _M.match_and_set(api_ctx, match_only, alt_sni)
     local err
     if not radixtree_router or
        radixtree_router_ver ~= ssl_certificates.conf_version then
@@ -129,13 +129,15 @@ function _M.match_and_set(api_ctx, match_only)
         radixtree_router_ver = ssl_certificates.conf_version
     end
 
-    local sni
-    sni, err = apisix_ssl.server_name()
-    if type(sni) ~= "string" then
-        local advise = "please check if the client requests via IP or uses an outdated protocol" ..
-                       ". If you need to report an issue, " ..
-                       "provide a packet capture file of the TLS handshake."
-        return false, "failed to find SNI: " .. (err or advise)
+    local sni = alt_sni
+    if not sni then
+        sni, err = apisix_ssl.server_name()
+        if type(sni) ~= "string" then
+            local advise = "please check if the client requests via IP or uses an outdated " ..
+                           "protocol. If you need to report an issue, " ..
+                           "provide a packet capture file of the TLS handshake."
+            return false, "failed to find SNI: " .. (err or advise)
+        end
     end
 
     core.log.debug("sni: ", sni)
@@ -143,7 +145,11 @@ function _M.match_and_set(api_ctx, match_only)
     local sni_rev = sni:reverse()
     local ok = radixtree_router:dispatch(sni_rev, nil, api_ctx)
     if not ok then
-        core.log.error("failed to find any SSL certificate by SNI: ", sni)
+        if not alt_sni then
+            -- it is expected that alternative SNI doesn't have a SSL certificate associated
+            -- with it sometimes
+            core.log.error("failed to find any SSL certificate by SNI: ", sni)
+        end
         return false
     end
 
diff --git a/docs/en/latest/mtls.md b/docs/en/latest/mtls.md
index 294d4b162..a2a60266d 100644
--- a/docs/en/latest/mtls.md
+++ b/docs/en/latest/mtls.md
@@ -89,6 +89,8 @@ apisix:
 
 Using mTLS is a way to verify clients cryptographically. It is useful and important in cases where you want to have encrypted and secure traffic in both directions.
 
+* Note: the mTLS protection only happens in HTTPS. If your route can also be accessed via HTTP, you should add additional protection in HTTP or disable the access via HTTP.*
+
 ### How to configure
 
 When configuring `ssl`, use parameter `client.ca` and `client.depth` to configure the root CA that signing client certificates and the max length of certificate chain. Please refer to [Admin API](./admin-api.md#ssl) for details.
diff --git a/docs/zh/latest/mtls.md b/docs/zh/latest/mtls.md
index 8996f2b5f..005a9d12e 100644
--- a/docs/zh/latest/mtls.md
+++ b/docs/zh/latest/mtls.md
@@ -89,6 +89,8 @@ apisix:
 
 双向认证是一种密码学安全的验证客户端身份的手段。当你需要加密并保护流量的双向安全时很有用。
 
+* 注意:双向认证只发生在 HTTPS 中。如果你的路由也可以通过 HTTP 访问,你应该在 HTTP 中添加额外的保护,或者禁止通过 HTTP 访问。*
+
 ### 如何配置
 
 在配置 `ssl` 资源时,同时需要配置 `client.ca` 和 `client.depth` 参数,分别代表为客户端证书签名的 CA 列表,和证书链的最大深度。可参考:[SSL API 文档](./admin-api.md#ssl)。
diff --git a/t/node/client-mtls-openresty.t b/t/node/client-mtls-openresty.t
index 7b388932d..6f15f687a 100644
--- a/t/node/client-mtls-openresty.t
+++ b/t/node/client-mtls-openresty.t
@@ -113,5 +113,164 @@ client certificate was not present
 curl --cert t/certs/apisix.crt --key t/certs/apisix.key -k https://localhost:1994/hello
 --- response_body eval
 qr/400 Bad Request/
+--- error_log eval
+qr/client certificate verification is not passed: FAILED:self[- ]signed certificate/
+
+
+
+=== TEST 5: hit with different host which doesn't require mTLS
+--- exec
+curl --cert t/certs/mtls_client.crt --key t/certs/mtls_client.key -k https://localhost:1994/hello -H "Host: test.com"
+--- response_body
+hello world
+
+
+
+=== TEST 6: set verification (2 ssl objects)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+            local json = require("toolkit.json")
+            local ssl_ca_cert = t.read_file("t/certs/mtls_ca.crt")
+            local ssl_cert = t.read_file("t/certs/mtls_client.crt")
+            local ssl_key = t.read_file("t/certs/mtls_client.key")
+            local data = {
+                upstream = {
+                    type = "roundrobin",
+                    nodes = {
+                        ["127.0.0.1:1980"] = 1,
+                    },
+                },
+                uri = "/hello"
+            }
+            assert(t.test('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            ))
+
+            local data = {
+                cert = ssl_cert,
+                key = ssl_key,
+                sni = "test.com",
+                client = {
+                    ca = ssl_ca_cert,
+                    depth = 2,
+                }
+            }
+            local code, body = t.test('/apisix/admin/ssl/1',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+
+            local data = {
+                cert = ssl_cert,
+                key = ssl_key,
+                sni = "localhost",
+            }
+            local code, body = t.test('/apisix/admin/ssl/2',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- request
+GET /t
+
+
+
+=== TEST 7: hit without mTLS verify, with Host requires mTLS verification
+--- exec
+curl -k https://localhost:1994/hello -H "Host: test.com"
+--- response_body eval
+qr/400 Bad Request/
+--- error_log
+client certificate was not present
+
+
+
+=== TEST 8: set verification (2 ssl objects, both have mTLS)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+            local json = require("toolkit.json")
+            local ssl_ca_cert = t.read_file("t/certs/mtls_ca.crt")
+            local ssl_ca_cert2 = t.read_file("t/certs/apisix.crt")
+            local ssl_cert = t.read_file("t/certs/mtls_client.crt")
+            local ssl_key = t.read_file("t/certs/mtls_client.key")
+            local data = {
+                upstream = {
+                    type = "roundrobin",
+                    nodes = {
+                        ["127.0.0.1:1980"] = 1,
+                    },
+                },
+                uri = "/hello"
+            }
+            assert(t.test('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            ))
+
+            local data = {
+                cert = ssl_cert,
+                key = ssl_key,
+                sni = "localhost",
+                client = {
+                    ca = ssl_ca_cert,
+                    depth = 2,
+                }
+            }
+            local code, body = t.test('/apisix/admin/ssl/1',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+
+            local data = {
+                cert = ssl_cert,
+                key = ssl_key,
+                sni = "test.com",
+                client = {
+                    ca = ssl_ca_cert2,
+                    depth = 2,
+                }
+            }
+            local code, body = t.test('/apisix/admin/ssl/2',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- request
+GET /t
+
+
+
+=== TEST 9: hit with mTLS verify, with Host requires different mTLS verification
+--- exec
+curl --cert t/certs/mtls_client.crt --key t/certs/mtls_client.key -k https://localhost:1994/hello -H "Host: test.com"
+--- response_body eval
+qr/400 Bad Request/
 --- error_log
-client certificate verification is not passed: FAILED:self signed certificate
+client certificate verified with SNI localhost, but the host is test.com
diff --git a/t/node/client-mtls.t b/t/node/client-mtls.t
index afccc9375..7cca549d4 100644
--- a/t/node/client-mtls.t
+++ b/t/node/client-mtls.t
@@ -304,3 +304,206 @@ Host: localhost
 --- error_code: 502
 --- error_log
 certificate verify failed
+
+
+
+=== TEST 9: set verification
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+            local json = require("toolkit.json")
+            local ssl_ca_cert = t.read_file("t/certs/mtls_ca.crt")
+            local ssl_cert = t.read_file("t/certs/mtls_client.crt")
+            local ssl_key = t.read_file("t/certs/mtls_client.key")
+            local data = {
+                upstream = {
+                    type = "roundrobin",
+                    nodes = {
+                        ["127.0.0.1:1980"] = 1,
+                    },
+                },
+                uri = "/hello"
+            }
+            assert(t.test('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            ))
+
+            local data = {
+                cert = ssl_cert,
+                key = ssl_key,
+                sni = "localhost",
+            }
+            local code, body = t.test('/apisix/admin/ssl/1',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- request
+GET /t
+
+
+
+=== TEST 10: hit with different host which doesn't require mTLS
+--- exec
+curl --cert t/certs/mtls_client.crt --key t/certs/mtls_client.key -k https://localhost:1994/hello -H "Host: x.com"
+--- response_body
+hello world
+
+
+
+=== TEST 11: set verification (2 ssl objects)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+            local json = require("toolkit.json")
+            local ssl_ca_cert = t.read_file("t/certs/mtls_ca.crt")
+            local ssl_cert = t.read_file("t/certs/mtls_client.crt")
+            local ssl_key = t.read_file("t/certs/mtls_client.key")
+            local data = {
+                upstream = {
+                    type = "roundrobin",
+                    nodes = {
+                        ["127.0.0.1:1980"] = 1,
+                    },
+                },
+                uri = "/hello"
+            }
+            assert(t.test('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            ))
+
+            local data = {
+                cert = ssl_cert,
+                key = ssl_key,
+                sni = "test.com",
+                client = {
+                    ca = ssl_ca_cert,
+                    depth = 2,
+                }
+            }
+            local code, body = t.test('/apisix/admin/ssl/1',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+
+            local data = {
+                cert = ssl_cert,
+                key = ssl_key,
+                sni = "localhost",
+            }
+            local code, body = t.test('/apisix/admin/ssl/2',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- request
+GET /t
+
+
+
+=== TEST 12: hit without mTLS verify, with Host requires mTLS verification
+--- exec
+curl -k https://localhost:1994/hello -H "Host: test.com"
+--- response_body eval
+qr/400 Bad Request/
+--- error_log
+client certificate verified with SNI localhost, but the host is test.com
+
+
+
+=== TEST 13: set verification (2 ssl objects, both have mTLS)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+            local json = require("toolkit.json")
+            local ssl_ca_cert = t.read_file("t/certs/mtls_ca.crt")
+            local ssl_ca_cert2 = t.read_file("t/certs/apisix.crt")
+            local ssl_cert = t.read_file("t/certs/mtls_client.crt")
+            local ssl_key = t.read_file("t/certs/mtls_client.key")
+            local data = {
+                upstream = {
+                    type = "roundrobin",
+                    nodes = {
+                        ["127.0.0.1:1980"] = 1,
+                    },
+                },
+                uri = "/hello"
+            }
+            assert(t.test('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            ))
+
+            local data = {
+                cert = ssl_cert,
+                key = ssl_key,
+                sni = "localhost",
+                client = {
+                    ca = ssl_ca_cert,
+                    depth = 2,
+                }
+            }
+            local code, body = t.test('/apisix/admin/ssl/1',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+
+            local data = {
+                cert = ssl_cert,
+                key = ssl_key,
+                sni = "test.com",
+                client = {
+                    ca = ssl_ca_cert2,
+                    depth = 2,
+                }
+            }
+            local code, body = t.test('/apisix/admin/ssl/2',
+                ngx.HTTP_PUT,
+                json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.print(body)
+        }
+    }
+--- request
+GET /t
+
+
+
+=== TEST 14: hit with mTLS verify, with Host requires different mTLS verification
+--- exec
+curl --cert t/certs/mtls_client.crt --key t/certs/mtls_client.key -k https://localhost:1994/hello -H "Host: test.com"
+--- response_body eval
+qr/400 Bad Request/
+--- error_log
+client certificate verified with SNI localhost, but the host is test.com