master
sh 919 lines 36.7 KB
Raw
1 #!/usr/bin/env bash
2 #
3 # Copyright (c) Protocol Labs
4
5 test_description="Test subdomain support on the HTTP gateway"
6
7
8 . lib/test-lib.sh
9
10 ## ============================================================================
11 ## Helpers specific to subdomain tests
12 ## ============================================================================
13
14 # Helper that tests gateway response over direct HTTP
15 # and in all supported HTTP proxy modes
16 test_localhost_gateway_response_should_contain() {
17 local label="$1"
18 local expected="$3"
19
20 # explicit "Host: $hostname" header to match browser behavior
21 # and also make tests independent from DNS
22 local host=$(echo $2 | cut -d'/' -f3 | cut -d':' -f1)
23 local hostname=$(echo $2 | cut -d'/' -f3 | cut -d':' -f1,2)
24
25 # Proxy is the same as HTTP Gateway, we use raw IP and port to be sure
26 local proxy="http://127.0.0.1:$GWAY_PORT"
27
28 # Create a raw URL version with IP to ensure hostname from Host header is used
29 # (removes false-positives, Host header is used for passing hostname already)
30 local url="$2"
31 local rawurl=$(echo "$url" | sed "s/$hostname/127.0.0.1:$GWAY_PORT/")
32
33 #echo "hostname: $hostname"
34 #echo "url before: $url"
35 #echo "url after: $rawurl"
36
37 # regular HTTP request
38 # (hostname in Host header, raw IP in URL)
39 test_expect_success "$label (direct HTTP)" "
40 curl -H \"Host: $hostname\" -sD - \"$rawurl\" > response &&
41 test_should_contain \"$expected\" response
42 "
43
44 # HTTP proxy
45 # (hostname is passed via URL)
46 # Note: proxy client should not care, but curl does DNS lookup
47 # for some reason anyway, so we pass static DNS mapping
48 test_expect_success "$label (HTTP proxy)" "
49 curl -x $proxy --resolve $hostname:127.0.0.1 -sD - \"$url\" > response &&
50 test_should_contain \"$expected\" response
51 "
52
53 # HTTP proxy 1.0
54 # (repeating proxy test with older spec, just to be sure)
55 test_expect_success "$label (HTTP proxy 1.0)" "
56 curl --proxy1.0 $proxy --resolve $hostname:127.0.0.1 -sD - \"$url\" > response &&
57 test_should_contain \"$expected\" response
58 "
59
60 # HTTP proxy tunneling (CONNECT)
61 # https://tools.ietf.org/html/rfc7231#section-4.3.6
62 # In HTTP/1.x, the pseudo-method CONNECT
63 # can be used to convert an HTTP connection into a tunnel to a remote host
64 test_expect_success "$label (HTTP proxy tunneling)" "
65 curl --proxytunnel -x $proxy -H \"Host: $hostname\" -sD - \"$rawurl\" > response &&
66 test_should_contain \"$expected\" response
67 "
68 }
69
70 # Helper that checks gateway response for specific hostname in Host header
71 test_hostname_gateway_response_should_contain() {
72 local label="$1"
73 local hostname="$2"
74 local url="$3"
75 local rawurl=$(echo "$url" | sed "s/$hostname/127.0.0.1:$GWAY_PORT/")
76 local expected="$4"
77 test_expect_success "$label" "
78 curl -H \"Host: $hostname\" -sD - \"$rawurl\" > response &&
79 test_should_contain \"$expected\" response
80 "
81 }
82
83 ## ============================================================================
84 ## Start IPFS Node and prepare test CIDs
85 ## ============================================================================
86
87 test_expect_success "ipfs init" '
88 export IPFS_PATH="$(pwd)/.ipfs" &&
89 ipfs init --profile=test > /dev/null
90 '
91
92 test_launch_ipfs_daemon_without_network
93
94 # Import test case
95 # See the static fixtures in ./t0114-gateway-subdomains/
96 CID_VAL=hello
97 CIDv1=bafkreicysg23kiwv34eg2d7qweipxwosdo2py4ldv42nbauguluen5v6am
98 CIDv0=QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN
99 CIDv0to1=bafybeiffndsajwhk3lwjewwdxqntmjm4b5wxaaanokonsggenkbw6slwk4
100 CIDv1_TOO_LONG=bafkrgqhhyivzstcz3hhswshfjgy6ertgmnqeleynhwt4dlfsthi4hn7zgh4uvlsb5xncykzapi3ocd4lzogukir6ksdy6wzrnz6ohnv4aglcs
101 DIR_CID=bafybeiht6dtwk3les7vqm6ibpvz6qpohidvlshsfyr7l5mpysdw2vmbbhe
102
103 RSA_KEY=QmVujd5Vb7moysJj8itnGufN7MEtPRCNHkKpNuA4onsRa3
104 RSA_IPNS_IDv0=QmVujd5Vb7moysJj8itnGufN7MEtPRCNHkKpNuA4onsRa3
105 RSA_IPNS_IDv1=k2k4r8m7xvggw5pxxk3abrkwyer625hg01hfyggrai7lk1m63fuihi7w
106 RSA_IPNS_IDv1_DAGPB=k2jmtxu61bnhrtj301lw7zizknztocdbeqhxgv76l2q9t36fn9jbzipo
107
108 ED25519_KEY=12D3KooWLQzUv2FHWGVPXTXSZpdHs7oHbXub2G5WC8Tx4NQhyd2d
109 ED25519_IPNS_IDv0=12D3KooWLQzUv2FHWGVPXTXSZpdHs7oHbXub2G5WC8Tx4NQhyd2d
110 ED25519_IPNS_IDv1=k51qzi5uqu5dk3v4rmjber23h16xnr23bsggmqqil9z2gduiis5se8dht36dam
111 ED25519_IPNS_IDv1_DAGPB=k50rm9yjlt0jey4fqg6wafvqprktgbkpgkqdg27tpqje6iimzxewnhvtin9hhq
112 IPNS_ED25519_B58MH=12D3KooWLQzUv2FHWGVPXTXSZpdHs7oHbXub2G5WC8Tx4NQhyd2d
113 IPNS_ED25519_B36CID=k51qzi5uqu5dk3v4rmjber23h16xnr23bsggmqqil9z2gduiis5se8dht36dam
114
115 test_expect_success "Add the test fixtures" '
116 ipfs dag import --pin-roots ../t0114-gateway-subdomains/fixtures.car &&
117 ipfs routing put --allow-offline /ipns/${RSA_KEY} ../t0114-gateway-subdomains/${RSA_KEY}.ipns-record &&
118 ipfs routing put --allow-offline /ipns/${ED25519_KEY} ../t0114-gateway-subdomains/${ED25519_KEY}.ipns-record
119 '
120
121 # ensure we start with empty Gateway.PublicGateways
122 test_expect_success 'start daemon with empty config for Gateway.PublicGateways' '
123 test_kill_ipfs_daemon &&
124 ipfs config --json Gateway.PublicGateways "{}" &&
125 test_launch_ipfs_daemon_without_network
126 '
127
128 ## ============================================================================
129 ## Test path-based requests to a local gateway with default config
130 ## (forced redirects to http://*.localhost)
131 ## ============================================================================
132
133 # /ipfs/<cid>
134
135 # IP remains old school path-based gateway
136
137 test_localhost_gateway_response_should_contain \
138 "request for 127.0.0.1/ipfs/{CID} stays on path" \
139 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1" \
140 "$CID_VAL"
141
142 # 'localhost' hostname is used for subdomains, and should not return
143 # payload directly, but redirect to URL with proper origin isolation
144
145 test_localhost_gateway_response_should_contain \
146 "request for localhost/ipfs/{CIDv1} returns HTTP 301 Moved Permanently" \
147 "http://localhost:$GWAY_PORT/ipfs/$CIDv1" \
148 "301 Moved Permanently"
149
150 test_localhost_gateway_response_should_contain \
151 "request for localhost/ipfs/{CIDv1} returns Location HTTP header for subdomain redirect in browsers" \
152 "http://localhost:$GWAY_PORT/ipfs/$CIDv1" \
153 "Location: http://$CIDv1.ipfs.localhost:$GWAY_PORT/"
154
155 test_localhost_gateway_response_should_contain \
156 "request for localhost/ipfs/{DIR_CID} returns HTTP 301 Moved Permanently" \
157 "http://localhost:$GWAY_PORT/ipfs/$DIR_CID" \
158 "301 Moved Permanently"
159
160 test_localhost_gateway_response_should_contain \
161 "request for localhost/ipfs/{DIR_CID} returns Location HTTP header for subdomain redirect in browsers" \
162 "http://localhost:$GWAY_PORT/ipfs/$DIR_CID/" \
163 "Location: http://$DIR_CID.ipfs.localhost:$GWAY_PORT/"
164
165 # Kubo specific end-to-end test
166 # (independent of gateway-conformance)
167
168 # We return human-readable body with HTTP 301 so existing cli scripts that use path-based
169 # gateway are informed to enable following HTTP redirects
170 test_localhost_gateway_response_should_contain \
171 "request for localhost/ipfs/{CIDv1} includes human-readable link and redirect info in HTTP 301 body" \
172 "http://localhost:$GWAY_PORT/ipfs/$CIDv1" \
173 ">Moved Permanently</a>"
174
175 # end Kubo specific end-to-end test
176
177 test_localhost_gateway_response_should_contain \
178 "request for localhost/ipfs/{CIDv0} redirects to CIDv1 representation in subdomain" \
179 "http://localhost:$GWAY_PORT/ipfs/$CIDv0" \
180 "Location: http://${CIDv0to1}.ipfs.localhost:$GWAY_PORT/"
181
182 # /ipns/<libp2p-key>
183
184 test_localhost_gateway_response_should_contain \
185 "request for localhost/ipns/{CIDv0} redirects to CIDv1 with libp2p-key multicodec in subdomain" \
186 "http://localhost:$GWAY_PORT/ipns/$RSA_IPNS_IDv0" \
187 "Location: http://${RSA_IPNS_IDv1}.ipns.localhost:$GWAY_PORT/"
188
189 test_localhost_gateway_response_should_contain \
190 "request for localhost/ipns/{CIDv0} redirects to CIDv1 with libp2p-key multicodec in subdomain" \
191 "http://localhost:$GWAY_PORT/ipns/$ED25519_IPNS_IDv0" \
192 "Location: http://${ED25519_IPNS_IDv1}.ipns.localhost:$GWAY_PORT/"
193
194 # /ipns/<dnslink-fqdn>
195
196 # Kubo specific end-to-end test
197 # (independent of gateway-conformance)
198
199 test_localhost_gateway_response_should_contain \
200 "request for localhost/ipns/{fqdn} redirects to DNSLink in subdomain" \
201 "http://localhost:$GWAY_PORT/ipns/en.wikipedia-on-ipfs.org/wiki" \
202 "Location: http://en.wikipedia-on-ipfs.org.ipns.localhost:$GWAY_PORT/wiki"
203
204 # end Kubo specific end-to-end test
205
206 ## ============================================================================
207 ## Test subdomain-based requests to a local gateway with default config
208 ## (origin per content root at http://*.localhost)
209 ## ============================================================================
210
211 # {CID}.ipfs.localhost
212
213 test_localhost_gateway_response_should_contain \
214 "request for {CID}.ipfs.localhost should return expected payload" \
215 "http://${CIDv1}.ipfs.localhost:$GWAY_PORT" \
216 "$CID_VAL"
217
218 # ensure /ipfs/ namespace is not mounted on subdomain
219 test_localhost_gateway_response_should_contain \
220 "request for {CID}.ipfs.localhost/ipfs/{CID} should return HTTP 404" \
221 "http://${CIDv1}.ipfs.localhost:$GWAY_PORT/ipfs/$CIDv1" \
222 "404 Not Found"
223
224 # ensure requests to /ipfs/* are not blocked, if content root has such subdirectory
225 test_localhost_gateway_response_should_contain \
226 "request for {CID}.ipfs.localhost/ipfs/file.txt should return data from a file in CID content root" \
227 "http://${DIR_CID}.ipfs.localhost:$GWAY_PORT/ipfs/file.txt" \
228 "I am a txt file"
229
230 # Kubo specific end-to-end test
231 # (independent of gateway-conformance)
232 # This tests link to parent specific to boxo + relative pathing end-to-end tests specific to Kubo.
233
234 # {CID}.ipfs.localhost/sub/dir (Directory Listing)
235 DIR_HOSTNAME="${DIR_CID}.ipfs.localhost:$GWAY_PORT"
236
237 test_expect_success "valid file and subdirectory paths in directory listing at {cid}.ipfs.localhost" '
238 curl -s --resolve $DIR_HOSTNAME:127.0.0.1 "http://$DIR_HOSTNAME" > list_response &&
239 test_should_contain "<a href=\"/hello\">hello</a>" list_response &&
240 test_should_contain "<a href=\"/ipfs\">ipfs</a>" list_response
241 '
242
243 test_expect_success "valid parent directory path in directory listing at {cid}.ipfs.localhost/sub/dir" '
244 curl -s --resolve $DIR_HOSTNAME:127.0.0.1 "http://$DIR_HOSTNAME/ipfs/ipns/" > list_response &&
245 test_should_contain "<a href=\"/ipfs/ipns/..\">..</a>" list_response &&
246 test_should_contain "<a href=\"/ipfs/ipns/bar\">bar</a>" list_response
247 '
248
249 test_expect_success "request for deep path resource at {cid}.ipfs.localhost/sub/dir/file" '
250 curl -s --resolve $DIR_HOSTNAME:127.0.0.1 "http://$DIR_HOSTNAME/ipfs/ipns/bar" > list_response &&
251 test_should_contain "text-file-content" list_response
252 '
253 # end Kubo specific end-to-end test
254
255 # *.ipns.localhost
256
257 # <libp2p-key>.ipns.localhost
258
259 test_localhost_gateway_response_should_contain \
260 "request for {CIDv1-libp2p-key}.ipns.localhost returns expected payload" \
261 "http://${RSA_IPNS_IDv1}.ipns.localhost:$GWAY_PORT" \
262 "$CID_VAL"
263
264 test_localhost_gateway_response_should_contain \
265 "request for {CIDv1-libp2p-key}.ipns.localhost returns expected payload" \
266 "http://${ED25519_IPNS_IDv1}.ipns.localhost:$GWAY_PORT" \
267 "$CID_VAL"
268
269 test_localhost_gateway_response_should_contain \
270 "localhost request for {CIDv1-dag-pb}.ipns.localhost redirects to CID with libp2p-key multicodec" \
271 "http://${RSA_IPNS_IDv1_DAGPB}.ipns.localhost:$GWAY_PORT" \
272 "Location: http://${RSA_IPNS_IDv1}.ipns.localhost:$GWAY_PORT/"
273
274 test_localhost_gateway_response_should_contain \
275 "localhost request for {CIDv1-dag-pb}.ipns.localhost redirects to CID with libp2p-key multicodec" \
276 "http://${ED25519_IPNS_IDv1_DAGPB}.ipns.localhost:$GWAY_PORT" \
277 "Location: http://${ED25519_IPNS_IDv1}.ipns.localhost:$GWAY_PORT/"
278
279 # <dnslink-fqdn>.ipns.localhost
280
281 # DNSLink test requires a daemon in online mode with precached /ipns/ mapping
282 test_kill_ipfs_daemon
283 DNSLINK_FQDN="dnslink-test.example.com"
284 export IPFS_NS_MAP="$DNSLINK_FQDN:/ipfs/$CIDv1"
285 test_launch_ipfs_daemon
286
287 test_localhost_gateway_response_should_contain \
288 "request for {dnslink}.ipns.localhost returns expected payload" \
289 "http://$DNSLINK_FQDN.ipns.localhost:$GWAY_PORT" \
290 "$CID_VAL"
291
292 ## ============================================================================
293 ## Test DNSLink inlining on HTTP gateways
294 ## ============================================================================
295
296 # set explicit subdomain gateway config for the hostname
297 ipfs config --json Gateway.PublicGateways '{
298 "localhost": {
299 "UseSubdomains": true,
300 "InlineDNSLink": true,
301 "Paths": ["/ipfs", "/ipns", "/api"]
302 },
303 "example.com": {
304 "UseSubdomains": true,
305 "InlineDNSLink": true,
306 "Paths": ["/ipfs", "/ipns", "/api"]
307 }
308 }' || exit 1
309 # restart daemon to apply config changes
310 test_kill_ipfs_daemon
311 test_launch_ipfs_daemon_without_network
312
313 test_localhost_gateway_response_should_contain \
314 "request for localhost/ipns/{fqdn} redirects to DNSLink in subdomain with DNS inlining" \
315 "http://localhost:$GWAY_PORT/ipns/en.wikipedia-on-ipfs.org/wiki" \
316 "Location: http://en-wikipedia--on--ipfs-org.ipns.localhost:$GWAY_PORT/wiki"
317
318 test_hostname_gateway_response_should_contain \
319 "request for example.com/ipns/{fqdn} redirects to DNSLink in subdomain with DNS inlining" \
320 "example.com" \
321 "http://127.0.0.1:$GWAY_PORT/ipns/en.wikipedia-on-ipfs.org/wiki" \
322 "Location: http://en-wikipedia--on--ipfs-org.ipns.example.com/wiki"
323
324 ## ============================================================================
325 ## Test subdomain-based requests with a custom hostname config
326 ## (origin per content root at http://*.example.com)
327 ## ============================================================================
328
329 # set explicit subdomain gateway config for the hostname
330 ipfs config --json Gateway.PublicGateways '{
331 "example.com": {
332 "UseSubdomains": true,
333 "Paths": ["/ipfs", "/ipns", "/api"]
334 }
335 }' || exit 1
336 # restart daemon to apply config changes
337 test_kill_ipfs_daemon
338 test_launch_ipfs_daemon_without_network
339
340
341 # example.com/ip(f|n)s/*
342 # =============================================================================
343
344 # path requests to the root hostname should redirect
345 # to a subdomain URL with proper origin isolation
346
347 test_hostname_gateway_response_should_contain \
348 "request for example.com/ipfs/{CIDv1} produces redirect to {CIDv1}.ipfs.example.com" \
349 "example.com" \
350 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1" \
351 "Location: http://$CIDv1.ipfs.example.com/"
352
353 # error message should include original CID
354 # (and it should be case-sensitive, as we can't assume everyone uses base32)
355 test_hostname_gateway_response_should_contain \
356 "request for example.com/ipfs/{InvalidCID} produces useful error before redirect" \
357 "example.com" \
358 "http://127.0.0.1:$GWAY_PORT/ipfs/QmInvalidCID" \
359 'invalid path \"/ipfs/QmInvalidCID\"'
360
361 test_hostname_gateway_response_should_contain \
362 "request for example.com/ipfs/{CIDv0} produces redirect to {CIDv1}.ipfs.example.com" \
363 "example.com" \
364 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv0" \
365 "Location: http://${CIDv0to1}.ipfs.example.com/"
366
367 # Support X-Forwarded-Proto
368 test_expect_success "request for http://example.com/ipfs/{CID} with X-Forwarded-Proto: https produces redirect to HTTPS URL" "
369 curl -H \"X-Forwarded-Proto: https\" -H \"Host: example.com\" -sD - \"http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1\" > response &&
370 test_should_contain \"Location: https://$CIDv1.ipfs.example.com/\" response
371 "
372
373 # Support ipfs:// in https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler
374 test_hostname_gateway_response_should_contain \
375 "request for example.com/ipfs/?uri=ipfs%3A%2F%2F.. produces redirect to /ipfs/.. content path" \
376 "example.com" \
377 "http://127.0.0.1:$GWAY_PORT/ipfs/?uri=ipfs%3A%2F%2FQmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco%2Fwiki%2FDiego_Maradona.html" \
378 "Location: /ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco/wiki/Diego_Maradona.html"
379
380 # example.com/ipns/<libp2p-key>
381
382 test_hostname_gateway_response_should_contain \
383 "request for example.com/ipns/{CIDv0} redirects to CIDv1 with libp2p-key multicodec in subdomain" \
384 "example.com" \
385 "http://127.0.0.1:$GWAY_PORT/ipns/$RSA_IPNS_IDv0" \
386 "Location: http://${RSA_IPNS_IDv1}.ipns.example.com/"
387
388 test_hostname_gateway_response_should_contain \
389 "request for example.com/ipns/{CIDv0} redirects to CIDv1 with libp2p-key multicodec in subdomain" \
390 "example.com" \
391 "http://127.0.0.1:$GWAY_PORT/ipns/$ED25519_IPNS_IDv0" \
392 "Location: http://${ED25519_IPNS_IDv1}.ipns.example.com/"
393
394 # example.com/ipns/<dnslink-fqdn>
395
396 test_hostname_gateway_response_should_contain \
397 "request for example.com/ipns/{fqdn} redirects to DNSLink in subdomain" \
398 "example.com" \
399 "http://127.0.0.1:$GWAY_PORT/ipns/en.wikipedia-on-ipfs.org/wiki" \
400 "Location: http://en.wikipedia-on-ipfs.org.ipns.example.com/wiki"
401
402 # DNSLink on Public gateway with a single-level wildcard TLS cert
403 # "Option C" from https://github.com/ipfs/in-web-browsers/issues/169
404 test_expect_success \
405 "request for example.com/ipns/{fqdn} with X-Forwarded-Proto redirects to TLS-safe label in subdomain" "
406 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 &&
407 test_should_contain \"Location: https://en-wikipedia--on--ipfs-org.ipns.example.com/wiki\" response
408 "
409
410 # Support ipns:// in https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler
411 test_hostname_gateway_response_should_contain \
412 "request for example.com/ipns/?uri=ipns%3A%2F%2F.. produces redirect to /ipns/.. content path" \
413 "example.com" \
414 "http://127.0.0.1:$GWAY_PORT/ipns/?uri=ipns%3A%2F%2Fen.wikipedia-on-ipfs.org" \
415 "Location: /ipns/en.wikipedia-on-ipfs.org"
416
417 # *.ipfs.example.com: subdomain requests made with custom FQDN in Host header
418
419 test_hostname_gateway_response_should_contain \
420 "request for {CID}.ipfs.example.com should return expected payload" \
421 "${CIDv1}.ipfs.example.com" \
422 "http://127.0.0.1:$GWAY_PORT/" \
423 "$CID_VAL"
424
425 test_hostname_gateway_response_should_contain \
426 "request for {CID}.ipfs.example.com/ipfs/{CID} should return HTTP 404" \
427 "${CIDv1}.ipfs.example.com" \
428 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1" \
429 "404 Not Found"
430
431 # Kubo specific end-to-end test
432 # (independent of gateway-conformance)
433 # HTML specific to Boxo/Kubo, and relative pathing specific to code in Kubo
434
435 # {CID}.ipfs.example.com/sub/dir (Directory Listing)
436 DIR_FQDN="${DIR_CID}.ipfs.example.com"
437
438 test_expect_success "valid file and directory paths in directory listing at {cid}.ipfs.example.com" '
439 curl -s -H "Host: $DIR_FQDN" http://127.0.0.1:$GWAY_PORT > list_response &&
440 test_should_contain "<a href=\"/hello\">hello</a>" list_response &&
441 test_should_contain "<a href=\"/ipfs\">ipfs</a>" list_response
442 '
443
444 test_expect_success "valid parent directory path in directory listing at {cid}.ipfs.example.com/sub/dir" '
445 curl -s -H "Host: $DIR_FQDN" http://127.0.0.1:$GWAY_PORT/ipfs/ipns/ > list_response &&
446 test_should_contain "<a href=\"/ipfs/ipns/..\">..</a>" list_response &&
447 test_should_contain "<a href=\"/ipfs/ipns/bar\">bar</a>" list_response
448 '
449
450 # Note 1: we test for sneaky subdir names {cid}.ipfs.example.com/ipfs/ipns/ :^)
451 # Note 2: example.com/ipfs/.. present in HTML will be redirected to subdomain, so this is expected behavior
452 test_expect_success "valid breadcrumb links in the header of directory listing at {cid}.ipfs.example.com/sub/dir" '
453 curl -s -H "Host: $DIR_FQDN" http://127.0.0.1:$GWAY_PORT/ipfs/ipns/ > list_response &&
454 test_should_contain "Index of" list_response &&
455 test_should_contain "/ipfs/<a href=\"//example.com/ipfs/${DIR_CID}\">${DIR_CID}</a>/<a href=\"//example.com/ipfs/${DIR_CID}/ipfs\">ipfs</a>/<a href=\"//example.com/ipfs/${DIR_CID}/ipfs/ipns\">ipns</a>" list_response
456 '
457
458 # end Kubo specific end-to-end test
459
460 test_expect_success "request for deep path resource {cid}.ipfs.example.com/sub/dir/file" '
461 curl -s -H "Host: $DIR_FQDN" http://127.0.0.1:$GWAY_PORT/ipfs/ipns/bar > list_response &&
462 test_should_contain "text-file-content" list_response
463 '
464
465 # *.ipns.example.com
466 # ============================================================================
467
468 # <libp2p-key>.ipns.example.com
469
470 test_hostname_gateway_response_should_contain \
471 "request for {CIDv1-libp2p-key}.ipns.example.com returns expected payload" \
472 "${RSA_IPNS_IDv1}.ipns.example.com" \
473 "http://127.0.0.1:$GWAY_PORT" \
474 "$CID_VAL"
475
476 test_hostname_gateway_response_should_contain \
477 "request for {CIDv1-libp2p-key}.ipns.example.com returns expected payload" \
478 "${ED25519_IPNS_IDv1}.ipns.example.com" \
479 "http://127.0.0.1:$GWAY_PORT" \
480 "$CID_VAL"
481
482 test_hostname_gateway_response_should_contain \
483 "hostname request for {CIDv1-dag-pb}.ipns.localhost redirects to CID with libp2p-key multicodec" \
484 "${RSA_IPNS_IDv1_DAGPB}.ipns.example.com" \
485 "http://127.0.0.1:$GWAY_PORT" \
486 "Location: http://${RSA_IPNS_IDv1}.ipns.example.com/"
487
488 test_hostname_gateway_response_should_contain \
489 "hostname request for {CIDv1-dag-pb}.ipns.localhost redirects to CID with libp2p-key multicodec" \
490 "${ED25519_IPNS_IDv1_DAGPB}.ipns.example.com" \
491 "http://127.0.0.1:$GWAY_PORT" \
492 "Location: http://${ED25519_IPNS_IDv1}.ipns.example.com/"
493
494 # DNSLink: <dnslink-fqdn>.ipns.example.com
495 # (not really useful outside of localhost, as setting TLS for more than one
496 # level of wildcard is a pain, but we support it if someone really wants it)
497 # ============================================================================
498
499 # DNSLink test requires a daemon in online mode with precached /ipns/ mapping
500 test_kill_ipfs_daemon
501 DNSLINK_FQDN="dnslink-subdomain-gw-test.example.org"
502 export IPFS_NS_MAP="$DNSLINK_FQDN:/ipfs/$CIDv1"
503 test_launch_ipfs_daemon
504
505 test_hostname_gateway_response_should_contain \
506 "request for {dnslink}.ipns.example.com returns expected payload" \
507 "$DNSLINK_FQDN.ipns.example.com" \
508 "http://127.0.0.1:$GWAY_PORT" \
509 "$CID_VAL"
510
511 # DNSLink on Public gateway with a single-level wildcard TLS cert
512 # "Option C" from https://github.com/ipfs/in-web-browsers/issues/169
513 test_expect_success \
514 "request for {single-label-dnslink}.ipns.example.com with X-Forwarded-Proto returns expected payload" "
515 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 &&
516 test_should_contain \"$CID_VAL\" response
517 "
518
519 ## Test subdomain handling of CIDs that do not fit in a single DNS Label (>63chars)
520 ## https://github.com/ipfs/go-ipfs/issues/7318
521 ## ============================================================================
522
523 # local: *.localhost
524 test_localhost_gateway_response_should_contain \
525 "request for a ED25519 libp2p-key at localhost/ipns/{b58mh} returns Location HTTP header for DNS-safe subdomain redirect in browsers" \
526 "http://localhost:$GWAY_PORT/ipns/$IPNS_ED25519_B58MH" \
527 "Location: http://${IPNS_ED25519_B36CID}.ipns.localhost:$GWAY_PORT/"
528
529 # router should not redirect to hostnames that could fail due to DNS limits
530 test_localhost_gateway_response_should_contain \
531 "request for a too long CID at localhost/ipfs/{CIDv1} returns human readable error" \
532 "http://localhost:$GWAY_PORT/ipfs/$CIDv1_TOO_LONG" \
533 "CID incompatible with DNS label length limit of 63"
534
535 test_localhost_gateway_response_should_contain \
536 "request for a too long CID at localhost/ipfs/{CIDv1} returns HTTP Error 400 Bad Request" \
537 "http://localhost:$GWAY_PORT/ipfs/$CIDv1_TOO_LONG" \
538 "400 Bad Request"
539
540 # direct request should also fail (provides the same UX as router and avoids confusion)
541 test_localhost_gateway_response_should_contain \
542 "request for a too long CID at {CIDv1}.ipfs.localhost returns expected payload" \
543 "http://$CIDv1_TOO_LONG.ipfs.localhost:$GWAY_PORT" \
544 "400 Bad Request"
545
546 # public subdomain gateway: *.example.com
547
548 test_hostname_gateway_response_should_contain \
549 "request for a ED25519 libp2p-key at example.com/ipns/{b58mh} returns Location HTTP header for DNS-safe subdomain redirect in browsers" \
550 "example.com" \
551 "http://127.0.0.1:$GWAY_PORT/ipns/$IPNS_ED25519_B58MH" \
552 "Location: http://${IPNS_ED25519_B36CID}.ipns.example.com"
553
554 test_hostname_gateway_response_should_contain \
555 "request for a too long CID at example.com/ipfs/{CIDv1} returns human readable error" \
556 "example.com" \
557 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1_TOO_LONG" \
558 "CID incompatible with DNS label length limit of 63"
559
560 test_hostname_gateway_response_should_contain \
561 "request for a too long CID at example.com/ipfs/{CIDv1} returns HTTP Error 400 Bad Request" \
562 "example.com" \
563 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1_TOO_LONG" \
564 "400 Bad Request"
565
566 test_hostname_gateway_response_should_contain \
567 "request for a too long CID at {CIDv1}.ipfs.example.com returns HTTP Error 400 Bad Request" \
568 "$CIDv1_TOO_LONG.ipfs.example.com" \
569 "http://127.0.0.1:$GWAY_PORT/" \
570 "400 Bad Request"
571
572 # Disable selected Paths for the subdomain gateway hostname
573 # =============================================================================
574
575 # disable /ipns for the hostname by not whitelisting it
576 ipfs config --json Gateway.PublicGateways '{
577 "example.com": {
578 "UseSubdomains": true,
579 "Paths": ["/ipfs"]
580 }
581 }' || exit 1
582 # restart daemon to apply config changes
583 test_kill_ipfs_daemon
584 test_launch_ipfs_daemon_without_network
585
586 # refuse requests to Paths that were not explicitly whitelisted for the hostname
587 test_hostname_gateway_response_should_contain \
588 "request for *.ipns.example.com returns HTTP 404 Not Found when /ipns is not on Paths whitelist" \
589 "${RSA_IPNS_IDv1}.ipns.example.com" \
590 "http://127.0.0.1:$GWAY_PORT" \
591 "404 Not Found"
592
593 test_hostname_gateway_response_should_contain \
594 "request for *.ipns.example.com returns HTTP 404 Not Found when /ipns is not on Paths whitelist" \
595 "${ED25519_IPNS_IDv1}.ipns.example.com" \
596 "http://127.0.0.1:$GWAY_PORT" \
597 "404 Not Found"
598
599 ## ============================================================================
600 ## Test path-based requests with a custom hostname config
601 ## ============================================================================
602
603 # set explicit no-subdomain gateway config for the hostname
604 ipfs config --json Gateway.PublicGateways '{
605 "example.com": {
606 "UseSubdomains": false,
607 "Paths": ["/ipfs"]
608 }
609 }' || exit 1
610
611 # restart daemon to apply config changes
612 test_kill_ipfs_daemon
613 test_launch_ipfs_daemon_without_network
614
615 # example.com/ip(f|n)s/* smoke-tests
616 # =============================================================================
617
618 # confirm path gateway works for /ipfs
619 test_hostname_gateway_response_should_contain \
620 "request for example.com/ipfs/{CIDv1} returns expected payload" \
621 "example.com" \
622 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1" \
623 "$CID_VAL"
624
625 # refuse subdomain requests on path gateway
626 # (we don't want false sense of security)
627 test_hostname_gateway_response_should_contain \
628 "request for {CID}.ipfs.example.com/ipfs/{CID} should return HTTP 404 Not Found" \
629 "${CIDv1}.ipfs.example.com" \
630 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1" \
631 "404 Not Found"
632
633 # refuse requests to Paths that were not explicitly whitelisted for the hostname
634 test_hostname_gateway_response_should_contain \
635 "request for example.com/ipns/ returns HTTP 404 Not Found when /ipns is not on Paths whitelist" \
636 "example.com" \
637 "http://127.0.0.1:$GWAY_PORT/ipns/$RSA_IPNS_IDv1" \
638 "404 Not Found"
639
640 test_hostname_gateway_response_should_contain \
641 "request for example.com/ipns/ returns HTTP 404 Not Found when /ipns is not on Paths whitelist" \
642 "example.com" \
643 "http://127.0.0.1:$GWAY_PORT/ipns/$ED25519_IPNS_IDv1" \
644 "404 Not Found"
645
646 ## ============================================================================
647 ## Test DNSLink requests with a custom PublicGateway (hostname config)
648 ## (DNSLink site at http://dnslink-test.example.com)
649 ## ============================================================================
650
651 test_kill_ipfs_daemon
652
653 # disable wildcard DNSLink gateway
654 # and enable it on specific NSLink hostname
655 ipfs config --json Gateway.NoDNSLink true && \
656 ipfs config --json Gateway.PublicGateways '{
657 "dnslink-enabled-on-fqdn.example.org": {
658 "NoDNSLink": false,
659 "UseSubdomains": false,
660 "Paths": ["/ipfs"]
661 },
662 "only-dnslink-enabled-on-fqdn.example.org": {
663 "NoDNSLink": false,
664 "UseSubdomains": false,
665 "Paths": []
666 },
667 "dnslink-disabled-on-fqdn.example.com": {
668 "NoDNSLink": true,
669 "UseSubdomains": false,
670 "Paths": []
671 }
672 }' || exit 1
673
674 # DNSLink test requires a daemon in online mode with precached /ipns/ mapping
675 DNSLINK_FQDN="dnslink-enabled-on-fqdn.example.org"
676 ONLY_DNSLINK_FQDN="only-dnslink-enabled-on-fqdn.example.org"
677 NO_DNSLINK_FQDN="dnslink-disabled-on-fqdn.example.com"
678 export IPFS_NS_MAP="$DNSLINK_FQDN:/ipfs/$CIDv1,$ONLY_DNSLINK_FQDN:/ipfs/$DIR_CID"
679
680 # restart daemon to apply config changes
681 test_launch_ipfs_daemon
682
683 # make sure test setup is valid (fail if CoreAPI is unable to resolve)
684 test_expect_success "spoofed DNSLink record resolves in cli" "
685 ipfs resolve /ipns/$DNSLINK_FQDN > result &&
686 test_should_contain \"$CIDv1\" result &&
687 ipfs cat /ipns/$DNSLINK_FQDN > result &&
688 test_should_contain \"$CID_VAL\" result
689 "
690
691 # DNSLink enabled
692
693 test_hostname_gateway_response_should_contain \
694 "request for http://{dnslink-fqdn}/ PublicGateway returns expected payload" \
695 "$DNSLINK_FQDN" \
696 "http://127.0.0.1:$GWAY_PORT/" \
697 "$CID_VAL"
698
699 test_hostname_gateway_response_should_contain \
700 "request for {dnslink-fqdn}/ipfs/{cid} returns expected payload when /ipfs is on Paths whitelist" \
701 "$DNSLINK_FQDN" \
702 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1" \
703 "$CID_VAL"
704
705 # Test for a fun edge case: DNSLink-only gateway without /ipfs/ namespace
706 # mounted, and with subdirectory named "ipfs" ¯\_(ツ)_/¯
707 test_hostname_gateway_response_should_contain \
708 "request for {dnslink-fqdn}/ipfs/file.txt returns data from content root when /ipfs in not on Paths whitelist" \
709 "$ONLY_DNSLINK_FQDN" \
710 "http://127.0.0.1:$GWAY_PORT/ipfs/file.txt" \
711 "I am a txt file"
712
713 test_hostname_gateway_response_should_contain \
714 "request for {dnslink-fqdn}/ipns/{peerid} returns 404 when path is not whitelisted" \
715 "$DNSLINK_FQDN" \
716 "http://127.0.0.1:$GWAY_PORT/ipns/$RSA_IPNS_IDv0" \
717 "404 Not Found"
718
719 test_hostname_gateway_response_should_contain \
720 "request for {dnslink-fqdn}/ipns/{peerid} returns 404 when path is not whitelisted" \
721 "$DNSLINK_FQDN" \
722 "http://127.0.0.1:$GWAY_PORT/ipns/$ED25519_IPNS_IDv0" \
723 "404 Not Found"
724
725 # DNSLink disabled
726
727 test_hostname_gateway_response_should_contain \
728 "request for http://{dnslink-fqdn}/ returns 404 when NoDNSLink=true" \
729 "$NO_DNSLINK_FQDN" \
730 "http://127.0.0.1:$GWAY_PORT/" \
731 "404 Not Found"
732
733 test_hostname_gateway_response_should_contain \
734 "request for {dnslink-fqdn}/ipfs/{cid} returns 404 when path is not whitelisted" \
735 "$NO_DNSLINK_FQDN" \
736 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv0" \
737 "404 Not Found"
738
739
740 ## ============================================================================
741 ## Test wildcard DNSLink (any hostname, with default config)
742 ## ============================================================================
743
744 test_kill_ipfs_daemon
745
746 # enable wildcard DNSLink gateway (any value in Host header)
747 # and remove custom PublicGateways
748 ipfs config --json Gateway.NoDNSLink false && \
749 ipfs config --json Gateway.PublicGateways '{}' || exit 1
750
751 # DNSLink test requires a daemon in online mode with precached /ipns/ mapping
752 DNSLINK_FQDN="wildcard-dnslink-not-in-config.example.com"
753 export IPFS_NS_MAP="$DNSLINK_FQDN:/ipfs/$CIDv1"
754
755 # restart daemon to apply config changes
756 test_launch_ipfs_daemon
757
758 # make sure test setup is valid (fail if CoreAPI is unable to resolve)
759 test_expect_success "spoofed DNSLink record resolves in cli" "
760 ipfs resolve /ipns/$DNSLINK_FQDN > result &&
761 test_should_contain \"$CIDv1\" result &&
762 ipfs cat /ipns/$DNSLINK_FQDN > result &&
763 test_should_contain \"$CID_VAL\" result
764 "
765
766 # gateway test
767 test_hostname_gateway_response_should_contain \
768 "request for http://{dnslink-fqdn}/ (wildcard) returns expected payload" \
769 "$DNSLINK_FQDN" \
770 "http://127.0.0.1:$GWAY_PORT/" \
771 "$CID_VAL"
772
773 ## ============================================================================
774 ## Test support for X-Forwarded-Host
775 ## ============================================================================
776
777 # set explicit subdomain gateway config for the hostname
778 ipfs config --json Gateway.PublicGateways '{
779 "example.com": {
780 "UseSubdomains": true,
781 "Paths": ["/ipfs", "/ipns", "/api"]
782 }
783 }' || exit 1
784 # restart daemon to apply config changes
785 test_kill_ipfs_daemon
786 test_launch_ipfs_daemon_without_network
787
788 test_expect_success "request for http://fake.domain.com/ipfs/{CID} doesn't match the example.com gateway" "
789 curl -H \"Host: fake.domain.com\" -sD - \"http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1\" > response &&
790 test_should_contain \"200 OK\" response
791 "
792
793 test_expect_success "request for http://fake.domain.com/ipfs/{CID} with X-Forwarded-Host: example.com match the example.com gateway" "
794 curl -H \"Host: fake.domain.com\" -H \"X-Forwarded-Host: example.com\" -sD - \"http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1\" > response &&
795 test_should_contain \"Location: http://$CIDv1.ipfs.example.com/\" response
796 "
797
798 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" "
799 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 &&
800 test_should_contain \"Location: https://$CIDv1.ipfs.example.com/\" response
801 "
802
803 # Kubo specific end-to-end test
804 # (independent of gateway-conformance)
805 # test configuration being wired up correctly end-to-end
806
807 ## ============================================================================
808 ## Test support for wildcards in gateway config
809 ## ============================================================================
810
811 # set explicit subdomain gateway config for the hostnames
812 ipfs config --json Gateway.PublicGateways '{
813 "*.example1.com": {
814 "UseSubdomains": true,
815 "Paths": ["/ipfs"]
816 },
817 "*.*.example2.com": {
818 "UseSubdomains": true,
819 "Paths": ["/ipfs"]
820 },
821 "foo.*.example3.com": {
822 "UseSubdomains": true,
823 "Paths": ["/ipfs"]
824 },
825 "foo.bar-*-boo.example4.com": {
826 "UseSubdomains": true,
827 "Paths": ["/ipfs"]
828 }
829 }' || exit 1
830 # restart daemon to apply config changes
831 test_kill_ipfs_daemon
832 test_launch_ipfs_daemon_without_network
833
834 # *.example1.com
835
836 test_hostname_gateway_response_should_contain \
837 "request for foo.example1.com/ipfs/{CIDv1} produces redirect to {CIDv1}.ipfs.foo.example1.com" \
838 "foo.example1.com" \
839 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1" \
840 "Location: http://$CIDv1.ipfs.foo.example1.com/"
841
842 test_hostname_gateway_response_should_contain \
843 "request for {CID}.ipfs.foo.example1.com should return expected payload" \
844 "${CIDv1}.ipfs.foo.example1.com" \
845 "http://127.0.0.1:$GWAY_PORT/" \
846 "$CID_VAL"
847
848 # *.*.example2.com
849
850 test_hostname_gateway_response_should_contain \
851 "request for foo.bar.example2.com/ipfs/{CIDv1} produces redirect to {CIDv1}.ipfs.foo.bar.example2.com" \
852 "foo.bar.example2.com" \
853 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1" \
854 "Location: http://$CIDv1.ipfs.foo.bar.example2.com/"
855
856 test_hostname_gateway_response_should_contain \
857 "request for {CID}.ipfs.foo.bar.example2.com should return expected payload" \
858 "${CIDv1}.ipfs.foo.bar.example2.com" \
859 "http://127.0.0.1:$GWAY_PORT/" \
860 "$CID_VAL"
861
862 # foo.*.example3.com
863
864 test_hostname_gateway_response_should_contain \
865 "request for foo.bar.example3.com/ipfs/{CIDv1} produces redirect to {CIDv1}.ipfs.foo.bar.example3.com" \
866 "foo.bar.example3.com" \
867 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1" \
868 "Location: http://$CIDv1.ipfs.foo.bar.example3.com/"
869
870 test_hostname_gateway_response_should_contain \
871 "request for {CID}.ipfs.foo.bar.example3.com should return expected payload" \
872 "${CIDv1}.ipfs.foo.bar.example3.com" \
873 "http://127.0.0.1:$GWAY_PORT/" \
874 "$CID_VAL"
875
876 # foo.bar-*-boo.example4.com
877
878 test_hostname_gateway_response_should_contain \
879 "request for foo.bar-dev-boo.example4.com/ipfs/{CIDv1} produces redirect to {CIDv1}.ipfs.foo.bar-dev-boo.example4.com" \
880 "foo.bar-dev-boo.example4.com" \
881 "http://127.0.0.1:$GWAY_PORT/ipfs/$CIDv1" \
882 "Location: http://$CIDv1.ipfs.foo.bar-dev-boo.example4.com/"
883
884 test_hostname_gateway_response_should_contain \
885 "request for {CID}.ipfs.foo.bar-dev-boo.example4.com should return expected payload" \
886 "${CIDv1}.ipfs.foo.bar-dev-boo.example4.com" \
887 "http://127.0.0.1:$GWAY_PORT/" \
888 "$CID_VAL"
889
890 ## ============================================================================
891 ## Test support for overriding implicit defaults
892 ## ============================================================================
893
894 # disable subdomain gateway at localhost by removing implicit config
895 ipfs config --json Gateway.PublicGateways '{
896 "localhost": null
897 }' || exit 1
898
899 # restart daemon to apply config changes
900 test_kill_ipfs_daemon
901 test_launch_ipfs_daemon_without_network
902
903 test_localhost_gateway_response_should_contain \
904 "request for localhost/ipfs/{CID} stays on path when subdomain gw is explicitly disabled" \
905 "http://localhost:$GWAY_PORT/ipfs/$CIDv1" \
906 "$CID_VAL"
907
908 # =============================================================================
909 # ensure we end with empty Gateway.PublicGateways
910 ipfs config --json Gateway.PublicGateways '{}'
911 test_kill_ipfs_daemon
912
913 test_expect_success "clean up ipfs dir" '
914 rm -rf "$IPFS_PATH"
915 '
916
917 test_done
918
919 # end Kubo specific end-to-end test