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