Files
EvoBGP/internal/httpapi/trusted_proxy.go
T
DenozordecandCursor f8e0520817 feat(httpapi): harden control plane and speed up prefix refresh
Очередь job получила lease и fencing, телеметрия и прогресс deploy сохраняются, ревизия пишет manifest входов. Обновление префиксов не переписывает неизменённый снимок, большие наборы идут через COPY, а устаревшая ревизия не попадает в deploy. Бандл ноды кэшируется, prune и постановка job больше не держат горячий путь.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-02 17:23:59 +07:00

92 lines
2.1 KiB
Go

package httpapi
import (
"net"
"net/http"
"net/netip"
"os"
"strings"
)
// trustedProxyPrefixes returns no trusted peers when any configured CIDR is invalid.
// An empty configuration deliberately treats every connection as direct.
func trustedProxyPrefixes() []netip.Prefix {
raw := strings.TrimSpace(os.Getenv("EVOBGP_TRUSTED_PROXY_CIDRS"))
if raw == "" {
return nil
}
parts := strings.Split(raw, ",")
prefixes := make([]netip.Prefix, 0, len(parts))
for _, part := range parts {
prefix, err := netip.ParsePrefix(strings.TrimSpace(part))
if err != nil {
return nil
}
if prefix.Addr().Is4In6() {
if prefix.Bits() < 96 {
return nil
}
prefix = netip.PrefixFrom(prefix.Addr().Unmap(), prefix.Bits()-96)
}
prefixes = append(prefixes, prefix.Masked())
}
return prefixes
}
func peerAddress(r *http.Request) (netip.Addr, bool) {
if r == nil {
return netip.Addr{}, false
}
host, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr))
if err != nil {
host = strings.TrimSpace(r.RemoteAddr)
}
ip, err := netip.ParseAddr(host)
if err != nil {
return netip.Addr{}, false
}
return ip.WithZone("").Unmap(), true
}
func isTrustedProxy(ip netip.Addr, prefixes []netip.Prefix) bool {
for _, prefix := range prefixes {
if prefix.Contains(ip) || prefix.Contains(ip.Unmap()) {
return true
}
}
return false
}
func clientIPWithTrustedProxies(r *http.Request, prefixes []netip.Prefix) string {
peer, ok := peerAddress(r)
if !ok {
if r == nil {
return ""
}
return strings.TrimSpace(r.RemoteAddr)
}
if !isTrustedProxy(peer, prefixes) {
return peer.String()
}
values := r.Header.Values("X-Forwarded-For")
if len(values) != 1 || strings.TrimSpace(values[0]) == "" {
return peer.String()
}
hops := strings.Split(values[0], ",")
current := peer
for i := len(hops) - 1; i >= 0; i-- {
if !isTrustedProxy(current, prefixes) {
return current.String()
}
next, err := netip.ParseAddr(strings.TrimSpace(hops[i]))
if err != nil {
return peer.String()
}
if next.Zone() != "" {
return peer.String()
}
current = next.Unmap()
}
return current.String()
}