- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
106 lines
2.2 KiB
Go
106 lines
2.2 KiB
Go
package surf
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"strings"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/compass"
|
|
)
|
|
|
|
// ClientIP is the single source of client IP for limiter keys (D-04).
|
|
// RemoteAddr is used unless it parses as being inside one of trusted;
|
|
// in that case the rightmost X-Forwarded-For hop NOT inside any trusted
|
|
// prefix is used. An empty trusted list means RemoteAddr only.
|
|
func ClientIP(r *http.Request, trusted []netip.Prefix) string {
|
|
if r == nil {
|
|
return ""
|
|
}
|
|
remote := parseIP(r.RemoteAddr)
|
|
if len(trusted) == 0 || remote == (netip.Addr{}) || !addrTrusted(remote, trusted) {
|
|
if remote == (netip.Addr{}) {
|
|
return ""
|
|
}
|
|
return remote.String()
|
|
}
|
|
xff := r.Header.Get("X-Forwarded-For")
|
|
if xff == "" {
|
|
return remote.String()
|
|
}
|
|
hops := strings.Split(xff, ",")
|
|
for i := len(hops) - 1; i >= 0; i-- {
|
|
hop := strings.TrimSpace(hops[i])
|
|
if hop == "" {
|
|
continue
|
|
}
|
|
addr, err := netip.ParseAddr(hop)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
addr = addr.Unmap()
|
|
if !addrTrusted(addr, trusted) {
|
|
return addr.String()
|
|
}
|
|
}
|
|
return remote.String()
|
|
}
|
|
|
|
// TrustedProxies reads http.trusted_proxies (a []string of CIDRs) from cfg
|
|
// and parses it once into []netip.Prefix. A malformed entry is skipped, not
|
|
// fatal (logged by the caller if desired).
|
|
func TrustedProxies(cfg *compass.Config) []netip.Prefix {
|
|
if cfg == nil {
|
|
return nil
|
|
}
|
|
raw, ok := cfg.Lookup("http.trusted_proxies")
|
|
if !ok {
|
|
return nil
|
|
}
|
|
var entries []string
|
|
switch v := raw.(type) {
|
|
case []string:
|
|
entries = v
|
|
case []any:
|
|
for _, item := range v {
|
|
s, _ := item.(string)
|
|
if s != "" {
|
|
entries = append(entries, s)
|
|
}
|
|
}
|
|
default:
|
|
return nil
|
|
}
|
|
var out []netip.Prefix
|
|
for _, e := range entries {
|
|
p, err := netip.ParsePrefix(strings.TrimSpace(e))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
out = append(out, p)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parseIP(remoteAddr string) netip.Addr {
|
|
host, _, err := net.SplitHostPort(remoteAddr)
|
|
if err != nil {
|
|
host = remoteAddr
|
|
}
|
|
host = strings.Trim(host, "[]")
|
|
addr, err := netip.ParseAddr(host)
|
|
if err != nil {
|
|
return netip.Addr{}
|
|
}
|
|
return addr.Unmap()
|
|
}
|
|
|
|
func addrTrusted(addr netip.Addr, trusted []netip.Prefix) bool {
|
|
for _, p := range trusted {
|
|
if p.Contains(addr) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|