Skip to content

Commit 8a930ff

Browse files
authored
feat: configurable ProxyFix trusted-hop count (#115)
Make the number of trusted reverse-proxy hops configurable via the PROXY_FIX_HOPS env var (default 1, unchanged behaviour). With two hops in front (e.g. Cloudflare then Traefik) the app's own login and audit logs would otherwise record the intermediate proxy's IP. The value is clamped to >= 0 (0 ignores X-Forwarded-For entirely), logged at startup as Trusted Hops, and surfaced by the client IP diagnostic.
1 parent 1e34823 commit 8a930ff

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

app.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,14 @@
3737
logger = logging.getLogger("traefik-manager")
3838

3939

40+
def _proxy_fix_hops() -> int:
41+
try:
42+
return max(0, int(os.environ.get('PROXY_FIX_HOPS', '1')))
43+
except ValueError:
44+
return 1
45+
4046
app = Flask(__name__)
41-
PROXY_FIX_HOPS = 1
47+
PROXY_FIX_HOPS = _proxy_fix_hops()
4248
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=PROXY_FIX_HOPS, x_proto=1, x_host=1)
4349

4450
_CONFIG_DIR = os.path.dirname(os.environ.get('SETTINGS_PATH', '/app/config/manager.yml'))
@@ -1240,6 +1246,7 @@ def _read_traefik_labels():
12401246
logger.info(f"Backup Dir: {BACKUP_DIR}")
12411247
logger.info(f"Traefik API: {_s['traefik_api_url']}")
12421248
logger.info(f"Restart Method: {_restart_meth}")
1249+
logger.info(f"Trusted Hops: {PROXY_FIX_HOPS}")
12431250
logger.info(f"Static Config: {_static_path if _static_path else 'not configured'}")
12441251
logger.info(f"Domains: {_s['domains']}")
12451252
logger.info(f"Cert Resolver: {_s['cert_resolver'] or 'not set'}")

docs/env-vars.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ Variables marked ✅ **override** the corresponding `manager.yml` field on every
8080
|---|---|---|---|
8181
| `SECRET_KEY` | _(auto-generated)_ | - | Flask session signing key |
8282
| `OTP_ENCRYPTION_KEY` | _(auto-generated)_ | - | Fernet key for encrypting TOTP secrets |
83+
| `PROXY_FIX_HOPS` | `1` | - | Number of trusted proxy hops in front of Traefik Manager for `X-Forwarded-For` |
8384

8485
---
8586

@@ -694,3 +695,29 @@ python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().
694695
::: warning
695696
If you lose this key, existing TOTP secrets become unreadable and 2FA must be re-enrolled. Back up `.otp_key` alongside your config volume.
696697
:::
698+
699+
---
700+
701+
### `PROXY_FIX_HOPS`
702+
703+
**Default:** `1`
704+
705+
How many trusted reverse-proxy hops sit in front of Traefik Manager. The app runs behind `ProxyFix`, which reads the client IP from the right of `X-Forwarded-For`; this value is how many positions it trusts. With a single proxy in front (Traefik → app) the default of `1` is correct. With two hops (e.g. Cloudflare → Traefik → app) the app's own login and audit logs would otherwise record the intermediate proxy's IP - set it to `2`.
706+
707+
The active value is shown in the startup log as `Trusted Hops` and in the Client IP Diagnostic.
708+
709+
:::tabs
710+
== Docker / Podman
711+
```yaml
712+
environment:
713+
- PROXY_FIX_HOPS=2
714+
```
715+
== Linux (systemd)
716+
```ini
717+
Environment=PROXY_FIX_HOPS=2
718+
```
719+
:::
720+
721+
::: warning
722+
Only count hops you actually control. Each trusted hop is one more `X-Forwarded-For` entry a client could forge, so setting this higher than your real proxy chain lets callers spoof their source IP past the login rate-limiter and audit log. Set it to `0` to ignore `X-Forwarded-For` entirely and use the direct connection IP.
723+
:::

0 commit comments

Comments
 (0)