This commit is contained in:
bluepython508 committed 2026-10-22 12:27:19 +01:00
1 parent 3530afb854
commit 6903fcff27
2146 files changed
+112256 -190703

No files matched your search

+274 -42
View File
@@ -11,7 +11,12 @@ import (
//
// See below for the different policies.
//
// In case an error is returned the connection is denied.
// In case an error is returned the connection is denied: an error wrapping
// ErrInvalidUpstream denies just that connection while Listener.Accept keeps
// listening; any other error is returned by Accept itself, which typically
// stops the caller's accept loop.
//
// Deprecated: use ConnPolicyFunc instead.
type PolicyFunc func(upstream net.Addr) (Policy, error)
// ConnPolicyFunc can be used to decide whether to trust the PROXY info
@@ -20,7 +25,10 @@ type PolicyFunc func(upstream net.Addr) (Policy, error)
//
// See below for the different policies.
//
// In case an error is returned the connection is denied.
// In case an error is returned the connection is denied: an error wrapping
// ErrInvalidUpstream denies just that connection while Listener.Accept keeps
// listening; any other error is returned by Accept itself, which typically
// stops the caller's accept loop.
type ConnPolicyFunc func(connPolicyOptions ConnPolicyOptions) (Policy, error)
// ConnPolicyOptions contains the remote and local addresses of a connection.
@@ -33,35 +41,67 @@ type ConnPolicyOptions struct {
type Policy int
const (
// USE address from PROXY header
// USE address from PROXY header.
USE Policy = iota
// IGNORE address from PROXY header, but accept connection
// IGNORE address from PROXY header, but accept connection.
IGNORE
// REJECT connection when PROXY header is sent
// Note: even though the first read on the connection returns an error if
// a PROXY header is present, subsequent reads do not. It is the task of
// the code using the connection to handle that case properly.
// Note: if a PROXY header is present the first read returns
// ErrSuperfluousProxyHeader, and every subsequent read returns the same
// error, so the connection should be closed.
REJECT
// REQUIRE connection to send PROXY header, reject if not present
// Note: even though the first read on the connection returns an error if
// a PROXY header is not present, subsequent reads do not. It is the task
// of the code using the connection to handle that case properly.
// Note: if no PROXY header is present the first read returns
// ErrNoProxyProtocol, and every subsequent read returns the same error,
// so the connection should be closed.
REQUIRE
// SKIP accepts a connection without requiring the PROXY header
// SKIP accepts a connection without requiring the PROXY header.
// Note: an example usage can be found in the SkipProxyHeaderForCIDR
// function.
//
// On a Listener, SKIP short-circuits Accept and returns the raw, unwrapped
// connection. On a Conn (via WithPolicy), a PROXY header that is present is
// still consumed from the stream but discarded: ProxyHeader returns nil and
// no validation runs.
SKIP
)
// SkipProxyHeaderForCIDR returns a PolicyFunc which can be used to accept a
// connection from a skipHeaderCIDR without requiring a PROXY header, e.g.
// DefaultPolicy is the policy applied when none is configured explicitly: by
// NewConn when no WithPolicy option is given, and by Listener.Accept when
// neither Policy nor ConnPolicy is set.
//
// It defaults to REQUIRE, per the spec's mandate that a receiver "MUST not try
// to guess whether the protocol header is present or not": a connection that
// does not open with a PROXY header fails its first Read/Write with
// ErrNoProxyProtocol. Note REQUIRE alone still honors headers from any peer;
// restricting who may send one needs a policy such as TrustProxyHeaderFrom or
// TrustProxyHeaderFromRanges. (The deprecated *WhiteListPolicy helpers are
// NOT equivalent: they return USE for allowed peers, which replaces this
// default and makes the header optional again — that is why they were
// deprecated in favor of the explicit PolicyFromRanges.)
//
// Deployments that relied on the historical optional-header behavior can
// restore it process-wide with:
//
// proxyproto.DefaultPolicy = proxyproto.USE
//
// Like DefaultReadHeaderTimeout, this is a package-level variable to keep it
// easy to override. Set it at program init; it must not be modified
// concurrently with accepting connections.
var DefaultPolicy = REQUIRE
// ConnSkipProxyHeaderForCIDR returns a ConnPolicyFunc which can be used to accept
// a connection from a skipHeaderCIDR without requiring a PROXY header, e.g.
// Kubernetes pods local traffic. The def is a policy to use when an upstream
// address doesn't match the skipHeaderCIDR.
func SkipProxyHeaderForCIDR(skipHeaderCIDR *net.IPNet, def Policy) PolicyFunc {
return func(upstream net.Addr) (Policy, error) {
ip, err := ipFromAddr(upstream)
func ConnSkipProxyHeaderForCIDR(skipHeaderCIDR *net.IPNet, def Policy) ConnPolicyFunc {
return func(connOpts ConnPolicyOptions) (Policy, error) {
ip, err := ipFromAddr(connOpts.Upstream)
if err != nil {
return def, err
// Deny only this connection: wrapping ErrInvalidUpstream keeps
// Listener.Accept listening instead of surfacing the error and
// stopping the caller's accept loop.
return def, fmt.Errorf("%w: %w", ErrInvalidUpstream, err)
}
if skipHeaderCIDR != nil && skipHeaderCIDR.Contains(ip) {
@@ -72,31 +112,66 @@ func SkipProxyHeaderForCIDR(skipHeaderCIDR *net.IPNet, def Policy) PolicyFunc {
}
}
// WithPolicy adds given policy to a connection when passed as option to NewConn()
// SkipProxyHeaderForCIDR returns a PolicyFunc which can be used to accept a
// connection from a skipHeaderCIDR without requiring a PROXY header, e.g.
// Kubernetes pods local traffic. The def is a policy to use when an upstream
// address doesn't match the skipHeaderCIDR.
//
// Deprecated: use ConnSkipProxyHeaderForCIDR instead.
func SkipProxyHeaderForCIDR(skipHeaderCIDR *net.IPNet, def Policy) PolicyFunc {
connPolicy := ConnSkipProxyHeaderForCIDR(skipHeaderCIDR, def)
return func(upstream net.Addr) (Policy, error) {
return connPolicy(ConnPolicyOptions{Upstream: upstream})
}
}
// WithPolicy adds given policy to a connection when passed as option to NewConn().
func WithPolicy(p Policy) func(*Conn) {
return func(c *Conn) {
c.ProxyHeaderPolicy = p
}
}
// ConnLaxWhiteListPolicy returns a ConnPolicyFunc which decides whether the
// upstream ip is allowed to send a proxy header based on a list of allowed
// IP addresses and IP ranges. In case upstream IP is not in list the proxy
// header will be ignored. If one of the provided IP addresses or IP ranges
// is invalid it will return an error instead of a ConnPolicyFunc.
//
// Deprecated: the name hides that the header stays optional (USE) for allowed
// peers, overriding the REQUIRE default. Use the equivalent, explicit
// PolicyFromRanges(allowed, USE, IGNORE), or TrustProxyHeaderFromRanges for
// the spec-strict header-mandatory posture.
func ConnLaxWhiteListPolicy(allowed []string) (ConnPolicyFunc, error) {
return PolicyFromRanges(allowed, USE, IGNORE)
}
// LaxWhiteListPolicy returns a PolicyFunc which decides whether the
// upstream ip is allowed to send a proxy header based on a list of allowed
// IP addresses and IP ranges. In case upstream IP is not in list the proxy
// header will be ignored. If one of the provided IP addresses or IP ranges
// is invalid it will return an error instead of a PolicyFunc.
//
// Deprecated: use PolicyFromRanges(allowed, USE, IGNORE) instead; see
// ConnLaxWhiteListPolicy for why.
func LaxWhiteListPolicy(allowed []string) (PolicyFunc, error) {
allowFrom, err := parse(allowed)
connPolicy, err := ConnLaxWhiteListPolicy(allowed)
if err != nil {
return nil, err
}
return whitelistPolicy(allowFrom, IGNORE), nil
return func(upstream net.Addr) (Policy, error) {
return connPolicy(ConnPolicyOptions{Upstream: upstream})
}, nil
}
// MustLaxWhiteListPolicy returns a LaxWhiteListPolicy but will panic if one
// of the provided IP addresses or IP ranges is invalid.
func MustLaxWhiteListPolicy(allowed []string) PolicyFunc {
pfunc, err := LaxWhiteListPolicy(allowed)
// ConnMustLaxWhiteListPolicy returns a ConnLaxWhiteListPolicy but will panic
// if one of the provided IP addresses or IP ranges is invalid.
//
// Deprecated: use MustPolicyFromRanges(allowed, USE, IGNORE) instead; see
// ConnLaxWhiteListPolicy for why.
func ConnMustLaxWhiteListPolicy(allowed []string) ConnPolicyFunc {
pfunc, err := ConnLaxWhiteListPolicy(allowed)
if err != nil {
panic(err)
}
@@ -104,26 +179,63 @@ func MustLaxWhiteListPolicy(allowed []string) PolicyFunc {
return pfunc
}
// MustLaxWhiteListPolicy returns a LaxWhiteListPolicy but will panic if one
// of the provided IP addresses or IP ranges is invalid.
//
// Deprecated: use MustPolicyFromRanges(allowed, USE, IGNORE) instead; see
// ConnLaxWhiteListPolicy for why.
func MustLaxWhiteListPolicy(allowed []string) PolicyFunc {
connPolicy := ConnMustLaxWhiteListPolicy(allowed)
return func(upstream net.Addr) (Policy, error) {
return connPolicy(ConnPolicyOptions{Upstream: upstream})
}
}
// ConnStrictWhiteListPolicy returns a ConnPolicyFunc which decides whether the
// upstream ip is allowed to send a proxy header based on a list of allowed
// IP addresses and IP ranges. In case upstream IP is not in list reading on
// the connection will be refused: the first read returns
// ErrSuperfluousProxyHeader and every subsequent read returns the same error,
// so the connection should be closed. If one of the provided IP addresses or IP
// ranges is invalid it will return an error instead of a ConnPolicyFunc.
//
// Deprecated: "strict" only refers to refusing headers from unlisted peers;
// the header stays optional (USE) for allowed peers, overriding the REQUIRE
// default. Use the equivalent, explicit PolicyFromRanges(allowed, USE,
// REJECT), or TrustProxyHeaderFromRanges for the spec-strict header-mandatory
// posture.
func ConnStrictWhiteListPolicy(allowed []string) (ConnPolicyFunc, error) {
return PolicyFromRanges(allowed, USE, REJECT)
}
// StrictWhiteListPolicy returns a PolicyFunc which decides whether the
// upstream ip is allowed to send a proxy header based on a list of allowed
// IP addresses and IP ranges. In case upstream IP is not in list reading on
// the connection will be refused on the first read. Please note: subsequent
// reads do not error. It is the task of the code using the connection to
// handle that case properly. If one of the provided IP addresses or IP
// the connection will be refused: the first read returns
// ErrSuperfluousProxyHeader and every subsequent read returns the same error,
// so the connection should be closed. If one of the provided IP addresses or IP
// ranges is invalid it will return an error instead of a PolicyFunc.
//
// Deprecated: use PolicyFromRanges(allowed, USE, REJECT) instead; see
// ConnStrictWhiteListPolicy for why.
func StrictWhiteListPolicy(allowed []string) (PolicyFunc, error) {
allowFrom, err := parse(allowed)
connPolicy, err := ConnStrictWhiteListPolicy(allowed)
if err != nil {
return nil, err
}
return whitelistPolicy(allowFrom, REJECT), nil
return func(upstream net.Addr) (Policy, error) {
return connPolicy(ConnPolicyOptions{Upstream: upstream})
}, nil
}
// MustStrictWhiteListPolicy returns a StrictWhiteListPolicy but will panic
// ConnMustStrictWhiteListPolicy returns a ConnStrictWhiteListPolicy but will panic
// if one of the provided IP addresses or IP ranges is invalid.
func MustStrictWhiteListPolicy(allowed []string) PolicyFunc {
pfunc, err := StrictWhiteListPolicy(allowed)
//
// Deprecated: use MustPolicyFromRanges(allowed, USE, REJECT) instead; see
// ConnStrictWhiteListPolicy for why.
func ConnMustStrictWhiteListPolicy(allowed []string) ConnPolicyFunc {
pfunc, err := ConnStrictWhiteListPolicy(allowed)
if err != nil {
panic(err)
}
@@ -131,24 +243,74 @@ func MustStrictWhiteListPolicy(allowed []string) PolicyFunc {
return pfunc
}
func whitelistPolicy(allowed []func(net.IP) bool, def Policy) PolicyFunc {
// MustStrictWhiteListPolicy returns a StrictWhiteListPolicy but will panic
// if one of the provided IP addresses or IP ranges is invalid.
//
// Deprecated: use MustPolicyFromRanges(allowed, USE, REJECT) instead; see
// ConnStrictWhiteListPolicy for why.
func MustStrictWhiteListPolicy(allowed []string) PolicyFunc {
connPolicy := ConnMustStrictWhiteListPolicy(allowed)
return func(upstream net.Addr) (Policy, error) {
upstreamIP, err := ipFromAddr(upstream)
return connPolicy(ConnPolicyOptions{Upstream: upstream})
}
}
func connRangesPolicy(allowed []func(net.IP) bool, matched, unmatched Policy) ConnPolicyFunc {
return func(connOpts ConnPolicyOptions) (Policy, error) {
upstreamIP, err := ipFromAddr(connOpts.Upstream)
if err != nil {
// something is wrong with the source IP, better reject the connection
return REJECT, err
// Something is wrong with the source IP: deny only this connection.
// Wrapping ErrInvalidUpstream keeps Listener.Accept listening instead
// of surfacing the error and stopping the caller's accept loop.
return REJECT, fmt.Errorf("%w: %w", ErrInvalidUpstream, err)
}
for _, allowFrom := range allowed {
if allowFrom(upstreamIP) {
return USE, nil
return matched, nil
}
}
return def, nil
return unmatched, nil
}
}
// PolicyFromRanges returns a ConnPolicyFunc that applies the matched policy to
// connections whose upstream address belongs to ranges, and the unmatched
// policy to every other connection. Each entry in ranges may be an individual
// IP address ("10.0.0.10") or a CIDR range ("10.0.0.0/24"); an invalid entry
// returns an error instead of a ConnPolicyFunc. Connections whose upstream
// address cannot be classified are dropped by Listener.Accept via an
// ErrInvalidUpstream-wrapping error.
//
// It is the explicit replacement for the deprecated *WhiteListPolicy helpers:
//
// PolicyFromRanges(ranges, USE, IGNORE) // ConnLaxWhiteListPolicy
// PolicyFromRanges(ranges, USE, REJECT) // ConnStrictWhiteListPolicy
//
// Both of those combinations leave the header optional for matched peers. For
// the spec-strict posture — trusted sources MUST send a header, everything
// else is dropped — use TrustProxyHeaderFromRanges instead.
func PolicyFromRanges(ranges []string, matched, unmatched Policy) (ConnPolicyFunc, error) {
allowFrom, err := parse(ranges)
if err != nil {
return nil, err
}
return connRangesPolicy(allowFrom, matched, unmatched), nil
}
// MustPolicyFromRanges returns PolicyFromRanges and panics if any entry in
// ranges is invalid. Intended for static configuration known at program init.
func MustPolicyFromRanges(ranges []string, matched, unmatched Policy) ConnPolicyFunc {
pfunc, err := PolicyFromRanges(ranges, matched, unmatched)
if err != nil {
panic(err)
}
return pfunc
}
func parse(allowed []string) ([]func(net.IP) bool, error) {
a := make([]func(net.IP) bool, len(allowed))
for i, allowFrom := range allowed {
@@ -186,15 +348,85 @@ func ipFromAddr(upstream net.Addr) (net.IP, error) {
return upstreamIP, nil
}
// IgnoreProxyHeaderNotOnInterface retuns a ConnPolicyFunc which can be used to
// TrustProxyHeaderFrom returns a ConnPolicyFunc implementing the spec's
// receiver posture end to end: connections from trusted IPs MUST carry a PROXY
// header (REQUIRE — absence fails the first Read with ErrNoProxyProtocol, so
// header presence is never guessed), and connections from any other source are
// dropped by Listener.Accept without stopping the listener.
//
// Note the REJECT policy alone cannot provide the second half: REJECT refuses
// connections that DO send a header, but a headerless connection from an
// untrusted peer would still be served raw, sharing the port between PROXY and
// non-PROXY traffic — exactly what the spec's security model forbids.
func TrustProxyHeaderFrom(trustedIPs ...net.IP) ConnPolicyFunc {
return func(connOpts ConnPolicyOptions) (Policy, error) {
ip, err := ipFromAddr(connOpts.Upstream)
if err != nil {
// Deny only this connection: wrapping ErrInvalidUpstream keeps
// Listener.Accept listening instead of surfacing the error and
// stopping the caller's accept loop.
return REJECT, fmt.Errorf("%w: %w", ErrInvalidUpstream, err)
}
for _, trustedIP := range trustedIPs {
if trustedIP.Equal(ip) {
return REQUIRE, nil
}
}
return REJECT, fmt.Errorf("%w: %s is not a trusted PROXY sender", ErrInvalidUpstream, ip)
}
}
// TrustProxyHeaderFromRanges is the CIDR-capable variant of
// TrustProxyHeaderFrom, with the same spec posture: connections from the
// trusted set MUST carry a PROXY header (REQUIRE), and connections from any
// other source are dropped by Listener.Accept without stopping the listener.
// Each entry in trusted may be an individual IP address ("10.0.0.10") or a
// CIDR range ("10.0.0.0/24"); an invalid entry returns an error instead of a
// ConnPolicyFunc.
//
// Unlike the *WhiteListPolicy helpers — which make the header optional (USE)
// for allowed peers and merely ignore or refuse the header for denied ones —
// this helper never guesses whether a header is present.
func TrustProxyHeaderFromRanges(trusted []string) (ConnPolicyFunc, error) {
allowFrom, err := parse(trusted)
if err != nil {
return nil, err
}
return func(connOpts ConnPolicyOptions) (Policy, error) {
ip, err := ipFromAddr(connOpts.Upstream)
if err != nil {
// Deny only this connection: wrapping ErrInvalidUpstream keeps
// Listener.Accept listening instead of surfacing the error and
// stopping the caller's accept loop.
return REJECT, fmt.Errorf("%w: %w", ErrInvalidUpstream, err)
}
for _, allow := range allowFrom {
if allow(ip) {
return REQUIRE, nil
}
}
return REJECT, fmt.Errorf("%w: %s is not a trusted PROXY sender", ErrInvalidUpstream, ip)
}, nil
}
// IgnoreProxyHeaderNotOnInterface returns a ConnPolicyFunc which can be used to
// decide whether to use or ignore PROXY headers depending on the connection
// being made on a specific interface. This policy can be used when the server
// is bound to multiple interfaces but wants to allow on only one interface.
// being made on specific interfaces. This policy can be used when the server
// is bound to multiple interfaces but wants to allow on one or more interfaces.
func IgnoreProxyHeaderNotOnInterface(allowedIP net.IP) ConnPolicyFunc {
return func(connOpts ConnPolicyOptions) (Policy, error) {
ip, err := ipFromAddr(connOpts.Downstream)
if err != nil {
return REJECT, err
// The local (downstream) address cannot be classified; deny only this
// connection. Wrapping ErrInvalidUpstream keeps Listener.Accept
// listening instead of surfacing the error and stopping the caller's
// accept loop.
return REJECT, fmt.Errorf("%w: %w", ErrInvalidUpstream, err)
}
if allowedIP.Equal(ip) {