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]