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 2b69dbc20f perf(core): sanitize the X-Forwarded-* headers in the NGINX
config (#13803)
2b69dbc20f is described below
commit 2b69dbc20f9f707a2b5385367b3c5bd030cfb42c
Author: AlinsRan <[email protected]>
AuthorDate: Fri Aug 14 11:43:19 2026 +0800
perf(core): sanitize the X-Forwarded-* headers in the NGINX config (#13803)
---
apisix/cli/ngx_tpl.lua | 74 ++++++++--
apisix/core/ctx.lua | 5 +-
apisix/init.lua | 139 ++++++++----------
conf/config.yaml.example | 17 +++
t/APISIX.pm | 30 ++--
t/core/trusted-addresses.t | 356 +++++++++++++++++++++++++++++++++++++++++++++
t/plugin/loggly.t | 8 +-
t/plugin/proxy-rewrite2.t | 88 +++++++++++
8 files changed, 611 insertions(+), 106 deletions(-)
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index 4fa56d57a4..2f9a5c0051 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -273,6 +273,28 @@ stream {
{% if enable_http then %}
http {
+ # X-Forwarded-* sanitization, first half. The second is
+ # `handle_trusted_x_forwarded_headers` in apisix/init.lua.
+ #
+ # Every request is neutralized unconditionally, in the rewrite phase, in C.
+ # That is the case worth optimizing for: with no `apisix.trusted_addresses`
+ # configured no peer is trusted, so it is what every request gets.
+ #
+ # These keep the names the `set` directives they replace used, and hold the
+ # same thing: what X-Forwarded-Host and X-Forwarded-Port are given below.
+ #
+ # the port carried by the Host header, falling back to the listener's own
+ map $http_host $var_x_forwarded_port {
+ default $server_port;
+ "~:(?<p>\\d+)$" $p;
+ }
+ # `$http_host` rather than `$host`: the port the client connected to
belongs
+ # in X-Forwarded-Host, and `$host` drops it
+ map $http_host $var_x_forwarded_host {
+ default $http_host;
+ "" $host;
+ }
+
# put extra_lua_path in front of the builtin path
# so user can override the source code
lua_package_path
"{*extra_lua_path*}$prefix/deps/share/lua/5.1/?.lua;$prefix/deps/share/lua/5.1/?/init.lua;]=]
@@ -880,14 +902,43 @@ http {
### the following x-forwarded-* headers is to send to upstream
server
- set $var_x_forwarded_proto $scheme;
- set $var_x_forwarded_host $host;
- set $var_x_forwarded_port $server_port;
-
+ # Take copies before neutralizing, so a trusted peer's own values
can
+ # be put back. ngx_rewrite's `set` runs before headers_more's
handler,
+ # which is what makes this ordering work -- do not reorder these.
+ #
+ # Reading `$http_x_forwarded_*` here indexes them, so they keep the
+ # client's raw value for the rest of the request. Nothing
downstream
+ # derives from them -- the upstream headers come from
`r->headers_in`
+ # and Lua's `ctx.var.http_x_forwarded_*` re-reads it through the
prefix
+ # handler -- but an access log format that names them logs what the
+ # client sent. `$scheme` / `$var_x_forwarded_host` /
+ # `$var_x_forwarded_port` are the sanitized values.
+ set $original_x_forwarded_proto $http_x_forwarded_proto;
+ set $original_x_forwarded_host $http_x_forwarded_host;
+ set $original_x_forwarded_port $http_x_forwarded_port;
+ # X-Forwarded-For is the one that cannot be copied here. Unlike
+ # `$http_x_forwarded_proto` and friends, which are prefix
variables and
+ # are re-evaluated on every read, `$http_x_forwarded_for` is a
dedicated
+ # entry in `ngx_http_core_variables[]`; naming it in the
configuration
+ # makes it indexed, and this `set` would then pin the client's
value in
+ # `r->variables[]` for the whole request -- surviving the clear
below and
+ # feeding it back to route `vars`, rate-limit keys and every other
+ # `ctx.var` reader. Lua fills the slot instead, in the one branch
that
+ # destroys the value.
+ set $original_x_forwarded_for '';
+ set $original_forwarded $http_forwarded;
+ more_set_input_headers "X-Forwarded-Proto: $scheme";
+ more_set_input_headers "X-Forwarded-Host: $var_x_forwarded_host";
+ more_set_input_headers "X-Forwarded-Port: $var_x_forwarded_port";
+ more_set_input_headers "Forwarded: ";
+
+ # X-Forwarded-Proto/Host/Port are not set here: `r->headers_in`
already
+ # holds the values this request should carry, and proxy_pass
forwards it
+ # as it stands. That is also what lets a plugin rewrite them -- a
+ # `proxy_set_header` would overwrite the plugin's value with
whatever the
+ # variable held. X-Forwarded-For is different: the connection
address has
+ # to be appended, which only $proxy_add_x_forwarded_for does.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $var_x_forwarded_proto;
- proxy_set_header X-Forwarded-Host $var_x_forwarded_host;
- proxy_set_header X-Forwarded-Port $var_x_forwarded_port;
{% if enabled_plugins["proxy-cache"] or
enabled_plugins["graphql-proxy-cache"] then %}
### the following configuration is to cache response content from
upstream server
@@ -1000,10 +1051,13 @@ http {
proxy_set_header X-Real-IP $remote_addr;
proxy_pass_header Date;
+ # X-Forwarded-Proto/Host/Port are not set here: `r->headers_in`
already
+ # holds the values this request should carry, and proxy_pass
forwards it
+ # as it stands. That is also what lets a plugin rewrite them -- a
+ # `proxy_set_header` would overwrite the plugin's value with
whatever the
+ # variable held. X-Forwarded-For is different: the connection
address has
+ # to be appended, which only $proxy_add_x_forwarded_for does.
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $var_x_forwarded_proto;
- proxy_set_header X-Forwarded-Host $var_x_forwarded_host;
- proxy_set_header X-Forwarded-Port $var_x_forwarded_port;
proxy_pass $upstream_scheme://apisix_backend$upstream_uri;
diff --git a/apisix/core/ctx.lua b/apisix/core/ctx.lua
index 64d1093312..12cdadc7de 100644
--- a/apisix/core/ctx.lua
+++ b/apisix/core/ctx.lua
@@ -249,9 +249,8 @@ do
rate_limiting_info = true,
- var_x_forwarded_proto = true,
- var_x_forwarded_port = true,
- var_x_forwarded_host = true,
+ original_x_forwarded_for = true,
+
}
-- sort in alphabetical
diff --git a/apisix/init.lua b/apisix/init.lua
index 13a8cf8a90..548c83b52d 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -686,88 +686,73 @@ function _M.handle_upstream(api_ctx, route,
enable_websocket)
end
-local function handle_x_forwarded_headers(api_ctx)
- local addr_is_trusted =
trusted_addresses_util.is_trusted(api_ctx.var.realip_remote_addr)
-
- -- Only untrusted values need to be overwritten or cleared.
- if not addr_is_trusted then
- -- store the original x-forwarded-* headers
- -- to allow future use by other plugins or processes
- api_ctx.var.original_x_forwarded_proto =
api_ctx.var.http_x_forwarded_proto
- api_ctx.var.original_x_forwarded_host =
api_ctx.var.http_x_forwarded_host
- api_ctx.var.original_x_forwarded_port =
api_ctx.var.http_x_forwarded_port
- api_ctx.var.original_x_forwarded_for = api_ctx.var.http_x_forwarded_for
-
- -- trusted ones
- -- ref: ngx_tpl.lua#L831-L840
- --
- -- these values are observed directly by APISIX and cannot be forged,
- -- making them highly credible.
- local proto = api_ctx.var.scheme
- local http_host = api_ctx.var.http_host or api_ctx.var.host
- -- parse_addr handles IPv6 literals and bracketed host:port correctly.
- local _, port_from_host = core.utils.parse_addr(http_host)
- local host = http_host
- local port = port_from_host or api_ctx.var.server_port
-
- -- override the x-forwarded-* headers to the trusted ones.
- -- make sure that the correct values are obtained
- -- in the subsequent stages using `core.request.header`.
- core.request.set_header(api_ctx, "X-Forwarded-Proto", proto)
- core.request.set_header(api_ctx, "X-Forwarded-Host", host)
- core.request.set_header(api_ctx, "X-Forwarded-Port", port)
- -- Clear RFC 7239 Forwarded header to prevent forgery.
- core.request.set_header(api_ctx, "Forwarded", nil)
-
- -- X-Forwarded-For: when a trust boundary is configured but this peer
is
- -- untrusted, reset it so the upstream only sees the APISIX-observed
- -- connection IP via `$proxy_add_x_forwarded_for`, dropping the
spoofable
- -- inbound chain. When `trusted_addresses` is unset, keep the
compatible
- -- default of preserving the inbound chain (the connection IP is
appended).
- if trusted_addresses_util.is_configured() then
- core.request.set_header(api_ctx, "X-Forwarded-For", nil)
- api_ctx.var.http_x_forwarded_for = nil
- end
-
- -- update the cached value in http_x_forwarded_* to the trusted ones.
- -- make sure that the correct values are obtained
- -- in the subsequent stages using `var.http_x_forwarded_*`.
- api_ctx.var.http_x_forwarded_proto = proto
- api_ctx.var.http_x_forwarded_host = host
- api_ctx.var.http_x_forwarded_port = port
- api_ctx.var.http_forwarded = nil
+-- X-Forwarded-Proto/Host/Port and Forwarded are already neutralized by the
time
+-- this runs: `more_set_input_headers` in apisix/cli/ngx_tpl.lua does it in the
+-- rewrite phase, in C, on every request. That is unconditional because with no
+-- trust boundary configured -- the default -- it is what every request needs,
and
+-- keeping it in the config keeps Lua off that path entirely.
+--
+-- What is left needs a trust decision, so it stays here, behind a check that
is a
+-- constant for the worker's lifetime: with no `trusted_addresses` this
returns on
+-- its first line and nothing else runs.
+--
+-- `set` captures an absent header as the empty string, so "" means the peer
sent
+-- nothing and the value the config injected stays. That is a deliberate change
+-- for a trusted peer: the Lua-only implementation skipped the whole rewrite
for
+-- one, so a header it did not send stayed absent and the upstream fell
through to
+-- `$host` / `$server_port`. A trusted peer now gets the same observed values
an
+-- untrusted one does -- the Host with its port and case, rather than the
+-- lower-cased portless `$host` -- which is the value the untrusted path has
always
+-- produced. `ctx.var.http_x_forwarded_*` is updated alongside, so a plugin
reading
+-- it in a later phase sees the restored value rather than the injected one.
+local function restore_if_sent(api_ctx, header_name, var_name, orig)
+ if not orig or orig == "" then
+ return
end
+
+ core.request.set_header(api_ctx, header_name, orig)
+ api_ctx.var[var_name] = orig
end
--- in ngx_tpl.lua#L831-L840,
--- there is such code: `proxy_set_header X-Forwarded-XXX $var_x_forwarded_xxx;`
--- that is, set the `X-Forwarded-XXX` header through `var_x_forwarded_xxx`.
---
--- therefore, it is necessary to set the trusted `http_x_forwarded_xxx` to
`var_x_forwarded_xxx`.
--- So that the `X-Forwarded-XXX` header is updated to a trusted value.
---
--- currently, only following headers are updated through these variables:
--- - X-Forwarded-Proto
--- - X-Forwarded-Port
--- - X-Forwarded-Host
---
--- the `X-Forwarded-For` header is not updated through these variables.
--- because it is set by the `proxy_add_x_forwarded_for` directive.
-local function set_upstream_x_forwarded_headers(api_ctx)
- local proto = api_ctx.var.http_x_forwarded_proto
- if proto then
- api_ctx.var.var_x_forwarded_proto = proto
+local function handle_trusted_x_forwarded_headers(api_ctx)
+ -- The other four originals are copied by the configuration; this one
cannot be,
+ -- because naming `$http_x_forwarded_for` there would pin it in
`r->variables[]`
+ -- and the clear below could not dislodge it. Copy it here instead, on
every
+ -- path: the header is only destroyed further down, but a plugin reading
+ -- `ctx.var.original_x_forwarded_for` should not have to know that.
+ local inbound_xff = api_ctx.var.http_x_forwarded_for
+ if inbound_xff then
+ api_ctx.var.original_x_forwarded_for = inbound_xff
end
- local port = api_ctx.var.http_x_forwarded_port
- if port then
- api_ctx.var.var_x_forwarded_port = port
+ if not trusted_addresses_util.is_configured() then
+ return
end
- local host = api_ctx.var.http_x_forwarded_host
- if host then
- api_ctx.var.var_x_forwarded_host = host
+ if trusted_addresses_util.is_trusted(api_ctx.var.realip_remote_addr) then
+ -- a trusted peer's own values go back, from the copies the config took
+ -- before overwriting them
+ restore_if_sent(api_ctx, "X-Forwarded-Proto", "http_x_forwarded_proto",
+ api_ctx.var.original_x_forwarded_proto)
+ restore_if_sent(api_ctx, "X-Forwarded-Host", "http_x_forwarded_host",
+ api_ctx.var.original_x_forwarded_host)
+ restore_if_sent(api_ctx, "X-Forwarded-Port", "http_x_forwarded_port",
+ api_ctx.var.original_x_forwarded_port)
+ restore_if_sent(api_ctx, "Forwarded", "http_forwarded",
+ api_ctx.var.original_forwarded)
+
+ return
+ end
+
+ -- An untrusted peer, with a trust boundary to measure it against: drop the
+ -- inbound X-Forwarded-For so the upstream only sees the connection IP via
+ -- `$proxy_add_x_forwarded_for`. Without a boundary the chain is preserved,
+ -- which is the compatible default and is why this lives behind the check
+ -- above rather than in the config.
+ if inbound_xff then
+ core.request.set_header(api_ctx, "X-Forwarded-For", nil)
+ api_ctx.var.http_x_forwarded_for = nil
end
end
@@ -828,7 +813,7 @@ function _M.http_access_phase()
-- var.request is read-only; copy to a writable variable so data-mask can
redact query params
api_ctx.var.request_line = api_ctx.var.request
- handle_x_forwarded_headers(api_ctx)
+ handle_trusted_x_forwarded_headers(api_ctx)
-- When match_uri_encoded_slash is on, match the route against a uri that
-- keeps the encoded slash (%2F) so it is treated as part of a path
@@ -970,10 +955,6 @@ function _M.http_access_phase()
end
span:finish(ngx_ctx)
- -- set before handle_upstream: grpc/dubbo/disable_proxy_buffering exit via
- -- ngx.exec() and never return, so the trusted values must be applied
first.
- set_upstream_x_forwarded_headers(api_ctx)
-
_M.handle_upstream(api_ctx, route, enable_websocket)
end
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index ebdc1535f4..26954e2797 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -168,6 +168,23 @@ apisix:
# address, it is reset so the upstream only
sees the APISIX-observed
# connection IP; when `trusted_addresses` is
not configured, it is
# preserved and the connection IP is
appended (compatible default).
+ # The override happens in the NGINX
configuration, before any Lua runs.
+ # Lua always sees the overridden values --
`core.request.header` and
+ # `ctx.var.http_x_forwarded_*` alike. At
config level it differs:
+ # naming `$http_x_forwarded_proto`,
`$http_x_forwarded_host`,
+ # `$http_x_forwarded_port` or
`$http_forwarded` in an access log
+ # format, an `if`, or a `map` reads the
value cached when the
+ # override was applied, which is what the
client sent. Use `$scheme`,
+ # `$var_x_forwarded_host` and
`$var_x_forwarded_port` for the
+ # overridden ones. `$http_x_forwarded_for`
is not affected.
+ # What the client sent is kept in
`$original_x_forwarded_proto`,
+ # `$original_x_forwarded_host`,
`$original_x_forwarded_port`,
+ # `$original_x_forwarded_for` and
`$original_forwarded` -- readable
+ # from a log format, or from Lua as
`ctx.var.original_x_forwarded_*`,
+ # the same names the values were previously
kept under, so a plugin
+ # reading them is unaffected. This matters
most for X-Forwarded-For,
+ # which is cleared rather than overwritten
when a trust boundary is
+ # configured and the peer is outside it.
# fine tune the parameters of LRU cache for some features like secret
lru:
secret:
diff --git a/t/APISIX.pm b/t/APISIX.pm
index e0b86560b0..05d477cbf2 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -238,9 +238,6 @@ my $disable_proxy_buffering_location = <<_EOC_;
proxy_pass_header Date;
proxy_set_header X-Forwarded-For
\$proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto \$var_x_forwarded_proto;
- proxy_set_header X-Forwarded-Host \$var_x_forwarded_host;
- proxy_set_header X-Forwarded-Port \$var_x_forwarded_port;
proxy_pass
\$upstream_scheme://apisix_backend\$upstream_uri;
mirror /proxy_mirror;
@@ -626,6 +623,16 @@ _EOC_
$http_config .= <<_EOC_;
$lua_deps_path
+ # mirrors apisix/cli/ngx_tpl.lua
+ map \$http_host \$var_x_forwarded_port {
+ default \$server_port;
+ "~:(?<p>\\\\d+)\$" \$p;
+ }
+ map \$http_host \$var_x_forwarded_host {
+ default \$http_host;
+ "" \$host;
+ }
+
lua_shared_dict plugin-limit-req 10m;
lua_shared_dict plugin-limit-count 10m;
lua_shared_dict plugin-limit-count-lock 10m;
@@ -953,16 +960,19 @@ _EOC_
proxy_set_header X-Real-IP \$remote_addr;
proxy_pass_header Date;
- ### the following x-forwarded-* headers is to send to upstream
server
+ set \$original_x_forwarded_proto \$http_x_forwarded_proto;
+ set \$original_x_forwarded_host \$http_x_forwarded_host;
+ set \$original_x_forwarded_port \$http_x_forwarded_port;
+ set \$original_x_forwarded_for '';
+ set \$original_forwarded \$http_forwarded;
+ more_set_input_headers "X-Forwarded-Proto: \$scheme";
+ more_set_input_headers "X-Forwarded-Host: \$var_x_forwarded_host";
+ more_set_input_headers "X-Forwarded-Port: \$var_x_forwarded_port";
+ more_set_input_headers "Forwarded: ";
- set \$var_x_forwarded_proto \$scheme;
- set \$var_x_forwarded_host \$host;
- set \$var_x_forwarded_port \$server_port;
+ ### the following x-forwarded-* headers is to send to upstream
server
proxy_set_header X-Forwarded-For
\$proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto \$var_x_forwarded_proto;
- proxy_set_header X-Forwarded-Host \$var_x_forwarded_host;
- proxy_set_header X-Forwarded-Port \$var_x_forwarded_port;
proxy_pass
\$upstream_scheme://apisix_backend\$upstream_uri;
mirror /proxy_mirror;
diff --git a/t/core/trusted-addresses.t b/t/core/trusted-addresses.t
index 233bca36ce..77164b4865 100644
--- a/t/core/trusted-addresses.t
+++ b/t/core/trusted-addresses.t
@@ -68,6 +68,7 @@ x-forwarded-proto: http
x-real-ip: 127.0.0.1
--- error_log
trusted_addresses is not configured
+--- no_error_log
trusted_addresses_matcher is not initialized
@@ -438,3 +439,358 @@ x-forwarded-host: example.com
x-forwarded-port: 8443
x-forwarded-proto: https
x-real-ip: 127.0.0.1
+
+
+
+=== TEST 11: Host carrying a port sets X-Forwarded-Host and X-Forwarded-Port
from it
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+Host: example.com:8443
+--- response_body
+uri: /old_uri
+host: example.com:8443
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: example.com:8443
+x-forwarded-port: 8443
+x-forwarded-proto: http
+x-real-ip: 127.0.0.1
+--- error_log
+trusted_addresses is not configured
+--- no_error_log
+trusted_addresses_matcher is not initialized
+
+
+
+=== TEST 12: request without a Host header falls back to $host
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+ pass_host: rewrite
+ upstream_host: localhost
+#END
+--- raw_request eval
+"GET /old_uri HTTP/1.0\r\n\r\n"
+--- response_body
+uri: /old_uri
+host: localhost
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: localhost
+x-forwarded-port: 1984
+x-forwarded-proto: http
+x-real-ip: 127.0.0.1
+--- error_log
+trusted_addresses is not configured
+--- no_error_log
+trusted_addresses_matcher is not initialized
+
+
+
+=== TEST 13: trusted client that sent no X-Forwarded-* gets the observed values
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "127.0.0.1"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+Host: Example.COM:8443
+--- response_body
+uri: /old_uri
+host: Example.COM:8443
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: Example.COM:8443
+x-forwarded-port: 8443
+x-forwarded-proto: http
+x-real-ip: 127.0.0.1
+--- no_error_log
+trusted_addresses is not configured
+trusted_addresses_matcher is not initialized
+
+
+
+=== TEST 14: trusted client, proxy-rewrite of X-Forwarded-Proto reaches the
upstream
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "127.0.0.1"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ plugins:
+ proxy-rewrite:
+ headers:
+ X-Forwarded-Proto: https
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+X-Forwarded-Proto: grpc
+--- response_body
+uri: /old_uri
+host: localhost
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: localhost
+x-forwarded-port: 1984
+x-forwarded-proto: https
+x-real-ip: 127.0.0.1
+--- no_error_log
+trusted_addresses is not configured
+trusted_addresses_matcher is not initialized
+
+
+
+=== TEST 15: client not in trusted list, every forged forwarding header is
dropped
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "1.0.0.1"
+ - "10.0.0.0/8"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+X-Forwarded-For: 9.9.9.9
+X-Forwarded-Proto: https
+X-Forwarded-Host: evil.com
+X-Forwarded-Port: 8443
+Forwarded: for=1.2.3.4
+--- response_body
+uri: /old_uri
+host: localhost
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: localhost
+x-forwarded-port: 1984
+x-forwarded-proto: http
+x-real-ip: 127.0.0.1
+--- no_error_log
+trusted_addresses is not configured
+trusted_addresses_matcher is not initialized
+
+
+
+=== TEST 16: trusted client sending an empty X-Forwarded-Proto gets the
observed one
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "127.0.0.1"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+X-Forwarded-Proto:
+--- response_body
+uri: /old_uri
+host: localhost
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: localhost
+x-forwarded-port: 1984
+x-forwarded-proto: http
+x-real-ip: 127.0.0.1
+--- no_error_log
+[error]
+
+
+
+=== TEST 17: the client's original X-Forwarded-For stays readable after it is
cleared
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "10.0.0.0/8"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ plugins:
+ serverless-pre-function:
+ phase: access
+ functions:
+ - "return function(conf, ctx) ngx.log(ngx.WARN, \"orig xff: \",
tostring(ctx.var.original_x_forwarded_for), \" current: \",
tostring(ctx.var.http_x_forwarded_for)) end"
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+X-Forwarded-For: 9.9.9.9, 8.8.8.8
+--- response_body
+uri: /old_uri
+host: localhost
+x-forwarded-for: 127.0.0.1
+x-forwarded-host: localhost
+x-forwarded-port: 1984
+x-forwarded-proto: http
+x-real-ip: 127.0.0.1
+--- error_log
+orig xff: 9.9.9.9, 8.8.8.8 current: nil
+
+
+
+=== TEST 18: a route matching on http_x_forwarded_for does not see the cleared
chain
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "10.0.0.0/8"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ vars:
+ - ["http_x_forwarded_for", "==", "9.9.9.9"]
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+X-Forwarded-For: 9.9.9.9
+--- error_code: 404
+
+
+
+=== TEST 19: with no trust boundary the original X-Forwarded-For is still
preserved
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /old_uri
+ plugins:
+ serverless-pre-function:
+ phase: access
+ functions:
+ - "return function(conf, ctx) ngx.log(ngx.WARN, \"orig xff: \",
tostring(ctx.var.original_x_forwarded_for)) end"
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /old_uri
+--- more_headers
+X-Forwarded-For: 9.9.9.9, 8.8.8.8
+--- response_body
+uri: /old_uri
+host: localhost
+x-forwarded-for: 9.9.9.9, 8.8.8.8, 127.0.0.1
+x-forwarded-host: localhost
+x-forwarded-port: 1984
+x-forwarded-proto: http
+x-real-ip: 127.0.0.1
+--- error_log
+orig xff: 9.9.9.9, 8.8.8.8
diff --git a/t/plugin/loggly.t b/t/plugin/loggly.t
index c81206f16e..69c3ddc427 100644
--- a/t/plugin/loggly.t
+++ b/t/plugin/loggly.t
@@ -362,7 +362,7 @@ opentracing
--- grep_error_log eval
qr/message received: [ -~]+/
--- grep_error_log_out eval
-qr/message received: <10>1 [\d\-T:.]+Z [\d.]+ apisix [\d]+ - \[token-1\@41058
tag="apisix"]
\{"apisix_latency":[\d.]*,"client_ip":"127\.0\.0\.1","latency":[\d.]*,"request":\{"headers":\{"content-type":"application\/x-www-form-urlencoded","host":"127\.0\.0\.1:1984","user-agent":"lua-resty-http\/[\d.]*
\(Lua\)
ngx_lua\/[\d]*"\},"method":"GET","querystring":\{\},"size":[\d]+,"uri":"\/opentracing","url":"http:\/\/127\.0\.0\.1:1984\/opentracing"\},"response":\{"headers":\{"connection":"close"
[...]
+qr/message received: <10>1 [\d\-T:.]+Z [\d.]+ apisix [\d]+ - \[token-1\@41058
tag="apisix"]
\{"apisix_latency":[\d.]*,"client_ip":"127\.0\.0\.1","latency":[\d.]*,"request":\{"headers":\{"content-type":"application\/x-www-form-urlencoded","host":"127\.0\.0\.1:1984","user-agent":"lua-resty-http\/[\d.]*
\(Lua\)
ngx_lua\/[\d]*","x-forwarded-host":"127\.0\.0\.1:1984","x-forwarded-port":"1984","x-forwarded-proto":"http"\},"method":"GET","querystring":\{\},"size":[\d]+,"uri":"\/opentracing","ur
[...]
@@ -410,7 +410,7 @@ opentracing
--- grep_error_log eval
qr/message received: [ -~]+/
--- grep_error_log_out eval
-qr/message received: <14>1 [\d\-T:.]+Z [\d.]+ apisix [\d]+ - \[tok\@41058
tag="apisix"]
\{"apisix_latency":[\d.]*,"client_ip":"127\.0\.0\.1","latency":[\d.]*,"request":\{"headers":\{"content-type":"application\/x-www-form-urlencoded","host":"127\.0\.0\.1:1984","user-agent":"lua-resty-http\/[\d.]*
\(Lua\)
ngx_lua\/[\d]*"\},"method":"GET","querystring":\{\},"size":[\d]+,"uri":"\/opentracing","url":"http:\/\/127\.0\.0\.1:1984\/opentracing"\},"response":\{"body":"opentracing\\n","headers":\{
[...]
+qr/message received: <14>1 [\d\-T:.]+Z [\d.]+ apisix [\d]+ - \[tok\@41058
tag="apisix"]
\{"apisix_latency":[\d.]*,"client_ip":"127\.0\.0\.1","latency":[\d.]*,"request":\{"headers":\{"content-type":"application\/x-www-form-urlencoded","host":"127\.0\.0\.1:1984","user-agent":"lua-resty-http\/[\d.]*
\(Lua\)
ngx_lua\/[\d]*","x-forwarded-host":"127\.0\.0\.1:1984","x-forwarded-port":"1984","x-forwarded-proto":"http"\},"method":"GET","querystring":\{\},"size":[\d]+,"uri":"\/opentracing","url":"
[...]
@@ -463,7 +463,7 @@ opentracing
--- grep_error_log eval
qr/message received: [ -~]+/
--- grep_error_log_out eval
-qr/message received: <14>1 [\d\-T:.]+Z [\d.]+ apisix [\d]+ - \[tok\@41058
tag="apisix"]
\{"apisix_latency":[\d.]*,"client_ip":"127\.0\.0\.1","latency":[\d.]*,"request":\{"headers":\{"content-type":"application\/x-www-form-urlencoded","host":"127\.0\.0\.1:1984","user-agent":"lua-resty-http\/[\d.]*
\(Lua\)
ngx_lua\/[\d]*"\},"method":"GET","querystring":\{"bar":"bar"\},"size":[\d]+,"uri":"\/opentracing\?bar=bar","url":"http:\/\/127\.0\.0\.1:1984\/opentracing\?bar=bar"\},"response":\{"body":
[...]
+qr/message received: <14>1 [\d\-T:.]+Z [\d.]+ apisix [\d]+ - \[tok\@41058
tag="apisix"]
\{"apisix_latency":[\d.]*,"client_ip":"127\.0\.0\.1","latency":[\d.]*,"request":\{"headers":\{"content-type":"application\/x-www-form-urlencoded","host":"127\.0\.0\.1:1984","user-agent":"lua-resty-http\/[\d.]*
\(Lua\)
ngx_lua\/[\d]*","x-forwarded-host":"127\.0\.0\.1:1984","x-forwarded-port":"1984","x-forwarded-proto":"http"\},"method":"GET","querystring":\{"bar":"bar"\},"size":[\d]+,"uri":"\/opentraci
[...]
@@ -487,7 +487,7 @@ opentracing
--- grep_error_log eval
qr/message received: [ -~]+/
--- grep_error_log_out eval
-qr/message received: <14>1 [\d\-T:.]+Z [\d.]+ apisix [\d]+ - \[tok\@41058
tag="apisix"]
\{"apisix_latency":[\d.]*,"client_ip":"127\.0\.0\.1","latency":[\d.]*,"request":\{"headers":\{"content-type":"application\/x-www-form-urlencoded","host":"127\.0\.0\.1:1984","user-agent":"lua-resty-http\/[\d.]*
\(Lua\)
ngx_lua\/[\d]*"\},"method":"GET","querystring":\{"foo":"bar"\},"size":[\d]+,"uri":"\/opentracing\?foo=bar","url":"http:\/\/127\.0\.0\.1:1984\/opentracing\?foo=bar"\},"response":\{"header
[...]
+qr/message received: <14>1 [\d\-T:.]+Z [\d.]+ apisix [\d]+ - \[tok\@41058
tag="apisix"]
\{"apisix_latency":[\d.]*,"client_ip":"127\.0\.0\.1","latency":[\d.]*,"request":\{"headers":\{"content-type":"application\/x-www-form-urlencoded","host":"127\.0\.0\.1:1984","user-agent":"lua-resty-http\/[\d.]*
\(Lua\)
ngx_lua\/[\d]*","x-forwarded-host":"127\.0\.0\.1:1984","x-forwarded-port":"1984","x-forwarded-proto":"http"\},"method":"GET","querystring":\{"foo":"bar"\},"size":[\d]+,"uri":"\/opentraci
[...]
diff --git a/t/plugin/proxy-rewrite2.t b/t/plugin/proxy-rewrite2.t
index a882ffd5af..03491c1771 100644
--- a/t/plugin/proxy-rewrite2.t
+++ b/t/plugin/proxy-rewrite2.t
@@ -263,3 +263,91 @@ X-Forwarded-Proto: http
X-Forwarded-Proto: grpc
--- response_headers
X-Forwarded-Proto: http
+
+
+
+=== TEST 9: remove X-Forwarded-Host, no trusted_addresses (apache/apisix#13753)
+--- yaml_config
+apisix:
+ node_listen: 1984
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /echo
+ plugins:
+ proxy-rewrite:
+ headers:
+ remove:
+ - "X-Forwarded-Host"
+ upstream_id: 1
+upstreams:
+ -
+ id: 1
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /echo
+--- response_headers
+X-Forwarded-Host:
+
+
+
+=== TEST 10: remove X-Forwarded-Host from a trusted client
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /echo
+ plugins:
+ proxy-rewrite:
+ headers:
+ remove:
+ - "X-Forwarded-Host"
+ upstream_id: 1
+upstreams:
+ -
+ id: 1
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /echo
+--- more_headers
+X-Forwarded-Host: client.example.com
+--- response_headers
+X-Forwarded-Host:
+
+
+
+=== TEST 11: customize X-Forwarded-Host
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /echo
+ plugins:
+ proxy-rewrite:
+ headers:
+ X-Forwarded-Host: my-upstream.example.com
+ upstream_id: 1
+upstreams:
+ -
+ id: 1
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /echo
+--- more_headers
+X-Forwarded-Host: client.example.com
+--- response_headers
+X-Forwarded-Host: my-upstream.example.com