diff --git a/apisix/cli/schema.lua b/apisix/cli/schema.lua
index b7a8b0fc0708..8a70bda8bca2 100644
--- a/apisix/cli/schema.lua
+++ b/apisix/cli/schema.lua
@@ -19,6 +19,7 @@ local jsonschema = require("jsonschema")
local pairs = pairs
local pcall = pcall
local require = require
+local schema_def = require("apisix.schema_def")
local _M = {}
@@ -259,6 +260,14 @@ local config_schema = {
default = false,
description = "a global switch to disable upstream health checks",
},
+ trusted_addresses = {
+ type = "array",
+ minItems = 1,
+ items = {
+ anyOf = schema_def.ip_def,
+ },
+ uniqueItems = true
+ },
}
},
nginx_config = {
diff --git a/apisix/core.lua b/apisix/core.lua
index 14c5186a4aae..ffae65f45637 100644
--- a/apisix/core.lua
+++ b/apisix/core.lua
@@ -48,7 +48,7 @@ return {
request = require("apisix.core.request"),
response = require("apisix.core.response"),
lrucache = require("apisix.core.lrucache"),
- schema = require("apisix.schema_def"),
+ schema = require("apisix.core.schema"),
string = require("apisix.core.string"),
ctx = require("apisix.core.ctx"),
timer = require("apisix.core.timer"),
diff --git a/apisix/core/schema.lua b/apisix/core/schema.lua
index 9ce6a553020b..9a50d93b747d 100644
--- a/apisix/core/schema.lua
+++ b/apisix/core/schema.lua
@@ -21,8 +21,10 @@
local jsonschema = require('jsonschema')
local lrucache = require("apisix.core.lrucache")
+local schema_def = require("apisix.schema_def")
local cached_validator = lrucache.new({count = 1000, ttl = 0})
local pcall = pcall
+local error = error
local _M = {
version = 0.3,
@@ -68,4 +70,9 @@ end
_M.valid = get_validator
+setmetatable(_M, {
+ __index = schema_def,
+ __newindex = function() error("no modification allowed") end,
+})
+
return _M
diff --git a/apisix/init.lua b/apisix/init.lua
index d6f7a1a3dff0..b0f727785a6f 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -46,6 +46,7 @@ local ctxdump = require("resty.ctxdump")
local debug = require("apisix.debug")
local pubsub_kafka = require("apisix.pubsub.kafka")
local resource = require("apisix.resource")
+local trusted_addresses_util = require("apisix.utils.trusted-addresses")
local ngx = ngx
local get_method = ngx.req.get_method
local ngx_exit = ngx.exit
@@ -162,6 +163,8 @@ function _M.http_init_worker()
-- To ensure that all workers related to Prometheus metrics are initialized,
-- we need to put the initialization of the Prometheus plugin here.
plugin.init_prometheus()
+
+ trusted_addresses_util.init_worker()
end
@@ -292,21 +295,6 @@ end
local function set_upstream_headers(api_ctx, picked_server)
set_upstream_host(api_ctx, picked_server)
-
- local proto = api_ctx.var.http_x_forwarded_proto
- if proto then
- api_ctx.var.var_x_forwarded_proto = proto
- end
-
- local x_forwarded_host = api_ctx.var.http_x_forwarded_host
- if x_forwarded_host then
- api_ctx.var.var_x_forwarded_host = x_forwarded_host
- end
-
- local port = api_ctx.var.http_x_forwarded_port
- if port then
- api_ctx.var.var_x_forwarded_port = port
- end
end
@@ -599,6 +587,79 @@ 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 host = api_ctx.var.host
+ local port = 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)
+ -- later processed in ngx_tpl by `$proxy_add_x_forwarded_for`.
+ core.request.set_header(api_ctx, "X-Forwarded-For", nil)
+
+ -- 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_x_forwarded_for = nil
+ end
+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
+ end
+
+ local port = api_ctx.var.http_x_forwarded_port
+ if port then
+ api_ctx.var.var_x_forwarded_port = port
+ end
+
+ local host = api_ctx.var.http_x_forwarded_host
+ if host then
+ api_ctx.var.var_x_forwarded_host = host
+ end
+end
+
+
function _M.http_access_phase()
-- from HTTP/3 to HTTP/1.1 we need to convert :authority pesudo-header
-- to Host header, so we set upstream_host variable here.
@@ -648,6 +709,8 @@ function _M.http_access_phase()
api_ctx.var.real_request_uri = api_ctx.var.request_uri
api_ctx.var.request_uri = api_ctx.var.uri .. api_ctx.var.is_args .. (api_ctx.var.args or "")
+ handle_x_forwarded_headers(api_ctx)
+
router.router_http.match(api_ctx)
local route = api_ctx.matched_route
@@ -754,6 +817,8 @@ function _M.http_access_phase()
end
_M.handle_upstream(api_ctx, route, enable_websocket)
+
+ set_upstream_x_forwarded_headers(api_ctx)
end
diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua
index 78f59fcd3e80..cff101007fd6 100644
--- a/apisix/schema_def.lua
+++ b/apisix/schema_def.lua
@@ -14,11 +14,8 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-local schema = require('apisix.core.schema')
local table_insert = table.insert
local table_concat = table.concat
-local setmetatable = setmetatable
-local error = error
local _M = {version = 0.5}
@@ -1093,10 +1090,4 @@ _M.plugin_injected_schema = {
}
-setmetatable(_M, {
- __index = schema,
- __newindex = function() error("no modification allowed") end,
-})
-
-
return _M
diff --git a/apisix/utils/trusted-addresses.lua b/apisix/utils/trusted-addresses.lua
new file mode 100644
index 000000000000..df2a314a2a91
--- /dev/null
+++ b/apisix/utils/trusted-addresses.lua
@@ -0,0 +1,52 @@
+--
+-- 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.
+--
+local require = require
+local core = require("apisix.core")
+
+local trusted_addresses_matcher
+
+local _M = {}
+
+
+function _M.init_worker()
+ local local_conf = core.config.local_conf()
+ local trusted_addresses = core.table.try_read_attr(local_conf, "apisix", "trusted_addresses")
+
+ if not trusted_addresses then
+ core.log.info("trusted_addresses is not configured")
+ return
+ end
+
+ local matcher, err = core.ip.create_ip_matcher(trusted_addresses)
+ if not matcher then
+ core.log.error("failed to create ip matcher for trusted_addresses: ", err)
+ return
+ end
+
+ trusted_addresses_matcher = matcher
+end
+
+
+function _M.is_trusted(address)
+ if not trusted_addresses_matcher then
+ core.log.info("trusted_addresses_matcher is not initialized")
+ return false
+ end
+ return trusted_addresses_matcher:match(address)
+end
+
+return _M
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index 7a0f6939c5a0..d06d689aa110 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -141,6 +141,10 @@ apisix:
# or (standalone mode) the config isn't loaded yet either via file or Admin API.
# disable_upstream_healthcheck: false # A global switch for healthcheck. Defaults to false.
# 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.
# 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 c1419b9db76f..2f3e357dccd5 100644
--- a/docs/en/latest/plugins/chaitin-waf.md
+++ b/docs/en/latest/plugins/chaitin-waf.md
@@ -90,7 +90,10 @@ 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.
+:::
+:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
diff --git a/docs/en/latest/plugins/real-ip.md b/docs/en/latest/plugins/real-ip.md
index c607046ccbf2..147ad73d6689 100644
--- a/docs/en/latest/plugins/real-ip.md
+++ b/docs/en/latest/plugins/real-ip.md
@@ -45,6 +45,10 @@ The Plugin is functionally similar to NGINX's [ngx_http_realip_module](https://n
| trusted_addresses | array[string] | False | | array of IPv4 or IPv6 addresses (CIDR notation acceptable) | Trusted addresses that are known to send correct replacement addresses. This configuration sets the [`set_real_ip_from`](https://nginx.org/en/docs/http/ngx_http_realip_module.html#set_real_ip_from) directive. |
| 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.
+:::
+
:::note
If the address specified in `source` is missing or invalid, the Plugin would not change the client address.
:::
diff --git a/docs/zh/latest/plugins/chaitin-waf.md b/docs/zh/latest/plugins/chaitin-waf.md
index 0f2c3eb091d7..a90cae2454c9 100644
--- a/docs/zh/latest/plugins/chaitin-waf.md
+++ b/docs/zh/latest/plugins/chaitin-waf.md
@@ -89,6 +89,10 @@ 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-*` 头将全部被可信值覆盖。
+:::
+
:::note
您可以这样从 `config.yaml` 中获取 `admin_key` 并存入环境变量:
diff --git a/docs/zh/latest/plugins/real-ip.md b/docs/zh/latest/plugins/real-ip.md
index 1b85b358b834..da1fa4b08eb1 100644
--- a/docs/zh/latest/plugins/real-ip.md
+++ b/docs/zh/latest/plugins/real-ip.md
@@ -45,6 +45,10 @@ description: real-ip 插件允许 Apache APISIX 通过 HTTP 请求头或 HTTP
| trusted_addresses | array[string] | 否 | | IPv4 或 IPv6 地址数组(接受 CIDR 表示法) | 已知会发送正确替代地址的可信地址。此配置设置 [`set_real_ip_from`](https://nginx.org/en/docs/http/ngx_http_realip_module.html#set_real_ip_from) 指令。 |
| recursive | boolean | 否 | false | | 如果为 false,则将匹配可信地址之一的原始客户端地址替换为配置的 `source` 中发送的最后一个地址。
如果为 true,则将匹配可信地址之一的原始客户端地址替换为配置的 `source` 中发送的最后一个非可信地址。 |
+:::note
+只有发送自 `apisix.trusted_addresses` 配置(支持 IP 和 CIDR)地址的 `X-Forwarded-*` 头才会被信任,并传递给插件或上游。如果未配置 `apisix.trusted_addresses` 或 ip 不在配置地址范围内的,`X-Forwarded-*` 头将全部被可信值覆盖。
+:::
+
:::note
如果 `source` 属性中设置的地址丢失或者无效,该插件将不会更改客户端地址。
:::
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 1bf43b888796..4ef30e506e71 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -105,6 +105,8 @@ apisix:
tcp:
- 9100
enable_resolv_search_opt: false
+ trusted_addresses:
+ - "127.0.0.1"
_EOC_
my $etcd_enable_auth = $ENV{"ETCD_ENABLE_AUTH"} || "false";
diff --git a/t/cli/test_validate_config.sh b/t/cli/test_validate_config.sh
index 0f8a09a43c2d..d779dbe0d9ab 100755
--- a/t/cli/test_validate_config.sh
+++ b/t/cli/test_validate_config.sh
@@ -204,3 +204,188 @@ if ! echo "$out" | grep 'property "host" validation failed'; then
fi
echo "passed: check etcd schema during init"
+
+# Test trusted_addresses with non-array value (string instead of array)
+echo '
+apisix:
+ trusted_addresses: "127.0.0.1"
+deployment:
+ role: traditional
+ role_traditional:
+ config_provider: etcd
+etcd:
+ host:
+ - "http://127.0.0.1:2379"
+ prefix: "/apisix"
+' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if ! echo "$out" | grep 'property "trusted_addresses" validation failed: wrong type: expected array, got string'; then
+ echo "failed: trusted_addresses should reject string value"
+ exit 1
+fi
+
+echo "passed: trusted_addresses rejects non-array value"
+
+# Test trusted_addresses with empty array (should be rejected)
+echo '
+apisix:
+ trusted_addresses: []
+deployment:
+ role: traditional
+ role_traditional:
+ config_provider: etcd
+etcd:
+ host:
+ - "http://127.0.0.1:2379"
+ prefix: "/apisix"
+' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if ! echo "$out" | grep 'property "trusted_addresses" validation failed: expect array to have at least 1 items'; then
+ echo "failed: trusted_addresses should reject empty array"
+ exit 1
+fi
+
+echo "passed: trusted_addresses rejects empty array"
+
+# Test trusted_addresses with non-string items in array
+echo '
+apisix:
+ trusted_addresses:
+ - "127.0.0.1"
+ - 123
+deployment:
+ role: traditional
+ role_traditional:
+ config_provider: etcd
+etcd:
+ host:
+ - "http://127.0.0.1:2379"
+ prefix: "/apisix"
+' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if ! echo "$out" | grep 'property "trusted_addresses" validation failed: failed to validate item 2: object matches none of the required'; then
+ echo "failed: trusted_addresses should reject non-string items"
+ exit 1
+fi
+
+echo "passed: trusted_addresses rejects non-string array items"
+
+# Test trusted_addresses with duplicate items (should be rejected)
+echo '
+apisix:
+ trusted_addresses:
+ - "127.0.0.1"
+ - "192.168.1.1"
+ - "127.0.0.1"
+deployment:
+ role: traditional
+ role_traditional:
+ config_provider: etcd
+etcd:
+ host:
+ - "http://127.0.0.1:2379"
+ prefix: "/apisix"
+' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if ! echo "$out" | grep 'property "trusted_addresses" validation failed.*equal'; then
+ echo "failed: trusted_addresses should reject duplicate items"
+ exit 1
+fi
+
+echo "passed: trusted_addresses rejects duplicate items"
+
+# Test trusted_addresses with invalid IP
+echo '
+apisix:
+ trusted_addresses:
+ - "127.0.0"
+deployment:
+ role: traditional
+ role_traditional:
+ config_provider: etcd
+etcd:
+ host:
+ - "http://127.0.0.1:2379"
+ prefix: "/apisix"
+' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if ! echo "$out" | grep 'property "trusted_addresses" validation failed: failed to validate item 1: object matches none of the required'; then
+ echo "failed: trusted_addresses should reject invalid IP"
+ exit 1
+fi
+
+echo "passed: trusted_addresses rejects invalid IP"
+
+# Test trusted_addresses with invalid CIDR
+echo '
+apisix:
+ trusted_addresses:
+ - "127.0.0.0/33"
+deployment:
+ role: traditional
+ role_traditional:
+ config_provider: etcd
+etcd:
+ host:
+ - "http://127.0.0.1:2379"
+ prefix: "/apisix"
+' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if ! echo "$out" | grep 'property "trusted_addresses" validation failed: failed to validate item 1: object matches none of the required'; then
+ echo "failed: trusted_addresses should reject invalid CIDR"
+ exit 1
+fi
+
+echo "passed: trusted_addresses rejects invalid CIDR"
+
+# Test trusted_addresses with valid IP
+echo '
+apisix:
+ trusted_addresses:
+ - "127.0.0.1"
+deployment:
+ role: traditional
+ role_traditional:
+ config_provider: etcd
+etcd:
+ host:
+ - "http://127.0.0.1:2379"
+ prefix: "/apisix"
+' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if echo "$out" | grep 'property "trusted_addresses" validation failed: failed to validate item 1: object matches none of the required'; then
+ echo "failed: trusted_addresses should accept valid IP"
+ exit 1
+fi
+
+echo "passed: trusted_addresses accepts valid IP"
+
+# Test trusted_addresses with valid CIDR
+echo '
+apisix:
+ trusted_addresses:
+ - "127.0.0.0/24"
+deployment:
+ role: traditional
+ role_traditional:
+ config_provider: etcd
+etcd:
+ host:
+ - "http://127.0.0.1:2379"
+ prefix: "/apisix"
+' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if echo "$out" | grep 'property "trusted_addresses" validation failed: failed to validate item 1: object matches none of the required'; then
+ echo "failed: trusted_addresses should accept valid CIDR"
+ exit 1
+fi
+
+echo "passed: trusted_addresses accepts valid CIDR"
diff --git a/t/core/trusted-addresses.t b/t/core/trusted-addresses.t
new file mode 100644
index 000000000000..c8a0fc6fafc9
--- /dev/null
+++ b/t/core/trusted-addresses.t
@@ -0,0 +1,360 @@
+#
+# 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_root_location();
+no_shuffle();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if (!$block->no_error_log && !$block->error_log) {
+ $block->set_value("no_error_log", "[error]\n[alert]");
+ }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: without trusted_addresses configuration, X-Forwarded headers should be overridden
+--- 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
+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-host: localhost
+x-forwarded-port: 1984
+x-forwarded-proto: http
+x-real-ip: 127.0.0.1
+--- error_log
+trusted_addresses is not configured
+trusted_addresses_matcher is not initialized
+
+
+
+=== TEST 2: with IP, X-Forwarded headers should be preserved from trusted client
+--- 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: 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-host: example.com
+x-forwarded-port: 8443
+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 3: with multiple IPs, X-Forwarded headers should be preserved from trusted client
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "127.0.0.1"
+ - "127.0.0.2"
+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: 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-host: example.com
+x-forwarded-port: 8443
+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 4: with CIDR, X-Forwarded headers should be preserved from trusted client
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "127.0.0.0/24"
+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: 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-host: example.com
+x-forwarded-port: 8443
+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 5: with multiple CIDRs, X-Forwarded headers should be preserved from trusted client
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "127.0.0.0/24"
+ - "1.1.1.0/24"
+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: 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-host: example.com
+x-forwarded-port: 8443
+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 6: with multiple IPs and CIDRs, X-Forwarded headers should be preserved from trusted client
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "127.0.0.0/24"
+ - "1.1.1.0/24"
+ - "127.0.0.1"
+ - "1.1.1.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: 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-host: example.com
+x-forwarded-port: 8443
+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 7: with `0.0.0.0/0`, X-Forwarded headers should be preserved from trusted client
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: false
+ trusted_addresses:
+ - "0.0.0.0/0"
+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: 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-host: example.com
+x-forwarded-port: 8443
+x-forwarded-proto: https
+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
+--- 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-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-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
diff --git a/t/plugin/proxy-rewrite2.t b/t/plugin/proxy-rewrite2.t
index 7096e4aad249..a882ffd5af02 100644
--- a/t/plugin/proxy-rewrite2.t
+++ b/t/plugin/proxy-rewrite2.t
@@ -27,6 +27,8 @@ add_block_preprocessor(sub {
my $yaml_config = $block->yaml_config // <<_EOC_;
apisix:
node_listen: 1984
+ trusted_addresses:
+ - "127.0.0.1"
deployment:
role: data_plane
role_data_plane:
@@ -230,3 +232,34 @@ GET /echo
X-Forwarded-Port: 8080
--- response_headers
X-Forwarded-Port: 10080
+
+
+
+=== TEST 8: pass duplicate X-Forwarded-Proto, but not configured trusted_addresses
+--- yaml_config
+apisix:
+ node_listen: 1984
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /echo
+ upstream_id: 1
+upstreams:
+ -
+ id: 1
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+--- request
+GET /echo
+--- more_headers
+X-Forwarded-Proto: http
+X-Forwarded-Proto: grpc
+--- response_headers
+X-Forwarded-Proto: http
diff --git a/t/plugin/real-ip.t b/t/plugin/real-ip.t
index e6e044730bb4..fafe0b4a5842 100644
--- a/t/plugin/real-ip.t
+++ b/t/plugin/real-ip.t
@@ -470,3 +470,39 @@ passed
GET /hello
--- more_headers
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
+--- yaml_config
+apisix:
+ node_listen: 1984
+ enable_admin: true
+ trusted_addresses:
+ - "192.128.0.0/16"
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+--- apisix_yaml
+routes:
+ -
+ id: 1
+ uri: /hello
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+ plugins:
+ real-ip:
+ trusted_addresses: ["192.128.0.0/16", "127.0.0.0/24"]
+ source: http_x_forwarded_for
+ ip-restriction:
+ whitelist: ["1.1.1.1"]
+#END
+--- request
+GET /hello
+--- more_headers
+X-Forwarded-For: 1.1.1.1
+--- error_code: 403