From 34daeb85f930927c981965270b436877a3a9f82a Mon Sep 17 00:00:00 2001 From: EyJunge1 <149941075+EyJunge1@users.noreply.github.com> Date: Fri, 28 Aug 2026 19:59:29 +0200 Subject: [PATCH 1/2] www/nginx: make upstream client-address headers configurable Allow X-Forwarded-For, Forwarded, and provider client-IP headers to be sanitized at the upstream so nginx can act as a strict trust boundary. --- www/nginx/Makefile | 2 +- www/nginx/pkg-descr | 1 + .../OPNsense/Nginx/forms/upstream.xml | 23 ++++++++++++++++++ .../mvc/app/models/OPNsense/Nginx/Nginx.xml | 24 ++++++++++++++++++- .../templates/OPNsense/Nginx/http.conf | 10 ++++++++ .../templates/OPNsense/Nginx/location.conf | 15 ++++++++++++ 6 files changed, 73 insertions(+), 2 deletions(-) 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..904f6d17a4 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 + Controls the X-Forwarded-For header sent to the upstream. Append (default) keeps the existing chain via $proxy_add_x_forwarded_for. Replace sends only the validated client address ($remote_addr after trusted-proxy / real_ip processing). Drop suppresses the header. Use Replace with Forwarded Replace and Suppress CF-/True-Client-IP when nginx should act as a strict client-IP trust boundary. + + + upstream.forwarded_header_mode + + + dropdown + true + Controls the RFC 7239 Forwarded header sent to the upstream. Preserve (default) leaves any client-supplied Forwarded header unchanged. Replace overwrites it with a sanitized value derived from $remote_addr and $scheme (IPv6 is quoted per RFC 7239). Drop suppresses the header. Without Replace or Drop, backends that prefer Forwarded over X-Real-IP may accept a spoofed client address. + + + upstream.suppress_client_headers + + Clear CF-Connecting-IP and True-Client-IP before proxying so provider-specific client identity headers cannot bypass the validated $remote_addr trust boundary. + 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..b426a6e507 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,16 @@ map $ssl_server_name $upstream_sni_name { '' $host; } +# 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"; +} + 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; From 324d130843944dea5317ab6429b406e89325c700 Mon Sep 17 00:00:00 2001 From: EyJunge1 <149941075+EyJunge1@users.noreply.github.com> Date: Fri, 28 Aug 2026 20:01:36 +0200 Subject: [PATCH 2/2] www/nginx: tighten client-address header help and maps Shorten upstream help text to match existing style and emit the Forwarded RFC 7239 maps only when an upstream uses Replace. --- .../mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml | 6 +++--- .../opnsense/service/templates/OPNsense/Nginx/http.conf | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) 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 904f6d17a4..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 @@ -63,7 +63,7 @@ dropdown true - Controls the X-Forwarded-For header sent to the upstream. Append (default) keeps the existing chain via $proxy_add_x_forwarded_for. Replace sends only the validated client address ($remote_addr after trusted-proxy / real_ip processing). Drop suppresses the header. Use Replace with Forwarded Replace and Suppress CF-/True-Client-IP when nginx should act as a strict client-IP trust boundary. + 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 @@ -71,12 +71,12 @@ dropdown true - Controls the RFC 7239 Forwarded header sent to the upstream. Preserve (default) leaves any client-supplied Forwarded header unchanged. Replace overwrites it with a sanitized value derived from $remote_addr and $scheme (IPv6 is quoted per RFC 7239). Drop suppresses the header. Without Replace or Drop, backends that prefer Forwarded over X-Real-IP may accept a spoofed client address. + 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 provider-specific client identity headers cannot bypass the validated $remote_addr trust boundary. + Clear CF-Connecting-IP and True-Client-IP before proxying so they cannot bypass the validated client address. true checkbox 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 b426a6e507..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,13 @@ 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"; @@ -81,6 +88,7 @@ map $remote_addr $forwarded_for { map $scheme $proxy_forwarded { default "$forwarded_for;proto=$scheme"; } +{% endif %} include http_post/*.conf;