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

AlinsRan 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 4d736af51 perf(forward-auth): send auth requests through 
ngx_http_ffi_client (#14144)
4d736af51 is described below

commit 4d736af51ab69c055dfa8deb4b079f54b2a911da
Author: AlinsRan <[email protected]>
AuthorDate: Thu Nov 5 16:15:18 2026 +0800

    perf(forward-auth): send auth requests through ngx_http_ffi_client (#14144)
---
 apisix/cli/config.lua                  |   3 +
 apisix/plugins/forward-auth.lua        | 173 ++++++++++--
 conf/config.yaml.example               |   3 +
 docs/en/latest/plugins/forward-auth.md |  17 ++
 docs/zh/latest/plugins/forward-auth.md |  17 ++
 t/plugin/forward-auth.t                |   8 +-
 t/plugin/forward-auth2.t               |   4 +-
 t/plugin/forward-auth4.t               | 468 +++++++++++++++++++++++++++++++++
 8 files changed, 662 insertions(+), 31 deletions(-)

diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua
index 07da7cb2e..3d5c7bdc3 100644
--- a/apisix/cli/config.lua
+++ b/apisix/cli/config.lua
@@ -318,6 +318,9 @@ local _M = {
     ["ai-proxy"] = {
       http_client = "ngx_http_ffi_client"
     },
+    ["forward-auth"] = {
+      http_client = "ngx_http_ffi_client"
+    },
     ["log-rotate"] = {
       timeout = 10000,
       interval = 3600,
diff --git a/apisix/plugins/forward-auth.lua b/apisix/plugins/forward-auth.lua
index 78efc1967..8ede935f4 100644
--- a/apisix/plugins/forward-auth.lua
+++ b/apisix/plugins/forward-auth.lua
@@ -15,12 +15,25 @@
 -- limitations under the License.
 --
 
-local ipairs   = ipairs
-local core     = require("apisix.core")
-local http     = require("resty.http")
-local pairs    = pairs
-local type     = type
-local tostring = tostring
+local ipairs       = ipairs
+local core         = require("apisix.core")
+local http         = require("resty.http")
+local http_client  = require("apisix.utils.http")
+local pairs        = pairs
+local setmetatable = setmetatable
+local type         = type
+local tostring     = tostring
+local unpack       = unpack
+local str_byte     = string.byte
+
+local plugin_name = "forward-auth"
+
+local attr_schema = {
+    type = "object",
+    properties = {
+        http_client = http_client.client_schema,
+    },
+}
 
 local schema = {
     type = "object",
@@ -97,7 +110,7 @@ local schema = {
 local _M = {
     version = 0.1,
     priority = 2002,
-    name = "forward-auth",
+    name = plugin_name,
     schema = schema,
 }
 
@@ -111,6 +124,128 @@ function _M.check_schema(conf)
 end
 
 
+local client_name
+
+
+-- `plugin_attr.forward-auth.http_client` names the client. Read on first
+-- request, because local_conf is not readable while this module is loading.
+local function resolve_client_name()
+    if client_name then
+        return client_name
+    end
+
+    local local_conf = core.config.local_conf()
+    local attr = core.table.try_read_attr(local_conf, "plugin_attr", 
plugin_name) or {}
+
+    local ok, err = core.schema.check(attr_schema, attr)
+    if not ok then
+        core.log.error("invalid plugin_attr.", plugin_name, ": ", err)
+        return nil, "invalid plugin_attr." .. plugin_name .. ": " .. err
+    end
+
+    client_name = attr.http_client or http_client.DEFAULT_CLIENT
+
+    return client_name
+end
+
+
+local SLASH = str_byte("/")
+local parsed_uris = setmetatable({}, {__mode = "k"})
+
+
+local function parse_uri(conf)
+    local parsed = parsed_uris[conf]
+    if parsed then
+        return parsed
+    end
+
+    local err
+    parsed, err = http.parse_uri(nil, conf.uri, false)
+    if not parsed then
+        return nil, err
+    end
+
+    -- a scheme-relative uri takes the scheme of the current request
+    if str_byte(conf.uri, 1) ~= SLASH then
+        parsed_uris[conf] = parsed
+    end
+
+    return parsed
+end
+
+
+-- What resty.http's request_uri does, on the object API both clients share.
+local function request_auth(conf, headers, body)
+    local name, err = resolve_client_name()
+    if not name then
+        return nil, err
+    end
+
+    local parsed
+    parsed, err = parse_uri(conf)
+    if not parsed then
+        return nil, err
+    end
+
+    local httpc
+    httpc, err = http_client.new(name)
+    if not httpc then
+        return nil, err
+    end
+
+    httpc:set_timeout(conf.timeout)
+
+    local scheme, host, port, path, query = unpack(parsed)
+    local ok
+    ok, err = httpc:connect({
+        scheme = scheme,
+        host = host,
+        port = port,
+        ssl_verify = conf.ssl_verify,
+        ssl_server_name = host,
+    })
+    if not ok then
+        return nil, err
+    end
+
+    local res
+    res, err = httpc:request({
+        method = conf.request_method,
+        path = path,
+        query = query,
+        headers = headers,
+        body = body,
+        -- ngx_http_ffi_client reads the whole body in the same resume
+        preread_body = true,
+    })
+    if not res then
+        httpc:close()
+        return nil, err
+    end
+
+    local res_body
+    res_body, err = res:read_body()
+    if not res_body then
+        httpc:close()
+        return nil, err
+    end
+
+    res.body = res_body
+
+    if conf.keepalive then
+        ok, err = httpc:set_keepalive(conf.keepalive_timeout, 
conf.keepalive_pool)
+        if not ok then
+            -- e.g. the auth service answered with "Connection: close"
+            core.log.info("failed to set keepalive for the auth connection: ", 
err)
+        end
+    else
+        httpc:close()
+    end
+
+    return res
+end
+
+
 function _M.access(conf, ctx)
     local auth_headers = {
         ["X-Forwarded-Proto"] = core.request.get_scheme(ctx),
@@ -152,31 +287,17 @@ function _M.access(conf, ctx)
         end
     end
 
-    local params = {
-        headers = auth_headers,
-        keepalive = conf.keepalive,
-        ssl_verify = conf.ssl_verify,
-        method = conf.request_method
-    }
-
-    if params.method == "POST" then
-        local body, err = core.request.get_body(conf.max_req_body_size)
+    local body
+    if conf.request_method == "POST" then
+        local err
+        body, err = core.request.get_body(conf.max_req_body_size)
         if err then
             core.log.error("failed to read request body: ", err)
             return 413
         end
-        params.body = body
     end
 
-    if conf.keepalive then
-        params.keepalive_timeout = conf.keepalive_timeout
-        params.keepalive_pool = conf.keepalive_pool
-    end
-
-    local httpc = http.new()
-    httpc:set_timeout(conf.timeout)
-
-    local res, err = httpc:request_uri(conf.uri, params)
+    local res, err = request_auth(conf, auth_headers, body)
     if not res and conf.allow_degradation then
         -- drop client-supplied copies of the configured upstream headers
         for _, header in ipairs(conf.upstream_headers) do
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index 25a4780a6..f47de2ff9 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -688,6 +688,9 @@ plugin_attr:          # Plugin attributes
   ai-proxy:           # Plugin: ai-proxy, ai-proxy-multi
     http_client: ngx_http_ffi_client # HTTP client the AI plugins use to reach 
the
                       # LLM upstream: ngx_http_ffi_client or lua-resty-http.
+  forward-auth:       # Plugin: forward-auth
+    http_client: ngx_http_ffi_client # HTTP client used to reach the 
authorization
+                      # service: ngx_http_ffi_client or lua-resty-http.
   log-rotate:         # Plugin: log-rotate
     timeout: 10000    # maximum wait time for a log rotation(unit: millisecond)
     interval: 3600    # Set the log rotate interval in seconds.
diff --git a/docs/en/latest/plugins/forward-auth.md 
b/docs/en/latest/plugins/forward-auth.md
index d099e54c1..4376cb15f 100644
--- a/docs/en/latest/plugins/forward-auth.md
+++ b/docs/en/latest/plugins/forward-auth.md
@@ -82,6 +82,23 @@ If your authorization service decides based on the URI that 
the Upstream service
 | allow_degradation | boolean       | False    | false   |                     
      | If true, allow APISIX to continue handling requests without the Plugin 
when the Plugin or its dependencies become unavailable.                         
                                                                                
                                                      |
 | status_on_error   | integer       | False    | 403     | between 200 and 599 
inclusive | HTTP status code to return to the client when there is a network 
error with the external authorization service.                                  
                                                                                
                                                       |
 
+## Authorization Service HTTP Client
+
+Requests to the authorization service go through `ngx_http_ffi_client`, a C 
HTTP client that costs less CPU per request than `lua-resty-http`.
+
+`plugin_attr.forward-auth.http_client` in `config.yaml` names the client:
+
+```yaml
+plugin_attr:
+  forward-auth:
+    http_client: ngx_http_ffi_client # or lua-resty-http
+```
+
+- `ngx_http_ffi_client` (default): the C client. It requires an APISIX runtime 
built with the module, which the runtime pinned in `.requirements` is. On a 
runtime without it, the auth request fails and returns `status_on_error`, and 
the error log names the missing module; the Plugin never silently switches 
clients.
+- `lua-resty-http`: the Lua client, on every runtime.
+
+The two clients frame the auth request slightly differently: 
`ngx_http_ffi_client` sends `Connection: keep-alive` (or `close` when 
`keepalive` is `false`), sends `Content-Length: 0` on a `GET`, and sends no 
default `User-Agent`. Its `timeout` bounds the whole response read, where 
`lua-resty-http` applies it to each receive.
+
 ## Examples
 
 The examples below demonstrate how you can use `forward-auth` for different 
scenarios.
diff --git a/docs/zh/latest/plugins/forward-auth.md 
b/docs/zh/latest/plugins/forward-auth.md
index d0983ef4a..2705ba421 100644
--- a/docs/zh/latest/plugins/forward-auth.md
+++ b/docs/zh/latest/plugins/forward-auth.md
@@ -81,6 +81,23 @@ import TabItem from '@theme/TabItem';
 | allow_degradation | boolean | 否     | false   |                              
 | 如果为 true,则允许在插件或其依赖项不可用时,APISIX 继续处理请求而不使用该插件。                               
                                                                                
                                                            |
 | status_on_error   | integer | 否     | 403     | 200 到 599 之间(含)         | 
与外部授权服务出现网络错误时返回给客户端的 HTTP 状态码。                                                 
                                                                                
                                                                  |
 
+## 授权服务 HTTP 客户端
+
+发往授权服务的请求通过 `ngx_http_ffi_client` 发送。这是一个 C 实现的 HTTP 客户端,每个请求的 CPU 开销低于 
`lua-resty-http`。
+
+在 `config.yaml` 中通过 `plugin_attr.forward-auth.http_client` 指定客户端:
+
+```yaml
+plugin_attr:
+  forward-auth:
+    http_client: ngx_http_ffi_client # 或 lua-resty-http
+```
+
+- `ngx_http_ffi_client`(默认):C 客户端,要求 APISIX runtime 编译了该模块,`.requirements` 
中固定的 runtime 已包含。在不含该模块的 runtime 上,鉴权请求会失败并返回 
`status_on_error`,错误日志会指出缺失的模块;插件不会静默切换客户端。
+- `lua-resty-http`:Lua 客户端,适用于所有 runtime。
+
+两种客户端发出的鉴权请求略有不同:`ngx_http_ffi_client` 会发送 `Connection: 
keep-alive`(`keepalive` 为 `false` 时为 `close`),`GET` 请求会带 `Content-Length: 
0`,且不发送默认的 `User-Agent`。它的 `timeout` 限制读取整个响应的总时长,而 `lua-resty-http` 对每次接收分别计时。
+
 ## 使用示例
 
 以下示例演示了如何针对不同场景使用 `forward-auth`。
diff --git a/t/plugin/forward-auth.t b/t/plugin/forward-auth.t
index e82294dc4..f62c8f053 100644
--- a/t/plugin/forward-auth.t
+++ b/t/plugin/forward-auth.t
@@ -465,8 +465,8 @@ GET /nodegr
 --- more_headers
 Authorization: 111
 --- error_code: 403
---- error_log
-failed to process forward auth, err: closed
+--- error_log eval
+qr/failed to process forward auth, err: (?:closed|upstream closed before 
complete response)/
 
 
 
@@ -516,8 +516,8 @@ GET /crlf?user=guest%0d%0ax-user1:%20admin
 --- more_headers
 Authorization: 111
 --- error_code: 403
---- error_log
-failed to process forward auth, err: invalid characters found in header value,
+--- error_log eval
+qr/failed to process forward auth, err: (?:invalid characters found in header 
value|invalid request header),/
 
 
 
diff --git a/t/plugin/forward-auth2.t b/t/plugin/forward-auth2.t
index f90841ede..52332bfcc 100644
--- a/t/plugin/forward-auth2.t
+++ b/t/plugin/forward-auth2.t
@@ -66,7 +66,9 @@ __DATA__
                                                     core.response.exit(403)
                                                 end
                                             else
-                                                if core.request.header(ctx, 
"Content-Length") or
+                                                -- ngx_http_ffi_client frames 
a GET with Content-Length: 0
+                                                local len = 
core.request.header(ctx, "Content-Length");
+                                                if (len and len ~= "0") or
                                                 core.request.header(ctx, 
"Transfer-Encoding") or
                                                 core.request.header(ctx, 
"Content-Encoding") then
                                                     core.response.exit(403)
diff --git a/t/plugin/forward-auth4.t b/t/plugin/forward-auth4.t
new file mode 100644
index 000000000..fdbc208c2
--- /dev/null
+++ b/t/plugin/forward-auth4.t
@@ -0,0 +1,468 @@
+#
+# 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();
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    # The auth service reports what reached it. Only ngx_http_ffi_client frames
+    # a GET with "Content-Length: 0" and sends no User-Agent, which tells the
+    # two clients apart.
+    my $http_config = $block->http_config // <<_EOC_;
+    server {
+        listen 1986;
+        listen 1987 ssl;
+        ssl_certificate     cert/apisix.crt;
+        ssl_certificate_key cert/apisix.key;
+
+        location /auth {
+            content_by_lua_block {
+                ngx.req.read_body()
+                ngx.header["X-Auth-Content-Length"] = 
ngx.var.http_content_length or "none"
+                ngx.header["X-Auth-Host"] = ngx.var.http_host
+                ngx.header["X-Auth-UA"] = ngx.var.http_user_agent
+                ngx.header["X-Auth-Method"] = ngx.req.get_method()
+                ngx.header["X-Auth-Args"] = ngx.var.args or "none"
+                ngx.header["X-Auth-Body"] = ngx.req.get_body_data() or "none"
+                ngx.header["X-Auth-Conn-Requests"] = 
ngx.var.connection_requests
+                ngx.header["X-User-ID"] = "user-1"
+                ngx.header["X-Multi"] = {"a", "b"}
+                ngx.header["x-lower"] = "l"
+                ngx.say("ok")
+            }
+        }
+
+        location /deny {
+            content_by_lua_block {
+                ngx.status = 401
+                ngx.header["WWW-Authenticate"] = 'Bearer realm="test"'
+                ngx.header["X-User-ID"] = "user-1"
+                ngx.say("denied")
+            }
+        }
+
+        location /slow {
+            content_by_lua_block {
+                ngx.sleep(1)
+                ngx.say("ok")
+            }
+        }
+    }
+_EOC_
+
+    $block->set_value("http_config", $http_config);
+
+    if (!defined $block->request) {
+        $block->set_value("request", "GET /t");
+    }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: setup routes
+--- config
+    location /t {
+        content_by_lua_block {
+            local up = {"X-Auth-Content-Length", "X-Auth-Host", "X-Auth-UA", 
"X-Auth-Method",
+                        "X-Auth-Args", "X-Auth-Body", "X-Auth-Conn-Requests", 
"X-User-ID",
+                        "X-Multi", "X-Lower", "X-Absent"}
+            local echo = {
+                phase = "access",
+                functions = {
+                    [[return function(conf, ctx)
+                        local core = require("apisix.core")
+                        core.response.exit(200, core.request.headers(ctx))
+                    end]]
+                }
+            }
+            local routes = {
+                {"fa-ip", {uri = "http://127.0.0.1:1986/auth";,
+                           request_headers = {"Authorization"}, 
upstream_headers = up}},
+                {"fa-name", {uri = "http://localhost:1986/auth?a=1";,
+                             upstream_headers = up}},
+                {"fa-deny", {uri = "http://127.0.0.1:1986/deny";,
+                             client_headers = {"WWW-Authenticate"}, 
upstream_headers = up}},
+                {"fa-post", {uri = "http://127.0.0.1:1986/auth";, 
request_method = "POST",
+                             upstream_headers = up}},
+                {"fa-forwarded-ua", {uri = "http://127.0.0.1:1986/auth";,
+                                     request_headers = {"User-Agent", "Host"},
+                                     upstream_headers = up}},
+                {"fa-no-keepalive", {uri = "http://127.0.0.1:1986/auth";, 
keepalive = false,
+                                     upstream_headers = up}},
+                {"fa-timeout", {uri = "http://127.0.0.1:1986/slow";, timeout = 
200,
+                                upstream_headers = up}},
+                {"fa-dns-fail", {uri = "http://forward-auth.invalid:1986/auth";,
+                                 upstream_headers = up}},
+                {"fa-scheme-relative", {uri = "//127.0.0.1:1987/auth", 
ssl_verify = false,
+                                        upstream_headers = up}},
+                {"fa-https", {uri = "https://127.0.0.1:1987/auth";, ssl_verify 
= false,
+                              upstream_headers = up}},
+                {"fa-https-verify", {uri = "https://127.0.0.1:1987/auth";,
+                                     upstream_headers = up}},
+            }
+
+            local t = require("lib.test_admin").test
+            for _, r in ipairs(routes) do
+                local code, body = t("/apisix/admin/routes/" .. r[1], 
ngx.HTTP_PUT, {
+                    uri = "/" .. r[1],
+                    priority = 10,
+                    plugins = {
+                        ["forward-auth"] = r[2],
+                        ["serverless-post-function"] = echo,
+                    },
+                    upstream = {nodes = {["127.0.0.1:1980"] = 1}, type = 
"roundrobin"},
+                })
+                if code >= 300 then
+                    ngx.say(r[1], ": ", body)
+                    return
+                end
+            end
+            ngx.say("passed")
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 2: ngx_http_ffi_client is the default client
+--- request
+GET /fa-ip
+--- more_headers
+Authorization: token-1
+X-User-ID: injected
+X-Absent: injected
+--- response_body_like eval
+qr/^(?!.*"x-auth-ua")
+   (?=.*"x-auth-content-length":"0")
+   (?=.*"x-auth-host":"127\.0\.0\.1:1986")
+   (?=.*"x-auth-method":"GET")
+   (?=.*"x-user-id":"user-1")
+   (?=.*"x-multi":\["a","b"\])
+   (?=.*"x-lower":"l")/sx
+--- response_body_unlike
+x-absent
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: a hostname is resolved by the APISIX resolver; Host keeps the name 
and the query is sent
+--- request
+GET /fa-name
+--- response_body_like eval
+qr/(?=.*"x-auth-content-length":"0")
+   (?=.*"x-auth-host":"localhost:1986")
+   (?=.*"x-auth-args":"a=1")
+   (?=.*"x-user-id":"user-1")/sx
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: denied requests get the auth status, body and client_headers
+--- request
+GET /fa-deny
+--- error_code: 401
+--- response_body
+denied
+--- response_headers
+WWW-Authenticate: Bearer realm="test"
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: POST forwards the request body
+--- request
+POST /fa-post
+hello-body
+--- response_body_like eval
+qr/(?=.*"x-auth-method":"POST")
+   (?=.*"x-auth-body":"hello-body")
+   (?=.*"x-auth-content-length":"10")/sx
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: forwarded Host and User-Agent are sent
+--- request
+GET /fa-forwarded-ua
+--- more_headers
+Host: client.test
+User-Agent: test-client/1.0
+--- response_body_like eval
+qr/(?=.*"x-auth-content-length":"0")
+   (?=.*"x-auth-host":"client\.test")
+   (?=.*"x-auth-ua":"test-client\/1\.0")/sx
+
+
+
+=== TEST 7: the connection to the auth service is reused
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require("resty.http")
+            local seen = {}
+            for i = 1, 2 do
+                local httpc = http.new()
+                local res, err = 
httpc:request_uri("http://127.0.0.1:1984/fa-ip";)
+                if not res then
+                    ngx.say("request failed: ", err)
+                    return
+                end
+                local headers = require("cjson").decode(res.body)
+                seen[i] = tonumber(headers["x-auth-conn-requests"])
+            end
+            ngx.say(seen[2] == seen[1] + 1 and "reused" or "not reused")
+        }
+    }
+--- response_body
+reused
+
+
+
+=== TEST 8: keepalive false closes the connection to the auth service
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require("resty.http")
+            for i = 1, 2 do
+                local httpc = http.new()
+                local res, err = 
httpc:request_uri("http://127.0.0.1:1984/fa-no-keepalive";)
+                if not res then
+                    ngx.say("request failed: ", err)
+                    return
+                end
+                local headers = require("cjson").decode(res.body)
+                ngx.say(headers["x-auth-conn-requests"])
+            end
+        }
+    }
+--- response_body
+1
+1
+
+
+
+=== TEST 9: auth service timeout
+--- request
+GET /fa-timeout
+--- error_code: 403
+--- error_log
+failed to process forward auth, err: timeout
+
+
+
+=== TEST 10: auth service name that does not resolve
+--- request
+GET /fa-dns-fail
+--- error_code: 403
+--- error_log
+failed to process forward auth, err: forward-auth.invalid could not be resolved
+
+
+
+=== TEST 11: plugin_attr selects lua-resty-http
+--- extra_yaml_config
+plugin_attr:
+  forward-auth:
+    http_client: lua-resty-http
+--- request
+GET /fa-ip
+--- more_headers
+Authorization: token-1
+--- response_body_like eval
+qr/(?=.*"x-auth-content-length":"none")
+   (?=.*"x-auth-ua":"lua-resty-http\/[\d.]+[ ]\(Lua\)[ ]ngx_lua\/\d+")
+   (?=.*"x-auth-host":"127\.0\.0\.1:1986")
+   (?=.*"x-user-id":"user-1")
+   (?=.*"x-multi":\["a","b"\])/sx
+--- no_error_log
+[error]
+
+
+
+=== TEST 12: lua-resty-http resolves hostnames and reports DNS failures
+--- extra_yaml_config
+plugin_attr:
+  forward-auth:
+    http_client: lua-resty-http
+--- request
+GET /fa-dns-fail
+--- error_code: 403
+--- error_log
+failed to process forward auth, err: failed to parse domain
+
+
+
+=== TEST 13: an unknown client name fails the request
+--- extra_yaml_config
+plugin_attr:
+  forward-auth:
+    http_client: unknown-client
+--- request
+GET /fa-ip
+--- error_code: 403
+--- error_log
+invalid plugin_attr.forward-auth: property "http_client" validation failed
+
+
+
+=== TEST 14: an unavailable ngx_http_ffi_client fails the request instead of 
switching clients
+--- extra_init_by_lua
+    package.loaded["resty.ngx_http_ffi_client"] = "not a module"
+--- request
+GET /fa-ip
+--- error_code: 403
+--- error_log
+resty.ngx_http_ffi_client is not available
+
+
+
+=== TEST 15: https endpoint goes through ngx_http_ffi_client
+--- request
+GET /fa-https
+--- response_body_like eval
+qr/(?=.*"x-auth-content-length":"0")
+   (?=.*"x-auth-host":"127\.0\.0\.1:1987")
+   (?=.*"x-user-id":"user-1")/sx
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: ssl_verify rejects an untrusted auth service certificate
+--- request
+GET /fa-https-verify
+--- error_code: 403
+--- error_log
+failed to process forward auth, err:
+
+
+
+=== TEST 17: a scheme-relative uri follows the scheme of each request
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin")
+            local code, body = t.test("/apisix/admin/ssls/fa-test", 
ngx.HTTP_PUT, {
+                cert = t.read_file("t/certs/apisix.crt"),
+                key = t.read_file("t/certs/apisix.key"),
+                sni = "test.com",
+            })
+            if code >= 300 then
+                ngx.say(body)
+                return
+            end
+
+            local http = require("resty.http")
+            -- 1987 speaks TLS only: plain HTTP to it gets a 400
+            for _, scheme in ipairs({"http", "https", "http", "https"}) do
+                local httpc = http.new()
+                local port = scheme == "http" and 1984 or 1994
+                local res, err = httpc:request_uri(scheme .. "://127.0.0.1:" 
.. port
+                                                   .. "/fa-scheme-relative",
+                                                   {ssl_verify = false,
+                                                    ssl_server_name = 
"test.com"})
+                ngx.say(scheme, ": ", res and res.status or err)
+            end
+        }
+    }
+--- response_body
+http: 400
+https: 200
+http: 400
+https: 200
+
+
+
+=== TEST 18: setup a route whose Host comes from a client header
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t("/apisix/admin/routes/fa-empty-host", 
ngx.HTTP_PUT, {
+                uri = "/fa-empty-host",
+                priority = 10,
+                plugins = {
+                    ["forward-auth"] = {
+                        uri = "http://localhost:1986/auth";,
+                        extra_headers = {Host = "$http_x_auth_target"},
+                        upstream_headers = {"X-Auth-Host"},
+                    },
+                    ["serverless-post-function"] = {
+                        phase = "access",
+                        functions = {
+                            [[return function(conf, ctx)
+                                local core = require("apisix.core")
+                                local host = core.request.header(ctx, 
"X-Auth-Host")
+                                core.response.exit(200, "host: " .. 
tostring(host) .. "\n")
+                            end]]
+                        }
+                    },
+                },
+                upstream = {nodes = {["127.0.0.1:1980"] = 1}, type = 
"roundrobin"},
+            })
+            ngx.say(code >= 300 and body or "passed")
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 19: ngx_http_ffi_client replaces an empty Host with the address it 
dials
+--- request
+GET /fa-empty-host
+--- response_body
+host: 127.0.0.1:1986
+--- no_error_log
+[error]
+
+
+
+=== TEST 20: a Host resolved from the client header is sent as is
+--- request
+GET /fa-empty-host
+--- more_headers
+X-Auth-Target: auth.example
+--- response_body
+host: auth.example
+--- no_error_log
+[error]
+
+
+
+=== TEST 21: lua-resty-http sends the empty Host, which the auth service 
rejects
+--- extra_yaml_config
+plugin_attr:
+  forward-auth:
+    http_client: lua-resty-http
+--- request
+GET /fa-empty-host
+--- error_code: 400
+--- no_error_log
+[error]

Reply via email to