hscontrol: gate proxy header trust on trusted_proxies

chi middleware.RealIP was mounted unconditionally on both the
public router and the noise router, so any client could send
X-Real-IP or X-Forwarded-For and have the spoofed value land in
r.RemoteAddr and the access-log remote= field.

Add a top-level trusted_proxies config option (list of CIDRs) and
replace middleware.RealIP with a gated middleware that:

  - honours True-Client-IP / X-Real-IP / X-Forwarded-For only when
    r.RemoteAddr is inside one of the configured prefixes;
  - strips those three headers from every request whose peer is
    not trusted, so downstream handlers cannot read them.

X-Forwarded-For is parsed via realclientip-go's
RightmostTrustedRangeStrategy so a prepended value cannot win in a
proxy chain. trustedProxies() rejects 0.0.0.0/0 and ::/0 at config
load.

Empty trusted_proxies (the default) skips the mount entirely;
r.RemoteAddr is the directly-connecting TCP peer.
This commit is contained in:
Kristoffer Dalby 2026-05-18 09:21:32 +00:00
parent 1f48ebb376
commit c6c29c05e5
6 changed files with 511 additions and 2 deletions

View file

@ -158,7 +158,11 @@ func (h *Headscale) NoiseUpgradeHandler(
},
}))
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
if h.realIPMiddleware != nil {
r.Use(h.realIPMiddleware)
}
r.Use(middleware.RequestLogger(&zerologRequestLogger{}))
r.Use(middleware.Recoverer)