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

Reply via email to