76ba500417
CI / docker (push) Successful in 2m4s
OpenAI-compatible AI gateway with Vue admin UI, multi-provider egress, ingress key governance, monitoring, and security controls. Co-authored-by: Cursor <cursoragent@cursor.com>
89 lines
1.9 KiB
Go
89 lines
1.9 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
var (
|
|
trustedMu sync.RWMutex
|
|
trustedProxies []*net.IPNet
|
|
trustedIPs []net.IP
|
|
)
|
|
|
|
// ConfigureTrustedProxies sets CIDRs/IPs allowed to set X-Forwarded-For / X-Real-IP.
|
|
// When empty, only the direct remote address is used.
|
|
func ConfigureTrustedProxies(cidrs []string) {
|
|
trustedMu.Lock()
|
|
defer trustedMu.Unlock()
|
|
trustedProxies = nil
|
|
trustedIPs = nil
|
|
for _, cidr := range cidrs {
|
|
cidr = strings.TrimSpace(cidr)
|
|
if cidr == "" {
|
|
continue
|
|
}
|
|
if strings.Contains(cidr, "/") {
|
|
_, network, err := net.ParseCIDR(cidr)
|
|
if err == nil {
|
|
trustedProxies = append(trustedProxies, network)
|
|
}
|
|
continue
|
|
}
|
|
if ip := net.ParseIP(cidr); ip != nil {
|
|
trustedIPs = append(trustedIPs, ip)
|
|
}
|
|
}
|
|
}
|
|
|
|
func isTrustedProxy(ip net.IP) bool {
|
|
trustedMu.RLock()
|
|
defer trustedMu.RUnlock()
|
|
if len(trustedProxies) == 0 && len(trustedIPs) == 0 {
|
|
return false
|
|
}
|
|
for _, tip := range trustedIPs {
|
|
if tip.Equal(ip) {
|
|
return true
|
|
}
|
|
}
|
|
for _, network := range trustedProxies {
|
|
if network.Contains(ip) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func directRemoteIP(ctx *gin.Context) string {
|
|
host, _, err := net.SplitHostPort(ctx.Request.RemoteAddr)
|
|
if err != nil {
|
|
return strings.TrimSpace(ctx.Request.RemoteAddr)
|
|
}
|
|
return host
|
|
}
|
|
|
|
// ClientIP returns the client IP. Forwarded headers are honored only when the
|
|
// direct remote address is a configured trusted proxy.
|
|
func ClientIP(ctx *gin.Context) string {
|
|
remote := directRemoteIP(ctx)
|
|
remoteIP := net.ParseIP(remote)
|
|
if remoteIP == nil || !isTrustedProxy(remoteIP) {
|
|
if remoteIP != nil {
|
|
return remoteIP.String()
|
|
}
|
|
return ctx.ClientIP()
|
|
}
|
|
if xff := ctx.GetHeader("X-Forwarded-For"); xff != "" {
|
|
parts := strings.Split(xff, ",")
|
|
return strings.TrimSpace(parts[0])
|
|
}
|
|
if xri := ctx.GetHeader("X-Real-IP"); xri != "" {
|
|
return strings.TrimSpace(xri)
|
|
}
|
|
return remoteIP.String()
|
|
}
|