diff --git a/apisix/init.lua b/apisix/init.lua
index 01838da5b0f4..56456c09a692 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -647,8 +647,9 @@ local function handle_x_forwarded_headers(api_ctx)
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)
- -- later processed in ngx_tpl by `$proxy_add_x_forwarded_for`.
- core.request.set_header(api_ctx, "X-Forwarded-For", nil)
+ -- X-Forwarded-For is kept as-is: ngx_tpl appends the trusted
+ -- connection IP via `$proxy_add_x_forwarded_for`, so the upstream
+ -- always sees the real client IP as the last hop of the chain.
-- Clear RFC 7239 Forwarded header to prevent forgery.
core.request.set_header(api_ctx, "Forwarded", nil)
@@ -658,7 +659,6 @@ local function handle_x_forwarded_headers(api_ctx)
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_x_forwarded_for = nil
api_ctx.var.http_forwarded = nil
end
end
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index 0a129a5ac83c..1a12b7dace4f 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -154,8 +154,11 @@ apisix:
# When set to true, it overrides all upstream healthcheck configurations and globally disabling healthchecks.
# trusted_addresses: # When configured, APISIX will trust the `X-Forwarded-*` Headers
# - 127.0.0.1 # passed in requests from the IP/CIDR in the list.
-# - 172.18.0.0/16 # CAUTION: When not configured or the request from an untrusted address,
- # APISIX will override `X-Forwarded-*` headers with trusted values.
+# - 172.18.0.0/16 # CAUTION: When not configured or the request is from an untrusted
+ # address, APISIX overrides `X-Forwarded-Proto/Host/Port` with trusted
+ # values and clears the `Forwarded` header. `X-Forwarded-For` is kept
+ # and the trusted connection IP is appended, so the upstream still
+ # receives the real client IP as the last hop of the chain.
# fine tune the parameters of LRU cache for some features like secret
lru:
secret:
diff --git a/docs/en/latest/plugins/chaitin-waf.md b/docs/en/latest/plugins/chaitin-waf.md
index 16a658791b30..d0f2b87c2ad8 100644
--- a/docs/en/latest/plugins/chaitin-waf.md
+++ b/docs/en/latest/plugins/chaitin-waf.md
@@ -90,7 +90,7 @@ The examples below demonstrate how you can configure chaitin-waf Plugin for diff
Before proceeding, make sure you have installed [Chaitin WAF (SafeLine)](https://docs.waf.chaitin.com/en/GetStarted/Deploy).
:::note
-Only `X-Forwarded-*` headers sent from addresses in the `apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted and passed to plugins or upstream. If `apisix.trusted_addresses` is not configured or the IP is not within the configured address range, all `X-Forwarded-*` headers will be overridden with trusted values.
+Only `X-Forwarded-*` headers sent from addresses in the `apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted and passed to plugins or upstream. If `apisix.trusted_addresses` is not configured or the IP is not within the configured address range, the `X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Forwarded-Port` headers are overridden with trusted values and the `Forwarded` header is cleared. The `X-Forwarded-For` header is kept and the trusted connection IP is appended, so the upstream still receives the real client IP as the last hop of the chain.
:::
:::note
diff --git a/docs/en/latest/plugins/real-ip.md b/docs/en/latest/plugins/real-ip.md
index 1d9977906503..4bf5e2b30b7a 100644
--- a/docs/en/latest/plugins/real-ip.md
+++ b/docs/en/latest/plugins/real-ip.md
@@ -49,7 +49,7 @@ The Plugin is functionally similar to NGINX's [ngx_http_realip_module](https://n
| recursive | boolean | False | False | | If false, replace the original client address that matches one of the trusted addresses by the last address sent in the configured `source`.
If true, replace the original client address that matches one of the trusted addresses by the last non-trusted address sent in the configured `source`. |
:::note
-Only `X-Forwarded-*` headers sent from addresses in the `apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted and passed to plugins or upstream. If `apisix.trusted_addresses` is not configured or the IP is not within the configured address range, all `X-Forwarded-*` headers will be overridden with trusted values.
+Only `X-Forwarded-*` headers sent from addresses in the `apisix.trusted_addresses` configuration (supports IP and CIDR) will be trusted and passed to plugins or upstream. If `apisix.trusted_addresses` is not configured or the IP is not within the configured address range, the `X-Forwarded-Proto`, `X-Forwarded-Host`, and `X-Forwarded-Port` headers are overridden with trusted values and the `Forwarded` header is cleared. The `X-Forwarded-For` header is kept and the trusted connection IP is appended, so the upstream still receives the real client IP as the last hop of the chain.
:::
:::note
diff --git a/docs/zh/latest/plugins/chaitin-waf.md b/docs/zh/latest/plugins/chaitin-waf.md
index 7a6193f7d818..428481e88cf8 100644
--- a/docs/zh/latest/plugins/chaitin-waf.md
+++ b/docs/zh/latest/plugins/chaitin-waf.md
@@ -90,7 +90,7 @@ description: chaitin-waf 插件与长亭雷池 WAF 集成,以检测和阻止
继续操作之前,请确保您已安装 [长亭雷池 WAF](https://docs.waf.chaitin.com/en/GetStarted/Deploy)。
:::note
-只有发送自 `apisix.trusted_addresses` 配置(支持 IP 和 CIDR)地址的 `X-Forwarded-*` 头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip 不在配置地址范围内的,`X-Forwarded-*` 头将全部被可信值覆盖。
+只有发送自 `apisix.trusted_addresses` 配置(支持 IP 和 CIDR)地址的 `X-Forwarded-*` 头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip 不在配置地址范围内的,`X-Forwarded-Proto`、`X-Forwarded-Host`、`X-Forwarded-Port` 头将被可信值覆盖,并清除 `Forwarded` 头。`X-Forwarded-For` 头会被保留,并追加可信的连接 IP,因此上游仍可在该头链的最后一跳获取到真实客户端 IP。
:::
:::note
diff --git a/docs/zh/latest/plugins/real-ip.md b/docs/zh/latest/plugins/real-ip.md
index c8628b719ca1..64b54caf61f8 100644
--- a/docs/zh/latest/plugins/real-ip.md
+++ b/docs/zh/latest/plugins/real-ip.md
@@ -49,7 +49,7 @@ import TabItem from '@theme/TabItem';
| recursive | boolean | 否 | false | | 如果为 false,则将匹配可信地址之一的原始客户端地址替换为配置的 `source` 中发送的最后一个地址。
如果为 true,则将匹配可信地址之一的原始客户端地址替换为配置的 `source` 中发送的最后一个非可信地址。 |
:::note
-只有发送自 `apisix.trusted_addresses` 配置 (支持 IP 和 CIDR) 地址的 `X-Forwarded-*` 头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip 不在配置地址范围内的,`X-Forwarded-*` 头将全部被可信值覆盖。
+只有发送自 `apisix.trusted_addresses` 配置 (支持 IP 和 CIDR) 地址的 `X-Forwarded-*` 头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip 不在配置地址范围内的,`X-Forwarded-Proto`、`X-Forwarded-Host`、`X-Forwarded-Port` 头将被可信值覆盖,并清除 `Forwarded` 头。`X-Forwarded-For` 头会被保留,并追加可信的连接 IP,因此上游仍可在该头链的最后一跳获取到真实客户端 IP。
:::
:::note
diff --git a/t/core/trusted-addresses.t b/t/core/trusted-addresses.t
index c8a0fc6fafc9..e7ec079aec3d 100644
--- a/t/core/trusted-addresses.t
+++ b/t/core/trusted-addresses.t
@@ -32,7 +32,7 @@ run_tests();
__DATA__
-=== TEST 1: without trusted_addresses configuration, X-Forwarded headers should be overridden
+=== TEST 1: without trusted_addresses, X-Forwarded-For is preserved while others are overridden
--- yaml_config
apisix:
node_listen: 1984
@@ -54,13 +54,14 @@ routes:
--- request
GET /old_uri
--- more_headers
+X-Forwarded-For: 1.2.3.4
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
X-Forwarded-Port: 8443
--- response_body
uri: /old_uri
host: localhost
-x-forwarded-for: 127.0.0.1
+x-forwarded-for: 1.2.3.4, 127.0.0.1
x-forwarded-host: localhost
x-forwarded-port: 1984
x-forwarded-proto: http
@@ -95,13 +96,14 @@ routes:
--- request
GET /old_uri
--- more_headers
+X-Forwarded-For: 1.2.3.4
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
X-Forwarded-Port: 8443
--- response_body
uri: /old_uri
host: localhost
-x-forwarded-for: 127.0.0.1
+x-forwarded-for: 1.2.3.4, 127.0.0.1
x-forwarded-host: example.com
x-forwarded-port: 8443
x-forwarded-proto: https
@@ -319,7 +321,7 @@ x-real-ip: 127.0.0.1
-=== TEST 8: with trusted_addresses configuration, but client not in trusted list, X-Forwarded headers should be overridden
+=== TEST 8: client not in trusted list, X-Forwarded-For is preserved while others are overridden
--- yaml_config
apisix:
node_listen: 1984
@@ -344,13 +346,14 @@ routes:
--- request
GET /old_uri
--- more_headers
+X-Forwarded-For: 1.2.3.4
X-Forwarded-Proto: https
X-Forwarded-Host: example.com
X-Forwarded-Port: 8443
--- response_body
uri: /old_uri
host: localhost
-x-forwarded-for: 127.0.0.1
+x-forwarded-for: 1.2.3.4, 127.0.0.1
x-forwarded-host: localhost
x-forwarded-port: 1984
x-forwarded-proto: http
diff --git a/t/plugin/real-ip.t b/t/plugin/real-ip.t
index fafe0b4a5842..4af911e705e9 100644
--- a/t/plugin/real-ip.t
+++ b/t/plugin/real-ip.t
@@ -473,8 +473,7 @@ X-Forwarded-For: 1.1.1.1, 192.128.1.1, 127.0.0.1
-=== TEST 24: trusted in real-ip, but not trusted by `apisix.trusted_addresses`
-should be rejected
+=== TEST 24: untrusted by `apisix.trusted_addresses`, but real-ip still honors X-Forwarded-For
--- yaml_config
apisix:
node_listen: 1984
@@ -505,4 +504,4 @@ routes:
GET /hello
--- more_headers
X-Forwarded-For: 1.1.1.1
---- error_code: 403
+--- error_code: 200