-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathip.go
More file actions
174 lines (152 loc) · 4.09 KB
/
Copy pathip.go
File metadata and controls
174 lines (152 loc) · 4.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package httpserver
import (
"fmt"
"net"
"net/http"
"net/netip"
"strings"
"sync"
)
var (
trustedProxyMu sync.RWMutex
trustedProxyNets = defaultTrustedProxyCIDRs()
)
func defaultTrustedProxyCIDRs() []netip.Prefix {
return []netip.Prefix{
netip.MustParsePrefix("127.0.0.0/8"),
netip.MustParsePrefix("::1/128"),
}
}
// SetTrustedProxyCIDRs configures which direct peers are allowed to supply
// trusted forwarded headers (X-Forwarded-For).
//
// Input format:
// - comma-separated CIDRs ("10.0.0.0/8,192.168.1.0/24")
// - or comma-separated IPs ("127.0.0.1,::1")
// - empty string resets to loopback defaults
// - "none" disables forwarded-header trust entirely
func SetTrustedProxyCIDRs(spec string) error {
prefixes, err := parseTrustedProxyCIDRs(spec)
if err != nil {
return err
}
trustedProxyMu.Lock()
trustedProxyNets = prefixes
trustedProxyMu.Unlock()
return nil
}
func parseTrustedProxyCIDRs(spec string) ([]netip.Prefix, error) {
trimmed := strings.TrimSpace(spec)
if trimmed == "" {
return defaultTrustedProxyCIDRs(), nil
}
if strings.EqualFold(trimmed, "none") {
return nil, nil
}
parts := strings.Split(trimmed, ",")
prefixes := make([]netip.Prefix, 0, len(parts))
for _, p := range parts {
part := strings.TrimSpace(p)
if part == "" {
continue
}
if strings.Contains(part, "/") {
prefix, err := netip.ParsePrefix(part)
if err != nil {
return nil, fmt.Errorf("invalid CIDR %q: %w", part, err)
}
prefixes = append(prefixes, prefix.Masked())
continue
}
addr, err := netip.ParseAddr(part)
if err != nil {
return nil, fmt.Errorf("invalid IP %q: %w", part, err)
}
bits := 32
if addr.Is6() {
bits = 128
}
prefixes = append(prefixes, netip.PrefixFrom(addr, bits))
}
return prefixes, nil
}
func isTrustedProxy(addr netip.Addr) bool {
trustedProxyMu.RLock()
defer trustedProxyMu.RUnlock()
for _, p := range trustedProxyNets {
if p.Contains(addr) {
return true
}
}
return false
}
// ClientIP returns the best-guess client IP address for the request.
//
// Security model:
// - X-Forwarded-For is trusted only when the direct peer is in the
// configured trusted-proxy CIDRs.
// - otherwise we ignore forwarded headers and use RemoteAddr directly.
//
// When X-Forwarded-For is trusted, we strip trusted-proxy hops from the right
// and return the rightmost remaining (untrusted) address — i.e. the leftmost
// address not appended by a known proxy. This prevents both spoofed leftmost
// entries and intermediate-proxy IPs from being mistaken for the real client.
func ClientIP(r *http.Request) string {
remote, ok := remoteIP(r.RemoteAddr)
if !ok {
return ""
}
if isTrustedProxy(remote) {
if addr, ok := firstUntrustedForwardedIP(r.Header.Get("X-Forwarded-For")); ok {
return addr.String()
}
}
return remote.String()
}
// IsTrustedPeer reports whether the direct peer of r is in the configured
// trusted-proxy set.
func IsTrustedPeer(r *http.Request) bool {
remote, ok := remoteIP(r.RemoteAddr)
if !ok {
return false
}
return isTrustedProxy(remote)
}
func remoteIP(remoteAddr string) (netip.Addr, bool) {
host, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
return netip.Addr{}, false
}
addr, err := netip.ParseAddr(host)
if err != nil {
return netip.Addr{}, false
}
return addr, true
}
// firstUntrustedForwardedIP scans the X-Forwarded-For list right-to-left,
// skipping entries whose IP falls within a trusted-proxy CIDR, and returns
// the rightmost remaining (untrusted) address.
//
// This handles multi-proxy chains correctly: trusted intermediate proxies are
// stripped so the result is the first address that was not injected by a known
// proxy — typically the real client IP.
func firstUntrustedForwardedIP(xff string) (netip.Addr, bool) {
if xff == "" {
return netip.Addr{}, false
}
parts := strings.Split(xff, ",")
for i := len(parts) - 1; i >= 0; i-- {
ipStr := strings.TrimSpace(parts[i])
if ipStr == "" {
continue
}
addr, err := netip.ParseAddr(ipStr)
if err != nil {
continue
}
if !isTrustedProxy(addr) {
return addr, true
}
}
return netip.Addr{}, false
}