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