Cybersecurity-Projects/PROJECTS/beginner/canary-token-generator/backend/internal/middleware/realip.go

109 lines
1.9 KiB
Go

// ©AngelaMos | 2026
// realip.go
package middleware
import (
"fmt"
"net"
"net/http"
"strings"
"sync/atomic"
)
const (
headerCFConnectingIP = "CF-Connecting-IP"
headerXForwardedFor = "X-Forwarded-For"
headerXRealIP = "X-Real-IP"
)
type proxyTrustState struct {
cidrs []*net.IPNet
}
var trustedProxies atomic.Pointer[proxyTrustState]
func SetTrustedProxyCIDRs(cidrs []string) error {
parsed := make([]*net.IPNet, 0, len(cidrs))
for _, c := range cidrs {
trimmed := strings.TrimSpace(c)
if trimmed == "" {
continue
}
_, n, err := net.ParseCIDR(trimmed)
if err != nil {
return fmt.Errorf("trusted proxy cidr %q: %w", c, err)
}
parsed = append(parsed, n)
}
trustedProxies.Store(&proxyTrustState{cidrs: parsed})
return nil
}
func ClearTrustedProxyCIDRs() {
trustedProxies.Store(nil)
}
func isTrustedProxy(remoteAddr string) bool {
state := trustedProxies.Load()
if state == nil {
return true
}
host, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
host = remoteAddr
}
ip := net.ParseIP(host)
if ip == nil {
return false
}
for _, n := range state.cidrs {
if n.Contains(ip) {
return true
}
}
return false
}
func RealIP(r *http.Request) string {
if isTrustedProxy(r.RemoteAddr) {
if v := strings.TrimSpace(
r.Header.Get(headerCFConnectingIP),
); v != "" {
return v
}
if v := firstNonEmptyXFF(
r.Header.Get(headerXForwardedFor),
); v != "" {
return v
}
if v := strings.TrimSpace(r.Header.Get(headerXRealIP)); v != "" {
return v
}
}
if host, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return host
}
return r.RemoteAddr
}
func OptionalHeader(v string) *string {
v = strings.TrimSpace(v)
if v == "" {
return nil
}
return &v
}
func firstNonEmptyXFF(header string) string {
if header == "" {
return ""
}
for _, p := range strings.Split(header, ",") {
if v := strings.TrimSpace(p); v != "" {
return v
}
}
return ""
}