[proxy] Optionally refuse private addresses on direct-upstream dials (#7913)

Direct-upstream targets are dialled on the proxy host's network stack,
outside the embedded client's LAN blocking. A proxy that serves
untrusted accounts lets them reach the host's loopback, its LAN or
cluster, and the cloud metadata service through such a target.

NB_PROXY_DIRECT_UPSTREAM_BLOCK_PRIVATE adds a dialer control that
refuses addresses that are not globally reachable. It checks each
socket's resolved address just before connect, so hostnames and DNS
rebinding are covered, and IPv4 embedded in IPv6 addresses is checked
as IPv4. Refused dials are served as a 502. The setting defaults to
off for private and self-hosted proxies; an unparsable value turns it
on.
This commit is contained in:
Brad Ison
2026-10-02 12:02:06 +02:00
committed by GitHub
parent 5ceca6e500
commit f400f4bee8
6 changed files with 329 additions and 1 deletions
+6 -1
View File
@@ -41,7 +41,9 @@ var errNoEmbeddedTransport = errors.New("multitransport: embedded roundtripper n
// MultiTransport that only ever uses the direct branch. The direct
// branches honour the same NB_PROXY_* tuning env vars as the embedded
// transport (see loadTransportConfig) plus a dial-timeout wrapper that
// respects types.WithDialTimeout.
// respects types.WithDialTimeout. With NB_PROXY_DIRECT_UPSTREAM_BLOCK_PRIVATE
// set, the direct branches refuse addresses that are not globally reachable
// (see guardUpstreamDial).
func NewMultiTransport(embedded http.RoundTripper, logger *log.Logger) *MultiTransport {
if logger == nil {
logger = log.StandardLogger()
@@ -51,6 +53,9 @@ func NewMultiTransport(embedded http.RoundTripper, logger *log.Logger) *MultiTra
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}
if cfg.blockPrivateUpstreams {
dialer.ControlContext = guardUpstreamDial
}
direct := &http.Transport{
DialContext: dialWithTimeout(dialer.DialContext),
MaxIdleConns: cfg.maxIdleConns,