diff --git a/www/nginx/Makefile b/www/nginx/Makefile
index 27170b4207..4541c0cf0c 100644
--- a/www/nginx/Makefile
+++ b/www/nginx/Makefile
@@ -1,6 +1,6 @@
PLUGIN_NAME= nginx
PLUGIN_VERSION= 1.36
-PLUGIN_REVISION= 5
+PLUGIN_REVISION= 6
PLUGIN_COMMENT= Nginx HTTP server and reverse proxy
PLUGIN_DEPENDS= nginx
PLUGIN_MAINTAINER= franz.fabian.94@gmail.com
diff --git a/www/nginx/pkg-descr b/www/nginx/pkg-descr
index ce13d67f6a..5548d0a692 100644
--- a/www/nginx/pkg-descr
+++ b/www/nginx/pkg-descr
@@ -12,6 +12,7 @@ Plugin Changelog
* Add optional HTTP/3 support with dynamic Alt-Svc (contributed by Jan Chlouba)
* Fix HTTP/3 reuseport duplicates (contributed by Jan Chlouba)
+* Make upstream client-address headers configurable (X-Forwarded-For, Forwarded, CF-/True-Client-IP)
1.35
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml
index 3f9bd6af5b..f08bd6690b 100644
--- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml
+++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml
@@ -57,6 +57,29 @@
true
checkbox
+
+ upstream.xff_header_mode
+
+
+ dropdown
+ true
+ Append (default) keeps the X-Forwarded-For chain via $proxy_add_x_forwarded_for. Replace sends only the validated client address ($remote_addr). Drop suppresses the header.
+
+
+ upstream.forwarded_header_mode
+
+
+ dropdown
+ true
+ Preserve (default) leaves a client-supplied Forwarded header unchanged. Replace sets a sanitized RFC 7239 value from $remote_addr and $scheme. Drop suppresses the header. Backends that prefer Forwarded may otherwise accept a spoofed address.
+
+
+ upstream.suppress_client_headers
+
+ Clear CF-Connecting-IP and True-Client-IP before proxying so they cannot bypass the validated client address.
+ true
+ checkbox
+
upstream.tls_enable
diff --git a/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml b/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml
index f740f7297d..8ee16e492d 100644
--- a/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml
+++ b/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml
@@ -1,6 +1,6 @@
//OPNsense/Nginx
- 1.35.2
+ 1.35.3
nginx web server, reverse proxy and waf
@@ -131,6 +131,28 @@
0
Y
+
+
+ Append
+ Replace
+ Drop
+
+ Y
+ append
+
+
+
+ Preserve
+ Replace
+ Drop
+
+ Y
+ preserve
+
+
+ 0
+ Y
+
0
Y
diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
index 64c2b305d1..68ee37d211 100644
--- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
+++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf
@@ -72,6 +72,24 @@ map $ssl_server_name $upstream_sni_name {
'' $host;
}
+{% set need_forwarded_map = [] %}
+{% for upstream in helpers.toList('OPNsense.Nginx.upstream') %}
+{% if upstream.forwarded_header_mode is defined and upstream.forwarded_header_mode == 'replace' %}
+{% do need_forwarded_map.append(1) %}
+{% endif %}
+{% endfor %}
+{% if need_forwarded_map %}
+# Maps used in location.conf for RFC 7239 Forwarded header
+map $remote_addr $forwarded_for {
+ ~^[0-9.]+$ "for=$remote_addr";
+ ~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\"";
+ default "for=unknown";
+}
+map $scheme $proxy_forwarded {
+ default "$forwarded_for;proto=$scheme";
+}
+{% endif %}
+
include http_post/*.conf;
# TODO add when core is ready for allowing nginx to serve the web interface
diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf
index 71529378b3..9a98f73d6e 100644
--- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf
+++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf
@@ -176,10 +176,25 @@ location {{ location.matchtype }} {{ location.urlpattern }} {
proxy_set_header Early-Data $ssl_early_data;
{% endif %}
proxy_set_header X-Real-IP $remote_addr;
+{% if upstream.xff_header_mode is defined and upstream.xff_header_mode == 'replace' %}
+ proxy_set_header X-Forwarded-For $remote_addr;
+{% elif upstream.xff_header_mode is defined and upstream.xff_header_mode == 'drop' %}
+ proxy_set_header X-Forwarded-For "";
+{% else %}
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+{% endif %}
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host {% if upstream.x_forwarded_host_verbatim is defined and upstream.x_forwarded_host_verbatim == '1'%}$http_host{% else %}$host{% endif %};
+{% if upstream.forwarded_header_mode is defined and upstream.forwarded_header_mode == 'replace' %}
+ proxy_set_header Forwarded $proxy_forwarded;
+{% elif upstream.forwarded_header_mode is defined and upstream.forwarded_header_mode == 'drop' %}
+ proxy_set_header Forwarded "";
+{% endif %}
+{% if upstream.suppress_client_headers is defined and upstream.suppress_client_headers == '1' %}
+ proxy_set_header CF-Connecting-IP "";
+ proxy_set_header True-Client-IP "";
+{% endif %}
proxy_set_header X-TLS-Client-Intercepted $tls_intercepted;
{% if location.proxy_read_timeout is defined and location.proxy_read_timeout != '' %}
proxy_read_timeout {{ location.proxy_read_timeout }}s;