1package middleware
2
3import (
4 "net"
5 "net/http"
6 "net/http/httptest"
7 "testing"
8
9 "github.com/stretchr/testify/assert"
10)
11
12func TestGetClientIP(t *testing.T) {
13 // Parse trusted proxies for tests
14 trustedNets := ParseTrustedProxies([]string{"127.0.0.1", "10.0.0.0/8"})
15 noTrustedNets := []*net.IPNet{}
16 // Include a Cloudflare IP in trusted proxies to test Cf-Connecting-Ip
17 trustedWithCF := ParseTrustedProxies([]string{"127.0.0.1", "10.0.0.0/8", "173.245.48.0/20"})
18
19 tests := []struct {
20 name string
21 remoteAddr string
22 xff string
23 xRealIP string
24 cfIP string
25 trustedNets []*net.IPNet
26 wantIP string
27 }{
28 {
29 name: "from RemoteAddr with port",
30 remoteAddr: "192.168.1.100:12345",
31 trustedNets: noTrustedNets,
32 wantIP: "192.168.1.100",
33 },
34 {
35 name: "from RemoteAddr without port",
36 remoteAddr: "192.168.1.100",
37 trustedNets: noTrustedNets,
38 wantIP: "192.168.1.100",
39 },
40 {
41 name: "XFF ignored when no trusted proxies",
42 remoteAddr: "192.168.1.100:12345",
43 xff: "203.0.113.50",
44 trustedNets: noTrustedNets,
45 wantIP: "192.168.1.100",
46 },
47 {
48 name: "XFF ignored when remote is not trusted",
49 remoteAddr: "192.168.1.100:12345",
50 xff: "203.0.113.50",
51 trustedNets: trustedNets, // trusts 127.0.0.1 and 10.0.0.0/8
52 wantIP: "192.168.1.100",
53 },
54 {
55 name: "XFF honored from trusted proxy (single IP)",
56 remoteAddr: "127.0.0.1:12345",
57 xff: "203.0.113.50",
58 trustedNets: trustedNets,
59 wantIP: "203.0.113.50",
60 },
61 {
62 name: "XFF honored from trusted proxy (multiple IPs)",
63 remoteAddr: "127.0.0.1:12345",
64 xff: "203.0.113.50, 10.0.0.1",
65 trustedNets: trustedNets,
66 wantIP: "203.0.113.50",
67 },
68 {
69 name: "XFF spoof chain returns first untrusted from right",
70 remoteAddr: "127.0.0.1:12345",
71 xff: "198.51.100.123, 203.0.113.50, 10.0.0.9",
72 trustedNets: trustedNets,
73 wantIP: "203.0.113.50",
74 },
75 {
76 name: "XFF honored from trusted CIDR",
77 remoteAddr: "10.1.2.3:12345",
78 xff: "203.0.113.50",
79 trustedNets: trustedNets,
80 wantIP: "203.0.113.50",
81 },
82 {
83 name: "X-Real-IP honored from trusted proxy",
84 remoteAddr: "127.0.0.1:12345",
85 xRealIP: "203.0.113.50",
86 trustedNets: trustedNets,
87 wantIP: "203.0.113.50",
88 },
89 {
90 name: "X-Real-IP ignored when not trusted",
91 remoteAddr: "192.168.1.100:12345",
92 xRealIP: "203.0.113.50",
93 trustedNets: trustedNets,
94 wantIP: "192.168.1.100",
95 },
96 {
97 name: "XFF takes precedence over X-Real-IP",
98 remoteAddr: "127.0.0.1:12345",
99 xff: "203.0.113.50",
100 xRealIP: "203.0.113.60",
101 trustedNets: trustedNets,
102 wantIP: "203.0.113.50",
103 },
104 {
105 name: "Cf-Connecting-Ip honored from Cloudflare IP",
106 remoteAddr: "173.245.48.1:12345",
107 cfIP: "203.0.113.99",
108 trustedNets: trustedWithCF,
109 wantIP: "203.0.113.99",
110 },
111 {
112 name: "Cf-Connecting-Ip ignored from non-Cloudflare trusted proxy",
113 remoteAddr: "127.0.0.1:12345",
114 cfIP: "203.0.113.99",
115 xff: "203.0.113.50",
116 trustedNets: trustedNets,
117 wantIP: "203.0.113.50",
118 },
119 {
120 name: "Cf-Connecting-Ip ignored from untrusted IP",
121 remoteAddr: "192.168.1.100:12345",
122 cfIP: "203.0.113.99",
123 trustedNets: trustedNets,
124 wantIP: "192.168.1.100",
125 },
126 {
127 name: "IPv6 RemoteAddr",
128 remoteAddr: "[::1]:12345",
129 trustedNets: noTrustedNets,
130 wantIP: "::1",
131 },
132 }
133
134 for _, tt := range tests {
135 t.Run(tt.name, func(t *testing.T) {
136 req := httptest.NewRequest(http.MethodGet, "/", nil)
137 req.RemoteAddr = tt.remoteAddr
138 if tt.xff != "" {
139 req.Header.Set("X-Forwarded-For", tt.xff)
140 }
141 if tt.xRealIP != "" {
142 req.Header.Set("X-Real-IP", tt.xRealIP)
143 }
144 if tt.cfIP != "" {
145 req.Header.Set("Cf-Connecting-Ip", tt.cfIP)
146 }
147
148 ip := GetClientIP(req, tt.trustedNets)
149 assert.Equal(t, tt.wantIP, ip)
150 })
151 }
152}
153
154func TestParseTrustedProxies(t *testing.T) {
155 tests := []struct {
156 name string
157 proxies []string
158 testIP string
159 want bool
160 }{
161 {
162 name: "empty list",
163 proxies: []string{},
164 testIP: "192.168.1.1",
165 want: false,
166 },
167 {
168 name: "single IP match",
169 proxies: []string{"192.168.1.1"},
170 testIP: "192.168.1.1",
171 want: true,
172 },
173 {
174 name: "single IP no match",
175 proxies: []string{"192.168.1.1"},
176 testIP: "192.168.1.2",
177 want: false,
178 },
179 {
180 name: "CIDR match",
181 proxies: []string{"10.0.0.0/8"},
182 testIP: "10.1.2.3",
183 want: true,
184 },
185 {
186 name: "CIDR no match",
187 proxies: []string{"10.0.0.0/8"},
188 testIP: "192.168.1.1",
189 want: false,
190 },
191 {
192 name: "mixed IP and CIDR",
193 proxies: []string{"127.0.0.1", "10.0.0.0/8", "172.16.0.0/12"},
194 testIP: "172.20.1.1",
195 want: true,
196 },
197 {
198 name: "invalid entries ignored",
199 proxies: []string{"not-an-ip", "10.0.0.0/8"},
200 testIP: "10.1.2.3",
201 want: true,
202 },
203 {
204 name: "IPv6 single IP",
205 proxies: []string{"::1"},
206 testIP: "::1",
207 want: true,
208 },
209 {
210 name: "IPv6 CIDR",
211 proxies: []string{"fd00::/8"},
212 testIP: "fd12:3456::1",
213 want: true,
214 },
215 }
216
217 for _, tt := range tests {
218 t.Run(tt.name, func(t *testing.T) {
219 nets := ParseTrustedProxies(tt.proxies)
220 got := IsTrustedProxy(tt.testIP, nets)
221 assert.Equal(t, tt.want, got)
222 })
223 }
224}
225
226func TestIsTrustedProxy(t *testing.T) {
227 tests := []struct {
228 name string
229 ip string
230 trustedNets []*net.IPNet
231 want bool
232 }{
233 {
234 name: "empty trusted nets",
235 ip: "192.168.1.1",
236 trustedNets: []*net.IPNet{},
237 want: false,
238 },
239 {
240 name: "nil trusted nets",
241 ip: "192.168.1.1",
242 trustedNets: nil,
243 want: false,
244 },
245 {
246 name: "invalid IP",
247 ip: "not-an-ip",
248 trustedNets: ParseTrustedProxies([]string{"192.168.1.0/24"}),
249 want: false,
250 },
251 {
252 name: "empty IP",
253 ip: "",
254 trustedNets: ParseTrustedProxies([]string{"192.168.1.0/24"}),
255 want: false,
256 },
257 }
258
259 for _, tt := range tests {
260 t.Run(tt.name, func(t *testing.T) {
261 got := IsTrustedProxy(tt.ip, tt.trustedNets)
262 assert.Equal(t, tt.want, got)
263 })
264 }
265}