Files
Luminary/internal/middleware/clientip.go
T
renjue 76ba500417
CI / docker (push) Successful in 2m4s
Initial commit: Luminary AI Gateway
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>
2026-06-23 21:46:16 +08:00

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()
}