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