Accept CIDR ranges in proxy trusted-proxies configuration
The HTTP middleware and proxy-protocol listeners both pinned trust to exact IPs, which forced re-applying terraform every time AWS rotated an ALB or NLB ENI. Trusted-proxies entries now accept CIDR ranges in addition to plain IPs, so callers can trust whole subnets (where the load balancer ENIs always live) and stop chasing rotating IPs. The HTTP middleware splits parsed entries into IPs and IPNets and checks both. The proxy-protocol listeners switch from TrustProxyHeaderFrom (IP-only, REJECT) to ConnStrictWhiteListPolicy (IP or CIDR, REJECT) which preserves the existing reject-on-unknown semantics. Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -15,8 +15,10 @@
|
||||
package trustedproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var forwardedHeaders = []string{
|
||||
@@ -26,22 +28,52 @@ var forwardedHeaders = []string{
|
||||
|
||||
// NewMiddleware returns an HTTP middleware that strips forwarded
|
||||
// headers from requests that did not originate from one of the given
|
||||
// trusted proxy IPs. When the list is empty every request is treated
|
||||
// as untrusted and the headers are always removed.
|
||||
func NewMiddleware(trusted []net.IP) func(http.Handler) http.Handler {
|
||||
// trusted proxies. Each entry in trusted may be either a single IP
|
||||
// address (e.g. "10.0.0.1") or a CIDR range (e.g. "10.0.0.0/24").
|
||||
// When the list is empty every request is treated as untrusted and
|
||||
// the headers are always removed. An error is returned if any entry
|
||||
// is neither a valid IP nor a valid CIDR.
|
||||
func NewMiddleware(trusted []string) (func(http.Handler) http.Handler, error) {
|
||||
ips, nets, err := parseTrusted(trusted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !isTrusted(r.RemoteAddr, trusted) {
|
||||
if !isTrusted(r.RemoteAddr, ips, nets) {
|
||||
for _, h := range forwardedHeaders {
|
||||
r.Header.Del(h)
|
||||
}
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isTrusted(remoteAddr string, trusted []net.IP) bool {
|
||||
func parseTrusted(trusted []string) ([]net.IP, []*net.IPNet, error) {
|
||||
ips := make([]net.IP, 0, len(trusted))
|
||||
nets := make([]*net.IPNet, 0, len(trusted))
|
||||
for _, entry := range trusted {
|
||||
if strings.Contains(entry, "/") {
|
||||
_, ipNet, err := net.ParseCIDR(entry)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot parse CIDR %q: %w", entry, err)
|
||||
}
|
||||
nets = append(nets, ipNet)
|
||||
continue
|
||||
}
|
||||
|
||||
ip := net.ParseIP(entry)
|
||||
if ip == nil {
|
||||
return nil, nil, fmt.Errorf("cannot parse IP address %q", entry)
|
||||
}
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
return ips, nets, nil
|
||||
}
|
||||
|
||||
func isTrusted(remoteAddr string, ips []net.IP, nets []*net.IPNet) bool {
|
||||
host, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err != nil {
|
||||
host = remoteAddr
|
||||
@@ -52,11 +84,17 @@ func isTrusted(remoteAddr string, trusted []net.IP) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, t := range trusted {
|
||||
for _, t := range ips {
|
||||
if t.Equal(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range nets {
|
||||
if n.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user