Skip to content

Commit

Permalink
feat: support X-Forwarded-Host when doing gateway redirect
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelMure authored and aschmahmann committed Jul 10, 2020
1 parent a61132e commit 87dfc46
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 9 deletions.
25 changes: 16 additions & 9 deletions core/corehttp/hostname.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,15 @@ func HostnameOption() ServeOption {
// and the paths that they serve "gateway" content on.
// That way, we can use DNSLink for everything else.

// Support X-Forwarded-Host if added by a reverse proxy
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host
host := r.Host
if xHost := r.Header.Get("X-Forwarded-Host"); xHost != "" {
host = xHost
}

// HTTP Host & Path check: is this one of our "known gateways"?
if gw, ok := isKnownHostname(r.Host, knownGateways); ok {
if gw, ok := isKnownHostname(host, knownGateways); ok {
// This is a known gateway but request is not using
// the subdomain feature.

Expand All @@ -94,7 +101,7 @@ func HostnameOption() ServeOption {
if gw.UseSubdomains {
// Yes, redirect if applicable
// Example: dweb.link/ipfs/{cid} → {cid}.ipfs.dweb.link
if newURL, ok := toSubdomainURL(r.Host, r.URL.Path, r); ok {
if newURL, ok := toSubdomainURL(host, r.URL.Path, r); ok {
// Just to be sure single Origin can't be abused in
// web browsers that ignored the redirect for some
// reason, Clear-Site-Data header clears browsing
Expand Down Expand Up @@ -124,9 +131,9 @@ func HostnameOption() ServeOption {
// Not a whitelisted path

// Try DNSLink, if it was not explicitly disabled for the hostname
if !gw.NoDNSLink && isDNSLinkRequest(r.Context(), coreApi, r) {
if !gw.NoDNSLink && isDNSLinkRequest(r.Context(), coreApi, host) {
// rewrite path and handle as DNSLink
r.URL.Path = "/ipns/" + stripPort(r.Host) + r.URL.Path
r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
childMux.ServeHTTP(w, r)
return
}
Expand All @@ -138,7 +145,7 @@ func HostnameOption() ServeOption {

// HTTP Host check: is this one of our subdomain-based "known gateways"?
// Example: {cid}.ipfs.localhost, {cid}.ipfs.dweb.link
if gw, hostname, ns, rootID, ok := knownSubdomainDetails(r.Host, knownGateways); ok {
if gw, hostname, ns, rootID, ok := knownSubdomainDetails(host, knownGateways); ok {
// Looks like we're using known subdomain gateway.

// Assemble original path prefix.
Expand Down Expand Up @@ -176,9 +183,9 @@ func HostnameOption() ServeOption {
// 1. is wildcard DNSLink enabled (Gateway.NoDNSLink=false)?
// 2. does Host header include a fully qualified domain name (FQDN)?
// 3. does DNSLink record exist in DNS?
if !cfg.Gateway.NoDNSLink && isDNSLinkRequest(r.Context(), coreApi, r) {
if !cfg.Gateway.NoDNSLink && isDNSLinkRequest(r.Context(), coreApi, host) {
// rewrite path and handle as DNSLink
r.URL.Path = "/ipns/" + stripPort(r.Host) + r.URL.Path
r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
childMux.ServeHTTP(w, r)
return
}
Expand Down Expand Up @@ -236,8 +243,8 @@ func knownSubdomainDetails(hostname string, knownGateways map[string]config.Gate

// isDNSLinkRequest returns bool that indicates if request
// should return data from content path listed in DNSLink record (if exists)
func isDNSLinkRequest(ctx context.Context, ipfs iface.CoreAPI, r *http.Request) bool {
fqdn := stripPort(r.Host)
func isDNSLinkRequest(ctx context.Context, ipfs iface.CoreAPI, host string) bool {
fqdn := stripPort(host)
if len(fqdn) == 0 && !isd.IsDomain(fqdn) {
return false
}
Expand Down
30 changes: 30 additions & 0 deletions test/sharness/t0114-gateway-subdomains.sh
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,36 @@ test_hostname_gateway_response_should_contain \
"http://127.0.0.1:$GWAY_PORT/" \
"$CID_VAL"

## ============================================================================
## Test support for X-Forwarded-Host
## ============================================================================

# set explicit subdomain gateway config for the hostname
ipfs config --json Gateway.PublicGateways '{
"example.com": {
"UseSubdomains": true,
"Paths": ["/ipfs", "/ipns", "/api"]
}
}' || exit 1
# restart daemon to apply config changes
test_kill_ipfs_daemon
test_launch_ipfs_daemon --offline

test_expect_success "request for http://fake.domain.com/ipfs/{CID} doesn't match the example.com gateway" "
curl -H \"Host: fake.domain.com\" -sD - \"http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1\" > response &&
test_should_contain \"200 OK\" response
"

test_expect_success "request for http://fake.domain.com/ipfs/{CID} with X-Forwarded-Host: example.com match the example.com gateway" "
curl -H \"Host: fake.domain.com\" -H \"X-Forwarded-Host: example.com\" -sD - \"http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1\" > response &&
test_should_contain \"Location: http://$CIDv1.ipfs.example.com/\" response
"

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" "
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 &&
test_should_contain \"Location: https://$CIDv1.ipfs.example.com/\" response
"

# =============================================================================
# ensure we end with empty Gateway.PublicGateways
ipfs config --json Gateway.PublicGateways '{}'
Expand Down

0 comments on commit 87dfc46

Please sign in to comment.