opnsense / opnsense/plugins

www/nginx: make upstream client-address headers configurable and sanitizable

Open
#5,684 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
PHP
Stars
1.2k
Forks
863
Avg merge
2d 6h
Merged PRs (30d)
10

Description

Important notices
Before you add a new report, we ask you kindly to acknowledge the following:

Is your feature request related to a problem? Please describe.

The www/nginx plugin currently hardcodes these upstream request headers in location.conf:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host ...;

Source:

https://github.com/opnsense/plugins/blob/26f4c373a158de5920b0f5e5576625488193eb70/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf#L178-L182

There is no GUI option to change or disable X-Real-IP and X-Forwarded-For. There is also no configuration for RFC 7239 Forwarded.

The HTTP server configuration already supports real_ip_header and trusted proxy networks. This makes $remote_addr the validated client address after trusted-proxy processing.

However, $proxy_add_x_forwarded_for preserves a client-supplied X-Forwarded-For chain. This is valid for traditional proxy chains, but it does not allow OPNsense nginx to act as a strict client-IP trust boundary.

More importantly, incoming Forwarded headers are passed unchanged to the upstream because the template does not set or remove them. A backend which gives RFC 7239 Forwarded precedence can therefore accept a client-supplied spoofed address even though X-Real-IP was generated from $remote_addr.

Example topology:

Cloudflare (optional)
  -> OPNsense nginx
  -> application

With trusted Cloudflare networks and:

real_ip_header CF-Connecting-IP;

$remote_addr contains the validated Cloudflare client address. Without Cloudflare, an untrusted direct client cannot override $remote_addr, so it contains the TCP client address.

The existing include hooks cannot solve this safely:

  • http_post/*.conf cannot override the location-level proxy_set_header list because nginx inherits that list only when no directives exist at the lower level.
  • <location-uuid>_post/*.conf is included in the same location, but repeating a normal proxy_set_header name may generate duplicate header fields instead of replacing the generated definition.

Location post-hook:

https://github.com/opnsense/plugins/blob/26f4c373a158de5920b0f5e5576625488193eb70/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf#L238

Nginx documentation:

https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header

Describe the solution you'd like

Please add first-class, location-scoped configuration for upstream client-address headers. The generated configuration must emit at most one effective proxy_set_header directive per header name.

Suggested X-Forwarded-For modes:

  1. Append — current and backwards-compatible default:

    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    
  2. Replace — use the validated client address:

    proxy_set_header X-Forwarded-For $remote_addr;
    
  3. Drop:

    proxy_set_header X-Forwarded-For "";
    

Suggested Forwarded modes:

  1. Preserve — current implicit behavior and backwards-compatible default.

  2. Replace — generate a sanitized RFC 7239 value from the effective $remote_addr and request scheme.

  3. Drop:

    proxy_set_header Forwarded "";
    

RFC 7239 generation must correctly format IPv4 and IPv6 addresses. For example:

Forwarded: for=192.0.2.10;proto=https
Forwarded: for="[2001:db8::10]";proto=https

It should also be possible to suppress provider-specific client identity headers:

proxy_set_header CF-Connecting-IP "";
proxy_set_header True-Client-IP "";

An optional secure trust-boundary mode could generate:

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Forwarded "<RFC 7239 value generated from $remote_addr and $scheme>";
proxy_set_header CF-Connecting-IP "";
proxy_set_header True-Client-IP "";

Alternatively, the plugin could provide a name-based upstream request-header configuration where custom entries replace generated defaults by header name and an empty value suppresses the header.

The current behavior should remain the default for backwards compatibility.

Describe alternatives you've considered

  • Patching location.conf, which is overwritten by plugin/package upgrades.
  • Using <location-uuid>_post/*.conf, which can create duplicate headers.
  • Using http_post/*.conf, whose header directives are not inherited by generated proxy locations.
  • Trusting only X-Real-IP in the application, which does not sanitize Forwarded.
  • Cloudflare Transform Rules, which do not work when Cloudflare is disabled.
  • Reimplementing trusted proxy-chain handling independently in every backend.

Additional context

Related issues and discussions:

This request specifically concerns safe and deterministic handling of generated client-address headers. It is narrower than general arbitrary request-header support.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf, especially the generated proxy_set_header directives and location post-hook. Review the existing HTTP server real_ip_header and trusted-proxy configuration, along with related issues 3505 and 5286, before choosing the configuration model. Done means location-scoped options produce deterministic, sanitized headers while preserving current behavior by default.

Written by the indexing model from the issue text.

Assessment

Tech stack
nginx
Domain
infrastructure, networking
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.