feat: support X-Forwarded-Host when doing gateway redirect
Michael Muré committed
Jul 1, 2020 at 12:52 UTC
87dfc46e037c06e7580dcb645f967c736a3830af
2 files changed
+46
-9
core/corehttp/hostname.go
+16
-9
@@ -81,8 +81,15 @@ func HostnameOption() ServeOption {
81
// and the paths that they serve "gateway" content on.
82
// That way, we can use DNSLink for everything else.
83
84
+ // Support X-Forwarded-Host if added by a reverse proxy
85
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host
86
+ host := r.Host
87
+ if xHost := r.Header.Get("X-Forwarded-Host"); xHost != "" {
88
+ host = xHost
89
+ }
90
+
91
// HTTP Host & Path check: is this one of our "known gateways"?
85
- if gw, ok := isKnownHostname(r.Host, knownGateways); ok {
92
+ if gw, ok := isKnownHostname(host, knownGateways); ok {
93
// This is a known gateway but request is not using
94
// the subdomain feature.
95
@@ -94,7 +101,7 @@ func HostnameOption() ServeOption {
101
if gw.UseSubdomains {
102
// Yes, redirect if applicable
103
// Example: dweb.link/ipfs/{cid} → {cid}.ipfs.dweb.link
97
- if newURL, ok := toSubdomainURL(r.Host, r.URL.Path, r); ok {
104
+ if newURL, ok := toSubdomainURL(host, r.URL.Path, r); ok {
105
// Just to be sure single Origin can't be abused in
106
// web browsers that ignored the redirect for some
107
// reason, Clear-Site-Data header clears browsing
@@ -124,9 +131,9 @@ func HostnameOption() ServeOption {
131
// Not a whitelisted path
132
133
// Try DNSLink, if it was not explicitly disabled for the hostname
127
- if !gw.NoDNSLink && isDNSLinkRequest(r.Context(), coreApi, r) {
134
+ if !gw.NoDNSLink && isDNSLinkRequest(r.Context(), coreApi, host) {
135
// rewrite path and handle as DNSLink
129
- r.URL.Path = "/ipns/" + stripPort(r.Host) + r.URL.Path
136
+ r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
137
childMux.ServeHTTP(w, r)
138
return
139
}
@@ -138,7 +145,7 @@ func HostnameOption() ServeOption {
145
146
// HTTP Host check: is this one of our subdomain-based "known gateways"?
147
// Example: {cid}.ipfs.localhost, {cid}.ipfs.dweb.link
141
- if gw, hostname, ns, rootID, ok := knownSubdomainDetails(r.Host, knownGateways); ok {
148
+ if gw, hostname, ns, rootID, ok := knownSubdomainDetails(host, knownGateways); ok {
149
// Looks like we're using known subdomain gateway.
150
151
// Assemble original path prefix.
@@ -176,9 +183,9 @@ func HostnameOption() ServeOption {
183
// 1. is wildcard DNSLink enabled (Gateway.NoDNSLink=false)?
184
// 2. does Host header include a fully qualified domain name (FQDN)?
185
// 3. does DNSLink record exist in DNS?
179
- if !cfg.Gateway.NoDNSLink && isDNSLinkRequest(r.Context(), coreApi, r) {
186
+ if !cfg.Gateway.NoDNSLink && isDNSLinkRequest(r.Context(), coreApi, host) {
187
// rewrite path and handle as DNSLink
181
- r.URL.Path = "/ipns/" + stripPort(r.Host) + r.URL.Path
188
+ r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
189
childMux.ServeHTTP(w, r)
190
return
191
}
@@ -236,8 +243,8 @@ func knownSubdomainDetails(hostname string, knownGateways map[string]config.Gate
243
244
// isDNSLinkRequest returns bool that indicates if request
245
// should return data from content path listed in DNSLink record (if exists)
239
-func isDNSLinkRequest(ctx context.Context, ipfs iface.CoreAPI, r *http.Request) bool {
240
- fqdn := stripPort(r.Host)
246
+func isDNSLinkRequest(ctx context.Context, ipfs iface.CoreAPI, host string) bool {
247
+ fqdn := stripPort(host)
248
if len(fqdn) == 0 && !isd.IsDomain(fqdn) {
249
return false
250
}
test/sharness/t0114-gateway-subdomains.sh
+30
@@ -664,6 +664,36 @@ test_hostname_gateway_response_should_contain \
664
"http://127.0.0.1:$GWAY_PORT/" \
665
"$CID_VAL"
666
667
+## ============================================================================
668
+## Test support for X-Forwarded-Host
669
+## ============================================================================
670
+
671
+# set explicit subdomain gateway config for the hostname
672
+ipfs config --json Gateway.PublicGateways '{
673
+ "example.com": {
674
+ "UseSubdomains": true,
675
+ "Paths": ["/ipfs", "/ipns", "/api"]
676
+ }
677
+}' || exit 1
678
+# restart daemon to apply config changes
679
+test_kill_ipfs_daemon
680
+test_launch_ipfs_daemon --offline
681
+
682
+test_expect_success "request for http://fake.domain.com/ipfs/{CID} doesn't match the example.com gateway" "
683
+ curl -H \"Host: fake.domain.com\" -sD - \"http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1\" > response &&
684
+ test_should_contain \"200 OK\" response
685
+"
686
+
687
+test_expect_success "request for http://fake.domain.com/ipfs/{CID} with X-Forwarded-Host: example.com match the example.com gateway" "
688
+ curl -H \"Host: fake.domain.com\" -H \"X-Forwarded-Host: example.com\" -sD - \"http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1\" > response &&
689
+ test_should_contain \"Location: http://$CIDv1.ipfs.example.com/\" response
690
+"
691
+
692
+test_expect_success "request for http://fake.domain.com/ipfs/{CID} with X-Forwarded-Host: example.com and X-Forwarded-Proto: https match the example.com gateway, redirect with https" "
693
+ curl -H \"Host: fake.domain.com\" -H \"X-Forwarded-Host: example.com\" -H \"X-Forwarded-Proto: https\" -sD - \"http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1\" > response &&
694
+ test_should_contain \"Location: https://$CIDv1.ipfs.example.com/\" response
695
+"
696
+
697
# =============================================================================
698
# ensure we end with empty Gateway.PublicGateways
699
ipfs config --json Gateway.PublicGateways '{}'