@cryptotaxi247 / kubo / commits / 09178aa71

feat(gw): support inlined DNSLink names with TLS

Problem statement and rationale for doing this can be found under "Option C" at: https://github.com/ipfs/in-web-browsers/issues/169 TLDR is: `https://dweb.link/ipns/my.v-long.example.com` can be loaded from a subdomain gateway with a wildcard TLS cert if represented as a single DNS label: `https://my-v--long-example-com.ipns.dweb.link`

Marcin Rataj committed Jan 1, 2021 at 21:39 UTC 09178aa717689a0ef9fd2042ad355320a16ffb35
4 files changed +218 -41
core/corehttp/hostname.go
+103 -22
@@ -91,7 +91,7 @@ func HostnameOption() ServeOption {
91 if gw.UseSubdomains {
92 // Yes, redirect if applicable
93 // Example: dweb.link/ipfs/{cid} → {cid}.ipfs.dweb.link
94 - newURL, err := toSubdomainURL(host, r.URL.Path, r)
94 + newURL, err := toSubdomainURL(host, r.URL.Path, r, coreAPI)
95 if err != nil {
96 http.Error(w, err.Error(), http.StatusBadRequest)
97 return
@@ -126,7 +126,7 @@ func HostnameOption() ServeOption {
126 // Not a whitelisted path
127
128 // Try DNSLink, if it was not explicitly disabled for the hostname
129 - if !gw.NoDNSLink && isDNSLinkRequest(r.Context(), coreAPI, host) {
129 + if !gw.NoDNSLink && isDNSLinkName(r.Context(), coreAPI, host) {
130 // rewrite path and handle as DNSLink
131 r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
132 childMux.ServeHTTP(w, withHostnameContext(r, host))
@@ -139,8 +139,10 @@ func HostnameOption() ServeOption {
139 }
140
141 // HTTP Host check: is this one of our subdomain-based "known gateways"?
142 - // Example: {cid}.ipfs.localhost, {cid}.ipfs.dweb.link
143 - if gw, hostname, ns, rootID, ok := knownSubdomainDetails(host, knownGateways); ok {
142 + // IPFS details extracted from the host: {rootID}.{ns}.{gwHostname}
143 + // /ipfs/ example: {cid}.ipfs.localhost:8080, {cid}.ipfs.dweb.link
144 + // /ipns/ example: {libp2p-key}.ipns.localhost:8080, {inlined-dnslink-fqdn}.ipns.dweb.link
145 + if gw, gwHostname, ns, rootID, ok := knownSubdomainDetails(host, knownGateways); ok {
146 // Looks like we're using a known gateway in subdomain mode.
147
148 // Assemble original path prefix.
@@ -156,14 +158,14 @@ func HostnameOption() ServeOption {
158 // Check if rootID is a valid CID
159 if rootCID, err := cid.Decode(rootID); err == nil {
160 // Do we need to redirect root CID to a canonical DNS representation?
159 - dnsCID, err := toDNSPrefix(rootID, rootCID)
161 + dnsCID, err := toDNSLabel(rootID, rootCID)
162 if err != nil {
163 http.Error(w, err.Error(), http.StatusBadRequest)
164 return
165 }
166 if !strings.HasPrefix(r.Host, dnsCID) {
167 dnsPrefix := "/" + ns + "/" + dnsCID
166 - newURL, err := toSubdomainURL(hostname, dnsPrefix+r.URL.Path, r)
168 + newURL, err := toSubdomainURL(gwHostname, dnsPrefix+r.URL.Path, r, coreAPI)
169 if err != nil {
170 http.Error(w, err.Error(), http.StatusBadRequest)
171 return
@@ -179,7 +181,7 @@ func HostnameOption() ServeOption {
181 // Do we need to fix multicodec in PeerID represented as CIDv1?
182 if isPeerIDNamespace(ns) {
183 if rootCID.Type() != cid.Libp2pKey {
182 - newURL, err := toSubdomainURL(hostname, pathPrefix+r.URL.Path, r)
184 + newURL, err := toSubdomainURL(gwHostname, pathPrefix+r.URL.Path, r, coreAPI)
185 if err != nil {
186 http.Error(w, err.Error(), http.StatusBadRequest)
187 return
@@ -191,13 +193,36 @@ func HostnameOption() ServeOption {
193 }
194 }
195 }
196 + } else { // rootID is not a CID..
197 +
198 + // Check if rootID is a single DNS label with an inlined
199 + // DNSLink FQDN a single DNS label. We support this so
200 + // loading DNSLink names over TLS "just works" on public
201 + // HTTP gateways.
202 + //
203 + // Rationale for doing this can be found under "Option C"
204 + // at: https://github.com/ipfs/in-web-browsers/issues/169
205 + //
206 + // TLDR is:
207 + // https://dweb.link/ipns/my.v-long.example.com
208 + // can be loaded from a subdomain gateway with a wildcard
209 + // TLS cert if represented as a single DNS label:
210 + // https://my-v--long-example-com.ipns.dweb.link
211 + if ns == "ipns" && !strings.Contains(rootID, ".") {
212 + // my-v--long-example-com → my.v-long.example.com
213 + dnslinkFQDN := toDNSLinkFQDN(rootID)
214 + if isDNSLinkName(r.Context(), coreAPI, dnslinkFQDN) {
215 + // update path prefix to use real FQDN with DNSLink
216 + pathPrefix = "/ipns/" + dnslinkFQDN
217 + }
218 + }
219 }
220
221 // Rewrite the path to not use subdomains
222 r.URL.Path = pathPrefix + r.URL.Path
223
224 // Serve path request
200 - childMux.ServeHTTP(w, withHostnameContext(r, hostname))
225 + childMux.ServeHTTP(w, withHostnameContext(r, gwHostname))
226 return
227 }
228 // We don't have a known gateway. Fallback on DNSLink lookup
@@ -206,7 +231,7 @@ func HostnameOption() ServeOption {
231 // 1. is wildcard DNSLink enabled (Gateway.NoDNSLink=false)?
232 // 2. does Host header include a fully qualified domain name (FQDN)?
233 // 3. does DNSLink record exist in DNS?
209 - if !cfg.Gateway.NoDNSLink && isDNSLinkRequest(r.Context(), coreAPI, host) {
234 + if !cfg.Gateway.NoDNSLink && isDNSLinkName(r.Context(), coreAPI, host) {
235 // rewrite path and handle as DNSLink
236 r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
237 childMux.ServeHTTP(w, withHostnameContext(r, host))
@@ -305,9 +330,10 @@ func isKnownHostname(hostname string, knownGateways gatewayHosts) (gw *config.Ga
330 }
331
332 // Parses Host header and looks for a known gateway matching subdomain host.
308 -// If found, returns GatewaySpec and subdomain components.
333 +// If found, returns GatewaySpec and subdomain components extracted from Host
334 +// header: {rootID}.{ns}.{gwHostname}
335 // Note: hostname is host + optional port
310 -func knownSubdomainDetails(hostname string, knownGateways gatewayHosts) (gw *config.GatewaySpec, knownHostname, ns, rootID string, ok bool) {
336 +func knownSubdomainDetails(hostname string, knownGateways gatewayHosts) (gw *config.GatewaySpec, gwHostname, ns, rootID string, ok bool) {
337 labels := strings.Split(hostname, ".")
338 // Look for FQDN of a known gateway hostname.
339 // Example: given "dist.ipfs.io.ipns.dweb.link":
@@ -336,9 +362,8 @@ func knownSubdomainDetails(hostname string, knownGateways gatewayHosts) (gw *con
362 return nil, "", "", "", false
363 }
364
339 -// isDNSLinkRequest returns bool that indicates if request
340 -// should return data from content path listed in DNSLink record (if exists)
341 -func isDNSLinkRequest(ctx context.Context, ipfs iface.CoreAPI, host string) bool {
365 +// isDNSLinkName returns bool if a valid DNS TXT record exist for provided host
366 +func isDNSLinkName(ctx context.Context, ipfs iface.CoreAPI, host string) bool {
367 fqdn := stripPort(host)
368 if len(fqdn) == 0 && !isd.IsDomain(fqdn) {
369 return false
@@ -368,8 +393,8 @@ func isPeerIDNamespace(ns string) bool {
393 }
394 }
395
371 -// Converts an identifier to DNS-safe representation that fits in 63 characters
372 -func toDNSPrefix(rootID string, rootCID cid.Cid) (prefix string, err error) {
396 +// Converts a CID to DNS-safe representation that fits in 63 characters
397 +func toDNSLabel(rootID string, rootCID cid.Cid) (dnsCID string, err error) {
398 // Return as-is if things fit
399 if len(rootID) <= dnsLabelMaxLength {
400 return rootID, nil
@@ -388,12 +413,45 @@ func toDNSPrefix(rootID string, rootCID cid.Cid) (prefix string, err error) {
413 return "", fmt.Errorf("CID incompatible with DNS label length limit of 63: %s", rootID)
414 }
415
416 +// Returns true if HTTP request involves TLS certificate.
417 +// See https://github.com/ipfs/in-web-browsers/issues/169 to uderstand how it
418 +// impacts DNSLink websites on public gateways.
419 +func isHTTPSRequest(r *http.Request) bool {
420 + // X-Forwarded-Proto if added by a reverse proxy
421 + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
422 + xproto := r.Header.Get("X-Forwarded-Proto")
423 + // Is request a native TLS (not used atm, but future-proofing)
424 + // or a proxied HTTPS (eg. go-ipfs behind nginx at a public gw)?
425 + return r.URL.Scheme == "https" || xproto == "https"
426 +}
427 +
428 +// Converts a FQDN to DNS-safe representation that fits in 63 characters:
429 +// my.v-long.example.com → my-v--long-example-com
430 +func toDNSLinkDNSLabel(fqdn string) (dnsLabel string, err error) {
431 + dnsLabel = strings.ReplaceAll(fqdn, "-", "--")
432 + dnsLabel = strings.ReplaceAll(dnsLabel, ".", "-")
433 + if len(dnsLabel) > dnsLabelMaxLength {
434 + return "", fmt.Errorf("DNSLink representation incompatible with DNS label length limit of 63: %s", dnsLabel)
435 + }
436 + return dnsLabel, nil
437 +}
438 +
439 +// Converts a DNS-safe representation of DNSLink FQDN to real FQDN:
440 +// my-v--long-example-com → my.v-long.example.com
441 +func toDNSLinkFQDN(dnsLabel string) (fqdn string) {
442 + fqdn = strings.ReplaceAll(dnsLabel, "--", "@") // @ placeholder is unused in DNS labels
443 + fqdn = strings.ReplaceAll(fqdn, "-", ".")
444 + fqdn = strings.ReplaceAll(fqdn, "@", "-")
445 + return fqdn
446 +}
447 +
448 // Converts a hostname/path to a subdomain-based URL, if applicable.
392 -func toSubdomainURL(hostname, path string, r *http.Request) (redirURL string, err error) {
449 +func toSubdomainURL(hostname, path string, r *http.Request, ipfs iface.CoreAPI) (redirURL string, err error) {
450 var scheme, ns, rootID, rest string
451
452 query := r.URL.RawQuery
453 parts := strings.SplitN(path, "/", 4)
454 + isHTTPS := isHTTPSRequest(r)
455 safeRedirectURL := func(in string) (out string, err error) {
456 safeURI, err := url.ParseRequestURI(in)
457 if err != nil {
@@ -402,10 +460,7 @@ func toSubdomainURL(hostname, path string, r *http.Request) (redirURL string, er
460 return safeURI.String(), nil
461 }
462
405 - // Support X-Forwarded-Proto if added by a reverse proxy
406 - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
407 - xproto := r.Header.Get("X-Forwarded-Proto")
408 - if xproto == "https" {
463 + if isHTTPS {
464 scheme = "https:"
465 } else {
466 scheme = "http:"
@@ -475,10 +530,36 @@ func toSubdomainURL(hostname, path string, r *http.Request) (redirURL string, er
530 }
531 // 2. Make sure CID fits in a DNS label, adjust encoding if needed
532 // (https://github.com/ipfs/go-ipfs/issues/7318)
478 - rootID, err = toDNSPrefix(rootID, rootCID)
533 + rootID, err = toDNSLabel(rootID, rootCID)
534 if err != nil {
535 return "", err
536 }
537 + } else { // rootID is not a CID
538 +
539 + // Check if rootID is a FQDN with DNSLink and convert it to TLS-safe
540 + // representation that fits in a single DNS label. We support this so
541 + // loading DNSLink names over TLS "just works" on public HTTP gateways
542 + // that pass 'https' in X-Forwarded-Proto to go-ipfs.
543 + //
544 + // Rationale can be found under "Option C"
545 + // at: https://github.com/ipfs/in-web-browsers/issues/169
546 + //
547 + // TLDR is:
548 + // /ipns/my.v-long.example.com
549 + // can be loaded from a subdomain gateway with a wildcard TLS cert if
550 + // represented as a single DNS label:
551 + // https://my-v--long-example-com.ipns.dweb.link
552 + if isHTTPS && ns == "ipns" && strings.Contains(rootID, ".") {
553 + if isDNSLinkName(r.Context(), ipfs, rootID) {
554 + // my.v-long.example.com → my-v--long-example-com
555 + dnsLabel, err := toDNSLinkDNSLabel(rootID)
556 + if err != nil {
557 + return "", err
558 + }
559 + // update path prefix to use real FQDN with DNSLink
560 + rootID = dnsLabel
561 + }
562 + }
563 }
564
565 return safeRedirectURL(fmt.Sprintf(
core/corehttp/hostname_test.go
+95 -16
@@ -2,41 +2,121 @@ package corehttp
2
3 import (
4 "errors"
5 + "net/http"
6 "net/http/httptest"
7 "testing"
8
9 cid "github.com/ipfs/go-cid"
10 config "github.com/ipfs/go-ipfs-config"
11 + files "github.com/ipfs/go-ipfs-files"
12 + coreapi "github.com/ipfs/go-ipfs/core/coreapi"
13 + path "github.com/ipfs/go-path"
14 )
15
16 func TestToSubdomainURL(t *testing.T) {
13 - r := httptest.NewRequest("GET", "http://request-stub.example.com", nil)
17 + ns := mockNamesys{}
18 + n, err := newNodeWithMockNamesys(ns)
19 + if err != nil {
20 + t.Fatal(err)
21 + }
22 + coreAPI, err := coreapi.NewCoreAPI(n)
23 + if err != nil {
24 + t.Fatal(err)
25 + }
26 + testCID, err := coreAPI.Unixfs().Add(n.Context(), files.NewBytesFile([]byte("fnord")))
27 + if err != nil {
28 + t.Fatal(err)
29 + }
30 + ns["/ipns/dnslink.long-name.example.com"] = path.FromString(testCID.String())
31 + ns["/ipns/dnslink.too-long.f1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5o.example.com"] = path.FromString(testCID.String())
32 + httpRequest := httptest.NewRequest("GET", "http://127.0.0.1:8080", nil)
33 + httpsRequest := httptest.NewRequest("GET", "https://https-request-stub.example.com", nil)
34 + httpsProxiedRequest := httptest.NewRequest("GET", "http://proxied-https-request-stub.example.com", nil)
35 + httpsProxiedRequest.Header.Set("X-Forwarded-Proto", "https")
36 +
37 for _, test := range []struct {
38 // in:
16 - hostname string
17 - path string
39 + request *http.Request
40 + gwHostname string
41 + path string
42 // out:
43 url string
44 err error
45 }{
46 // DNSLink
23 - {"localhost", "/ipns/dnslink.io", "http://dnslink.io.ipns.localhost/", nil},
47 + {httpRequest, "localhost", "/ipns/dnslink.io", "http://dnslink.io.ipns.localhost/", nil},
48 // Hostname with port
25 - {"localhost:8080", "/ipns/dnslink.io", "http://dnslink.io.ipns.localhost:8080/", nil},
49 + {httpRequest, "localhost:8080", "/ipns/dnslink.io", "http://dnslink.io.ipns.localhost:8080/", nil},
50 // CIDv0 → CIDv1base32
27 - {"localhost", "/ipfs/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "http://bafybeif7a7gdklt6hodwdrmwmxnhksctcuav6lfxlcyfz4khzl3qfmvcgu.ipfs.localhost/", nil},
51 + {httpRequest, "localhost", "/ipfs/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "http://bafybeif7a7gdklt6hodwdrmwmxnhksctcuav6lfxlcyfz4khzl3qfmvcgu.ipfs.localhost/", nil},
52 // CIDv1 with long sha512
29 - {"localhost", "/ipfs/bafkrgqe3ohjcjplc6n4f3fwunlj6upltggn7xqujbsvnvyw764srszz4u4rshq6ztos4chl4plgg4ffyyxnayrtdi5oc4xb2332g645433aeg", "", errors.New("CID incompatible with DNS label length limit of 63: kf1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5oj")},
53 + {httpRequest, "localhost", "/ipfs/bafkrgqe3ohjcjplc6n4f3fwunlj6upltggn7xqujbsvnvyw764srszz4u4rshq6ztos4chl4plgg4ffyyxnayrtdi5oc4xb2332g645433aeg", "", errors.New("CID incompatible with DNS label length limit of 63: kf1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5oj")},
54 // PeerID as CIDv1 needs to have libp2p-key multicodec
31 - {"localhost", "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", "http://k2k4r8n0flx3ra0y5dr8fmyvwbzy3eiztmtq6th694k5a3rznayp3e4o.ipns.localhost/", nil},
32 - {"localhost", "/ipns/bafybeickencdqw37dpz3ha36ewrh4undfjt2do52chtcky4rxkj447qhdm", "http://k2k4r8l9ja7hkzynavdqup76ou46tnvuaqegbd04a4o1mpbsey0meucb.ipns.localhost/", nil},
55 + {httpRequest, "localhost", "/ipns/QmY3hE8xgFCjGcz6PHgnvJz5HZi1BaKRfPkn1ghZUcYMjD", "http://k2k4r8n0flx3ra0y5dr8fmyvwbzy3eiztmtq6th694k5a3rznayp3e4o.ipns.localhost/", nil},
56 + {httpRequest, "localhost", "/ipns/bafybeickencdqw37dpz3ha36ewrh4undfjt2do52chtcky4rxkj447qhdm", "http://k2k4r8l9ja7hkzynavdqup76ou46tnvuaqegbd04a4o1mpbsey0meucb.ipns.localhost/", nil},
57 // PeerID: ed25519+identity multihash → CIDv1Base36
34 - {"localhost", "/ipns/12D3KooWFB51PRY9BxcXSH6khFXw1BZeszeLDy7C8GciskqCTZn5", "http://k51qzi5uqu5di608geewp3nqkg0bpujoasmka7ftkyxgcm3fh1aroup0gsdrna.ipns.localhost/", nil},
35 - {"sub.localhost", "/ipfs/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "http://bafybeif7a7gdklt6hodwdrmwmxnhksctcuav6lfxlcyfz4khzl3qfmvcgu.ipfs.sub.localhost/", nil},
58 + {httpRequest, "localhost", "/ipns/12D3KooWFB51PRY9BxcXSH6khFXw1BZeszeLDy7C8GciskqCTZn5", "http://k51qzi5uqu5di608geewp3nqkg0bpujoasmka7ftkyxgcm3fh1aroup0gsdrna.ipns.localhost/", nil},
59 + {httpRequest, "sub.localhost", "/ipfs/QmbCMUZw6JFeZ7Wp9jkzbye3Fzp2GGcPgC3nmeUjfVF87n", "http://bafybeif7a7gdklt6hodwdrmwmxnhksctcuav6lfxlcyfz4khzl3qfmvcgu.ipfs.sub.localhost/", nil},
60 + // HTTPS requires DNSLink name to fit in a single DNS label – see "Option C" from https://github.com/ipfs/in-web-browsers/issues/169
61 + {httpRequest, "dweb.link", "/ipns/dnslink.long-name.example.com", "http://dnslink.long-name.example.com.ipns.dweb.link/", nil},
62 + {httpsRequest, "dweb.link", "/ipns/dnslink.long-name.example.com", "https://dnslink-long--name-example-com.ipns.dweb.link/", nil},
63 + {httpsProxiedRequest, "dweb.link", "/ipns/dnslink.long-name.example.com", "https://dnslink-long--name-example-com.ipns.dweb.link/", nil},
64 } {
37 - url, err := toSubdomainURL(test.hostname, test.path, r)
65 + url, err := toSubdomainURL(test.gwHostname, test.path, test.request, coreAPI)
66 if url != test.url || !equalError(err, test.err) {
39 - t.Errorf("(%s, %s) returned (%s, %v), expected (%s, %v)", test.hostname, test.path, url, err, test.url, test.err)
67 + t.Errorf("(%s, %s) returned (%s, %v), expected (%s, %v)", test.gwHostname, test.path, url, err, test.url, test.err)
68 + }
69 + }
70 +}
71 +
72 +func TestToDNSLinkDNSLabel(t *testing.T) {
73 + for _, test := range []struct {
74 + in string
75 + out string
76 + err error
77 + }{
78 + {"dnslink.long-name.example.com", "dnslink-long--name-example-com", nil},
79 + {"dnslink.too-long.f1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5o.example.com", "", errors.New("DNSLink representation incompatible with DNS label length limit of 63: dnslink-too--long-f1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5o-example-com")},
80 + } {
81 + out, err := toDNSLinkDNSLabel(test.in)
82 + if out != test.out || !equalError(err, test.err) {
83 + t.Errorf("(%s) returned (%s, %v), expected (%s, %v)", test.in, out, err, test.out, test.err)
84 + }
85 + }
86 +}
87 +
88 +func TestToDNSLinkFQDN(t *testing.T) {
89 + for _, test := range []struct {
90 + in string
91 + out string
92 + }{
93 + {"singlelabel", "singlelabel"},
94 + {"docs-ipfs-io", "docs.ipfs.io"},
95 + {"dnslink-long--name-example-com", "dnslink.long-name.example.com"},
96 + } {
97 + out := toDNSLinkFQDN(test.in)
98 + if out != test.out {
99 + t.Errorf("(%s) returned (%s), expected (%s)", test.in, out, test.out)
100 + }
101 + }
102 +}
103 +
104 +func TestIsHTTPSRequest(t *testing.T) {
105 + httpRequest := httptest.NewRequest("GET", "http://127.0.0.1:8080", nil)
106 + httpsRequest := httptest.NewRequest("GET", "https://https-request-stub.example.com", nil)
107 + httpsProxiedRequest := httptest.NewRequest("GET", "http://proxied-https-request-stub.example.com", nil)
108 + httpsProxiedRequest.Header.Set("X-Forwarded-Proto", "https")
109 + for _, test := range []struct {
110 + in *http.Request
111 + out bool
112 + }{
113 + {httpRequest, false},
114 + {httpsRequest, true},
115 + {httpsProxiedRequest, true},
116 + } {
117 + out := isHTTPSRequest(test.in)
118 + if out != test.out {
119 + t.Errorf("(%+v): returned %t, expected %t", test.in, out, test.out)
120 }
121 }
122 }
@@ -77,10 +157,9 @@ func TestPortStripping(t *testing.T) {
157 t.Errorf("(%s): returned '%s', expected '%s'", test.in, out, test.out)
158 }
159 }
80 -
160 }
161
83 -func TestDNSPrefix(t *testing.T) {
162 +func TestToDNSLabel(t *testing.T) {
163 for _, test := range []struct {
164 in string
165 out string
@@ -96,7 +175,7 @@ func TestDNSPrefix(t *testing.T) {
175 {"bafkrgqe3ohjcjplc6n4f3fwunlj6upltggn7xqujbsvnvyw764srszz4u4rshq6ztos4chl4plgg4ffyyxnayrtdi5oc4xb2332g645433aeg", "", errors.New("CID incompatible with DNS label length limit of 63: kf1siqrebi3vir8sab33hu5vcy008djegvay6atmz91ojesyjs8lx350b7y7i1nvyw2haytfukfyu2f2x4tocdrfa0zgij6p4zpl4u5oj")},
176 } {
177 inCID, _ := cid.Decode(test.in)
99 - out, err := toDNSPrefix(test.in, inCID)
178 + out, err := toDNSLabel(test.in, inCID)
179 if out != test.out || !equalError(err, test.err) {
180 t.Errorf("(%s): returned (%s, %v) expected (%s, %v)", test.in, out, err, test.out, test.err)
181 }
docs/config.md
+2 -1
@@ -709,8 +709,9 @@ Below is a list of the most common public gateway setups.
709 ```
710 **Backward-compatible:** this feature enables automatic redirects from content paths to subdomains:
711 `http://dweb.link/ipfs/{cid}` → `http://{cid}.ipfs.dweb.link`
712 - **X-Forwarded-Proto:** if you run go-ipfs behind a reverse proxy that provides TLS, make it add a `X-Forwarded-Proto: https` HTTP header to ensure users are redirected to `https://`, not `http://`. The NGINX directive is `proxy_set_header X-Forwarded-Proto "https";`.:
712 + **X-Forwarded-Proto:** if you run go-ipfs behind a reverse proxy that provides TLS, make it add a `X-Forwarded-Proto: https` HTTP header to ensure users are redirected to `https://`, not `http://`. It will also ensure DNSLink names are inlined to fit in a single DNS label, so they work fine with a wildcart TLS cert ([details](https://github.com/ipfs/in-web-browsers/issues/169)). The NGINX directive is `proxy_set_header X-Forwarded-Proto "https";`.:
713 `http://dweb.link/ipfs/{cid}` → `https://{cid}.ipfs.dweb.link`
714 + `http://dweb.link/ipns/your-dnslink.site.example.com` → `https://your--dnslink-site-example-com.ipfs.dweb.link`
715 **X-Forwarded-Host:** we also support `X-Forwarded-Host: example.com` if you want to override subdomain gateway host from the original request:
716 `http://dweb.link/ipfs/{cid}` → `http://{cid}.ipfs.example.com`
717
test/sharness/t0114-gateway-subdomains.sh
+18 -2
@@ -403,6 +403,14 @@ test_hostname_gateway_response_should_contain \
403 "http://127.0.0.1:$GWAY_PORT/ipns/en.wikipedia-on-ipfs.org/wiki" \
404 "Location: http://en.wikipedia-on-ipfs.org.ipns.example.com/wiki"
405
406 +# DNSLink on Public gateway with a single-level wildcard TLS cert
407 +# "Option C" from https://github.com/ipfs/in-web-browsers/issues/169
408 +test_expect_success \
409 + "request for example.com/ipns/{fqdn} with X-Forwarded-Proto redirects to TLS-safe label in subdomain" "
410 + curl -H \"Host: example.com\" -H \"X-Forwarded-Proto: https\" -sD - \"http://127.0.0.1:$GWAY_PORT/ipns/en.wikipedia-on-ipfs.org/wiki\" > response &&
411 + test_should_contain \"Location: https://en-wikipedia--on--ipfs-org.ipns.example.com/wiki\" response
412 + "
413 +
414 # *.ipfs.example.com: subdomain requests made with custom FQDN in Host header
415
416 test_hostname_gateway_response_should_contain \
@@ -539,14 +547,22 @@ test_hostname_gateway_response_should_contain \
547 "http://127.0.0.1:$GWAY_PORT" \
548 "$CID_VAL"
549
550 +# DNSLink on Public gateway with a single-level wildcard TLS cert
551 +# "Option C" from https://github.com/ipfs/in-web-browsers/issues/169
552 +test_expect_success \
553 + "request for {single-label-dnslink}.ipns.example.com with X-Forwarded-Proto returns expected payload" "
554 + curl -H \"Host: dnslink--subdomain--gw--test-example-org.ipns.example.com\" -H \"X-Forwarded-Proto: https\" -sD - \"http://127.0.0.1:$GWAY_PORT\" > response &&
555 + test_should_contain \"$CID_VAL\" response
556 + "
557 +
558 ## Test subdomain handling of CIDs that do not fit in a single DNS Label (>63chars)
559 ## https://github.com/ipfs/go-ipfs/issues/7318
560 ## ============================================================================
561
562 # ed25519 fits under 63 char limit when represented in base36
563 IPNS_KEY="test_key_ed25519"
548 -IPNS_ED25519_B58MH=$(ipfs key list -l -f b58mh | grep $IPNS_KEY | cut -d " " -f1 | tr -d "\n")
549 -IPNS_ED25519_B36CID=$(ipfs key list -l -f b36cid | grep $IPNS_KEY | cut -d " " -f1 | tr -d "\n")
564 +IPNS_ED25519_B58MH=$(ipfs key list -l --ipns-base b58mh | grep $IPNS_KEY | cut -d" " -f1 | tr -d "\n")
565 +IPNS_ED25519_B36CID=$(ipfs key list -l --ipns-base base36 | grep $IPNS_KEY | cut -d" " -f1 | tr -d "\n")
566 # sha512 will be over 63char limit, even when represented in Base36
567 CIDv1_TOO_LONG=$(echo $CID_VAL | ipfs add --cid-version 1 --hash sha2-512 -Q)
568