@@ -6,6 +6,130 @@ import (
6
6
"strings"
7
7
)
8
8
9
+ /**
10
+ By: https://github.com/tmshn (See: https://github.com/labstack/echo/pull/1478 , https://github.com/labstack/echox/pull/134 )
11
+ Source: https://echo.labstack.com/guide/ip-address/
12
+
13
+ IP address plays fundamental role in HTTP; it's used for access control, auditing, geo-based access analysis and more.
14
+ Echo provides handy method [`Context#RealIP()`](https://godoc.org/github.com/labstack/echo#Context) for that.
15
+
16
+ However, it is not trivial to retrieve the _real_ IP address from requests especially when you put L7 proxies before the application.
17
+ In such situation, _real_ IP needs to be relayed on HTTP layer from proxies to your app, but you must not trust HTTP headers unconditionally.
18
+ Otherwise, you might give someone a chance of deceiving you. **A security risk!**
19
+
20
+ To retrieve IP address reliably/securely, you must let your application be aware of the entire architecture of your infrastructure.
21
+ In Echo, this can be done by configuring `Echo#IPExtractor` appropriately.
22
+ This guides show you why and how.
23
+
24
+ > Note: if you dont' set `Echo#IPExtractor` explicitly, Echo fallback to legacy behavior, which is not a good choice.
25
+
26
+ Let's start from two questions to know the right direction:
27
+
28
+ 1. Do you put any HTTP (L7) proxy in front of the application?
29
+ - It includes both cloud solutions (such as AWS ALB or GCP HTTP LB) and OSS ones (such as Nginx, Envoy or Istio ingress gateway).
30
+ 2. If yes, what HTTP header do your proxies use to pass client IP to the application?
31
+
32
+ ## Case 1. With no proxy
33
+
34
+ If you put no proxy (e.g.: directory facing to the internet), all you need to (and have to) see is IP address from network layer.
35
+ Any HTTP header is untrustable because the clients have full control what headers to be set.
36
+
37
+ In this case, use `echo.ExtractIPDirect()`.
38
+
39
+ ```go
40
+ e.IPExtractor = echo.ExtractIPDirect()
41
+ ```
42
+
43
+ ## Case 2. With proxies using `X-Forwarded-For` header
44
+
45
+ [`X-Forwared-For` (XFF)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For) is the popular header
46
+ to relay clients' IP addresses.
47
+ At each hop on the proxies, they append the request IP address at the end of the header.
48
+
49
+ Following example diagram illustrates this behavior.
50
+
51
+ ```text
52
+ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
53
+ │ "Origin" │───────────>│ Proxy 1 │───────────>│ Proxy 2 │───────────>│ Your app │
54
+ │ (IP: a) │ │ (IP: b) │ │ (IP: c) │ │ │
55
+ └──────────┘ └──────────┘ └──────────┘ └──────────┘
56
+
57
+ Case 1.
58
+ XFF: "" "a" "a, b"
59
+ ~~~~~~
60
+ Case 2.
61
+ XFF: "x" "x, a" "x, a, b"
62
+ ~~~~~~~~~
63
+ ↑ What your app will see
64
+ ```
65
+
66
+ In this case, use **first _untrustable_ IP reading from right**. Never use first one reading from left, as it is
67
+ configurable by client. Here "trustable" means "you are sure the IP address belongs to your infrastructre".
68
+ In above example, if `b` and `c` are trustable, the IP address of the client is `a` for both cases, never be `x`.
69
+
70
+ In Echo, use `ExtractIPFromXFFHeader(...TrustOption)`.
71
+
72
+ ```go
73
+ e.IPExtractor = echo.ExtractIPFromXFFHeader()
74
+ ```
75
+
76
+ By default, it trusts internal IP addresses (loopback, link-local unicast, private-use and unique local address
77
+ from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and
78
+ [RFC4193](https://tools.ietf.org/html/rfc4193)).
79
+ To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s.
80
+
81
+ E.g.:
82
+
83
+ ```go
84
+ e.IPExtractor = echo.ExtractIPFromXFFHeader(
85
+ TrustLinkLocal(false),
86
+ TrustIPRanges(lbIPRange),
87
+ )
88
+ ```
89
+
90
+ - Ref: https://godoc.org/github.com/labstack/echo#TrustOption
91
+
92
+ ## Case 3. With proxies using `X-Real-IP` header
93
+
94
+ `X-Real-IP` is another HTTP header to relay clients' IP addresses, but it carries only one address unlike XFF.
95
+
96
+ If your proxies set this header, use `ExtractIPFromRealIPHeader(...TrustOption)`.
97
+
98
+ ```go
99
+ e.IPExtractor = echo.ExtractIPFromRealIPHeader()
100
+ ```
101
+
102
+ Again, it trusts internal IP addresses by default (loopback, link-local unicast, private-use and unique local address
103
+ from [RFC6890](https://tools.ietf.org/html/rfc6890), [RFC4291](https://tools.ietf.org/html/rfc4291) and
104
+ [RFC4193](https://tools.ietf.org/html/rfc4193)).
105
+ To control this behavior, use [`TrustOption`](https://godoc.org/github.com/labstack/echo#TrustOption)s.
106
+
107
+ - Ref: https://godoc.org/github.com/labstack/echo#TrustOption
108
+
109
+ > **Never forget** to configure the outermost proxy (i.e.; at the edge of your infrastructure) **not to pass through incoming headers**.
110
+ > Otherwise there is a chance of fraud, as it is what clients can control.
111
+
112
+ ## About default behavior
113
+
114
+ In default behavior, Echo sees all of first XFF header, X-Real-IP header and IP from network layer.
115
+
116
+ As you might already notice, after reading this article, this is not good.
117
+ Sole reason this is default is just backward compatibility.
118
+
119
+ ## Private IP ranges
120
+
121
+ See: https://en.wikipedia.org/wiki/Private_network
122
+
123
+ Private IPv4 address ranges (RFC 1918):
124
+ * 10.0.0.0 – 10.255.255.255 (24-bit block)
125
+ * 172.16.0.0 – 172.31.255.255 (20-bit block)
126
+ * 192.168.0.0 – 192.168.255.255 (16-bit block)
127
+
128
+ Private IPv6 address ranges:
129
+ * fc00::/7 address block = RFC 4193 Unique Local Addresses (ULA)
130
+
131
+ */
132
+
9
133
type ipChecker struct {
10
134
trustLoopback bool
11
135
trustLinkLocal bool
@@ -52,6 +176,7 @@ func newIPChecker(configs []TrustOption) *ipChecker {
52
176
return checker
53
177
}
54
178
179
+ // Go1.16+ added `ip.IsPrivate()` but until that use this implementation
55
180
func isPrivateIPRange (ip net.IP ) bool {
56
181
if ip4 := ip .To4 (); ip4 != nil {
57
182
return ip4 [0 ] == 10 ||
@@ -87,25 +212,26 @@ type IPExtractor func(*http.Request) string
87
212
// ExtractIPDirect extracts IP address using actual IP address.
88
213
// Use this if your server faces to internet directory (i.e.: uses no proxy).
89
214
func ExtractIPDirect () IPExtractor {
90
- return func (req * http.Request ) string {
91
- ra , _ , _ := net .SplitHostPort (req .RemoteAddr )
92
- return ra
93
- }
215
+ return extractIP
216
+ }
217
+
218
+ func extractIP (req * http.Request ) string {
219
+ ra , _ , _ := net .SplitHostPort (req .RemoteAddr )
220
+ return ra
94
221
}
95
222
96
223
// ExtractIPFromRealIPHeader extracts IP address using x-real-ip header.
97
224
// Use this if you put proxy which uses this header.
98
225
func ExtractIPFromRealIPHeader (options ... TrustOption ) IPExtractor {
99
226
checker := newIPChecker (options )
100
227
return func (req * http.Request ) string {
101
- directIP := ExtractIPDirect ()(req )
102
228
realIP := req .Header .Get (HeaderXRealIP )
103
229
if realIP != "" {
104
- if ip := net .ParseIP (directIP ); ip != nil && checker .trust (ip ) {
230
+ if ip := net .ParseIP (realIP ); ip != nil && checker .trust (ip ) {
105
231
return realIP
106
232
}
107
233
}
108
- return directIP
234
+ return extractIP ( req )
109
235
}
110
236
}
111
237
@@ -115,7 +241,7 @@ func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
115
241
func ExtractIPFromXFFHeader (options ... TrustOption ) IPExtractor {
116
242
checker := newIPChecker (options )
117
243
return func (req * http.Request ) string {
118
- directIP := ExtractIPDirect () (req )
244
+ directIP := extractIP (req )
119
245
xffs := req .Header [HeaderXForwardedFor ]
120
246
if len (xffs ) == 0 {
121
247
return directIP
0 commit comments