-
Notifications
You must be signed in to change notification settings - Fork 4.9k
/
forwarded_header.go
216 lines (182 loc) · 5.56 KB
/
forwarded_header.go
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package forwardedheaders
import (
"net"
"net/http"
"os"
"strings"
"github.com/traefik/traefik/v3/pkg/ip"
)
const (
xForwardedProto = "X-Forwarded-Proto"
xForwardedFor = "X-Forwarded-For"
xForwardedHost = "X-Forwarded-Host"
xForwardedPort = "X-Forwarded-Port"
xForwardedServer = "X-Forwarded-Server"
xForwardedURI = "X-Forwarded-Uri"
xForwardedMethod = "X-Forwarded-Method"
xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert"
xForwardedTLSClientCertInfo = "X-Forwarded-Tls-Client-Cert-Info"
xRealIP = "X-Real-Ip"
connection = "Connection"
upgrade = "Upgrade"
)
var xHeaders = []string{
xForwardedProto,
xForwardedFor,
xForwardedHost,
xForwardedPort,
xForwardedServer,
xForwardedURI,
xForwardedMethod,
xForwardedTLSClientCert,
xForwardedTLSClientCertInfo,
xRealIP,
}
// XForwarded is an HTTP handler wrapper that sets the X-Forwarded headers,
// and other relevant headers for a reverse-proxy.
// Unless insecure is set,
// it first removes all the existing values for those headers if the remote address is not one of the trusted ones.
type XForwarded struct {
insecure bool
trustedIps []string
ipChecker *ip.Checker
next http.Handler
hostname string
}
// NewXForwarded creates a new XForwarded.
func NewXForwarded(insecure bool, trustedIps []string, next http.Handler) (*XForwarded, error) {
var ipChecker *ip.Checker
if len(trustedIps) > 0 {
var err error
ipChecker, err = ip.NewChecker(trustedIps)
if err != nil {
return nil, err
}
}
hostname, err := os.Hostname()
if err != nil {
hostname = "localhost"
}
return &XForwarded{
insecure: insecure,
trustedIps: trustedIps,
ipChecker: ipChecker,
next: next,
hostname: hostname,
}, nil
}
func (x *XForwarded) isTrustedIP(ip string) bool {
if x.ipChecker == nil {
return false
}
return x.ipChecker.IsAuthorized(ip) == nil
}
// removeIPv6Zone removes the zone if the given IP is an ipv6 address and it has {zone} information in it,
// like "[fe80::d806:a55d:eb1b:49cc%vEthernet (vmxnet3 Ethernet Adapter - Virtual Switch)]:64692".
func removeIPv6Zone(clientIP string) string {
if idx := strings.Index(clientIP, "%"); idx != -1 {
return clientIP[:idx]
}
return clientIP
}
// isWebsocketRequest returns whether the specified HTTP request is a websocket handshake request.
func isWebsocketRequest(req *http.Request) bool {
containsHeader := func(name, value string) bool {
h := unsafeHeader(req.Header).Get(name)
for {
pos := strings.Index(h, ",")
if pos == -1 {
return strings.EqualFold(value, strings.TrimSpace(h))
}
if strings.EqualFold(value, strings.TrimSpace(h[:pos])) {
return true
}
h = h[pos+1:]
}
}
return containsHeader(connection, "upgrade") && containsHeader(upgrade, "websocket")
}
func forwardedPort(req *http.Request) string {
if req == nil {
return ""
}
if _, port, err := net.SplitHostPort(req.Host); err == nil && port != "" {
return port
}
if unsafeHeader(req.Header).Get(xForwardedProto) == "https" || unsafeHeader(req.Header).Get(xForwardedProto) == "wss" {
return "443"
}
if req.TLS != nil {
return "443"
}
return "80"
}
func (x *XForwarded) rewrite(outreq *http.Request) {
if clientIP, _, err := net.SplitHostPort(outreq.RemoteAddr); err == nil {
clientIP = removeIPv6Zone(clientIP)
if unsafeHeader(outreq.Header).Get(xRealIP) == "" {
unsafeHeader(outreq.Header).Set(xRealIP, clientIP)
}
}
xfProto := unsafeHeader(outreq.Header).Get(xForwardedProto)
if xfProto == "" {
// TODO: is this expected to set the X-Forwarded-Proto header value to
// ws(s) as the underlying request used to upgrade the connection is
// made over HTTP(S)?
if isWebsocketRequest(outreq) {
if outreq.TLS != nil {
unsafeHeader(outreq.Header).Set(xForwardedProto, "wss")
} else {
unsafeHeader(outreq.Header).Set(xForwardedProto, "ws")
}
} else {
if outreq.TLS != nil {
unsafeHeader(outreq.Header).Set(xForwardedProto, "https")
} else {
unsafeHeader(outreq.Header).Set(xForwardedProto, "http")
}
}
}
if xfPort := unsafeHeader(outreq.Header).Get(xForwardedPort); xfPort == "" {
unsafeHeader(outreq.Header).Set(xForwardedPort, forwardedPort(outreq))
}
if xfHost := unsafeHeader(outreq.Header).Get(xForwardedHost); xfHost == "" && outreq.Host != "" {
unsafeHeader(outreq.Header).Set(xForwardedHost, outreq.Host)
}
// Per https://www.rfc-editor.org/rfc/rfc2616#section-4.2, the Forwarded IPs list is in
// the same order as the values in the X-Forwarded-For header(s).
if xffs := unsafeHeader(outreq.Header).Values(xForwardedFor); len(xffs) > 0 {
unsafeHeader(outreq.Header).Set(xForwardedFor, strings.Join(xffs, ", "))
}
if x.hostname != "" {
unsafeHeader(outreq.Header).Set(xForwardedServer, x.hostname)
}
}
// ServeHTTP implements http.Handler.
func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !x.insecure && !x.isTrustedIP(r.RemoteAddr) {
for _, h := range xHeaders {
unsafeHeader(r.Header).Del(h)
}
}
x.rewrite(r)
x.next.ServeHTTP(w, r)
}
// unsafeHeader allows to manage Header values.
// Must be used only when the header name is already a canonical key.
type unsafeHeader map[string][]string
func (h unsafeHeader) Set(key, value string) {
h[key] = []string{value}
}
func (h unsafeHeader) Get(key string) string {
if len(h[key]) == 0 {
return ""
}
return h[key][0]
}
func (h unsafeHeader) Values(key string) []string {
return h[key]
}
func (h unsafeHeader) Del(key string) {
delete(h, key)
}