core/corehttp!: remove /api/v0 from gateway port
Henrique Dias committed
Mar 5, 2024 at 09:21 UTC
e22f47ae4b4d83cf5e3f1c901c10905ebd69d492
13 files changed
+12
-367
cmd/ipfs/kubo/daemon.go
-2
@@ -850,8 +850,6 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
850
corehttp.GatewayOption("/ipfs", "/ipns"),
851
corehttp.VersionOption(),
852
corehttp.CheckVersionOption(),
853
- // TODO[api-on-gw]: remove for 0.28.0: https://github.com/ipfs/kubo/issues/10312
854
- corehttp.CommandsROOption(cmdctx),
853
}
854
855
if cfg.Experimental.P2pHttpProxy {
config/gateway.go
+1
-1
@@ -9,7 +9,7 @@ const (
9
10
type GatewaySpec struct {
11
// Paths is explicit list of path prefixes that should be handled by
12
- // this gateway. Example: `["/ipfs", "/ipns", "/api"]`
12
+ // this gateway. Example: `["/ipfs", "/ipns"]`
13
Paths []string
14
15
// UseSubdomains indicates whether or not this gateway uses subdomains
core/commands/commands_test.go
-57
@@ -15,63 +15,6 @@ func collectPaths(prefix string, cmd *cmds.Command, out map[string]struct{}) {
15
}
16
}
17
18
-func TestROCommands(t *testing.T) {
19
- list := []string{
20
- "/block",
21
- "/block/get",
22
- "/block/stat",
23
- "/cat",
24
- "/commands",
25
- "/commands/completion",
26
- "/commands/completion/bash",
27
- "/commands/completion/fish",
28
- "/commands/completion/zsh",
29
- "/dag",
30
- "/dag/get",
31
- "/dag/resolve",
32
- "/dag/stat",
33
- "/dag/export",
34
- "/get",
35
- "/ls",
36
- "/name",
37
- "/name/resolve",
38
- "/object",
39
- "/object/data",
40
- "/object/get",
41
- "/object/links",
42
- "/object/stat",
43
- "/refs",
44
- "/resolve",
45
- "/version",
46
- }
47
-
48
- cmdSet := make(map[string]struct{})
49
- collectPaths("", RootRO, cmdSet)
50
-
51
- for _, path := range list {
52
- if _, ok := cmdSet[path]; !ok {
53
- t.Errorf("%q not in result", path)
54
- } else {
55
- delete(cmdSet, path)
56
- }
57
- }
58
-
59
- for path := range cmdSet {
60
- t.Errorf("%q in result but shouldn't be", path)
61
- }
62
-
63
- for _, path := range list {
64
- path = path[1:] // remove leading slash
65
- split := strings.Split(path, "/")
66
- sub, err := RootRO.Get(split)
67
- if err != nil {
68
- t.Errorf("error getting subcommand %q: %v", path, err)
69
- } else if sub == nil {
70
- t.Errorf("subcommand %q is nil even though there was no error", path)
71
- }
72
- }
73
-}
74
-
18
func TestCommands(t *testing.T) {
19
list := []string{
20
"/add",
core/commands/root.go
-63
@@ -162,72 +162,9 @@ var rootSubcommands = map[string]*cmds.Command{
162
"multibase": MbaseCmd,
163
}
164
165
-// RootRO is the readonly version of Root
166
-var RootRO = &cmds.Command{}
167
-
168
-var CommandsDaemonROCmd = CommandsCmd(RootRO)
169
-
170
-// RefsROCmd is `ipfs refs` command
171
-var RefsROCmd = &cmds.Command{}
172
-
173
-// VersionROCmd is `ipfs version` command (without deps).
174
-var VersionROCmd = &cmds.Command{}
175
-
176
-var rootROSubcommands = map[string]*cmds.Command{
177
- "commands": CommandsDaemonROCmd,
178
- "cat": CatCmd,
179
- "block": {
180
- Subcommands: map[string]*cmds.Command{
181
- "stat": blockStatCmd,
182
- "get": blockGetCmd,
183
- },
184
- },
185
- "get": GetCmd,
186
- "ls": LsCmd,
187
- "name": {
188
- Subcommands: map[string]*cmds.Command{
189
- "resolve": name.IpnsCmd,
190
- },
191
- },
192
- "object": {
193
- Subcommands: map[string]*cmds.Command{
194
- "data": ocmd.ObjectDataCmd,
195
- "links": ocmd.ObjectLinksCmd,
196
- "get": ocmd.ObjectGetCmd,
197
- "stat": ocmd.ObjectStatCmd,
198
- },
199
- },
200
- "dag": {
201
- Subcommands: map[string]*cmds.Command{
202
- "get": dag.DagGetCmd,
203
- "resolve": dag.DagResolveCmd,
204
- "stat": dag.DagStatCmd,
205
- "export": dag.DagExportCmd,
206
- },
207
- },
208
- "resolve": ResolveCmd,
209
-}
210
-
165
func init() {
166
Root.ProcessHelp()
213
- *RootRO = *Root
214
-
215
- // this was in the big map definition above before,
216
- // but if we leave it there lgc.NewCommand will be executed
217
- // before the value is updated (:/sanitize readonly refs command/)
218
-
219
- // sanitize readonly refs command
220
- *RefsROCmd = *RefsCmd
221
- RefsROCmd.Subcommands = map[string]*cmds.Command{}
222
- rootROSubcommands["refs"] = RefsROCmd
223
-
224
- // sanitize readonly version command (no need to expose precise deps)
225
- *VersionROCmd = *VersionCmd
226
- VersionROCmd.Subcommands = map[string]*cmds.Command{}
227
- rootROSubcommands["version"] = VersionROCmd
228
-
167
Root.Subcommands = rootSubcommands
230
- RootRO.Subcommands = rootROSubcommands
168
}
169
170
type MessageOutput struct {
core/commands/root_test.go
-1
@@ -18,5 +18,4 @@ func TestCommandTree(t *testing.T) {
18
}
19
}
20
printErrors(Root.DebugValidate())
21
- printErrors(RootRO.DebugValidate())
21
}
core/corehttp/commands.go
+2
-20
@@ -9,7 +9,6 @@ import (
9
"strconv"
10
"strings"
11
12
- "github.com/ipfs/boxo/gateway"
12
cmds "github.com/ipfs/go-ipfs-cmds"
13
cmdsHttp "github.com/ipfs/go-ipfs-cmds/http"
14
version "github.com/ipfs/kubo"
@@ -122,14 +121,10 @@ func patchCORSVars(c *cmdsHttp.ServerConfig, addr net.Addr) {
121
c.SetAllowedOrigins(newOrigins...)
122
}
123
125
-func commandsOption(cctx oldcmds.Context, command *cmds.Command, allowGet bool) ServeOption {
124
+func commandsOption(cctx oldcmds.Context, command *cmds.Command) ServeOption {
125
return func(n *core.IpfsNode, l net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
126
cfg := cmdsHttp.NewServerConfig()
128
- cfg.AllowGet = allowGet
127
corsAllowedMethods := []string{http.MethodPost}
130
- if allowGet {
131
- corsAllowedMethods = append(corsAllowedMethods, http.MethodGet)
132
- }
128
129
cfg.SetAllowedMethods(corsAllowedMethods...)
130
cfg.APIPath = APIPath
@@ -150,13 +145,6 @@ func commandsOption(cctx oldcmds.Context, command *cmds.Command, allowGet bool)
145
cmdHandler = withAuthSecrets(authorizations, cmdHandler)
146
}
147
153
- // TODO[api-on-gw]: remove for Kubo 0.28
154
- if command == corecommands.RootRO && allowGet {
155
- cmdHandler = gateway.NewHeaders(map[string][]string{
156
- "Link": {`<https://github.com/ipfs/kubo/issues/10312>; rel="deprecation"; type="text/html"`},
157
- }).Wrap(cmdHandler)
158
- }
159
-
148
cmdHandler = otelhttp.NewHandler(cmdHandler, "corehttp.cmdsHandler")
149
mux.Handle(APIPath+"/", cmdHandler)
150
return mux, nil
@@ -211,13 +199,7 @@ func withAuthSecrets(authorizations map[string]rpcAuthScopeWithUser, next http.H
199
// CommandsOption constructs a ServerOption for hooking the commands into the
200
// HTTP server. It will NOT allow GET requests.
201
func CommandsOption(cctx oldcmds.Context) ServeOption {
214
- return commandsOption(cctx, corecommands.Root, false)
215
-}
216
-
217
-// CommandsROOption constructs a ServerOption for hooking the read-only commands
218
-// into the HTTP server. It will allow GET requests.
219
-func CommandsROOption(cctx oldcmds.Context) ServeOption {
220
- return commandsOption(cctx, corecommands.RootRO, true)
202
+ return commandsOption(cctx, corecommands.Root)
203
}
204
205
// CheckVersionOption returns a ServeOption that checks whether the client ipfs version matches. Does nothing when the user agent string does not contain `/kubo/` or `/go-ipfs/`
core/corehttp/gateway.go
+1
-1
@@ -235,7 +235,7 @@ func (o *offlineGatewayErrWrapper) GetDNSLinkRecord(ctx context.Context, s strin
235
236
var _ gateway.IPFSBackend = (*offlineGatewayErrWrapper)(nil)
237
238
-var defaultPaths = []string{"/ipfs/", "/ipns/", "/api/", "/p2p/"}
238
+var defaultPaths = []string{"/ipfs/", "/ipns/", "/p2p/"}
239
240
var subdomainGatewaySpec = &gateway.PublicGateway{
241
Paths: defaultPaths,
docs/changelogs/v0.28.md
+7
-2
@@ -7,17 +7,22 @@
7
- [Overview](#overview)
8
- [🔦 Highlights](#-highlights)
9
- [RPC client: removed deprecated DHT API](#rpc-client-removed-deprecated-dht-api)
10
+ - [Gateway: `/api/v0` is removed](#gateway-apiv0-is-removed)
11
- [📝 Changelog](#-changelog)
12
- [👨👩👧👦 Contributors](#-contributors)
13
14
### Overview
15
15
-### 🔦 Highlights
16
-
16
#### RPC client: removed deprecated DHT API
17
18
The deprecated DHT API commands in the RPC client have been removed. Instead, use the Routing API.
19
20
+#### Gateway: `/api/v0` is removed
21
+
22
+The legacy subset of the Kubo RPC that was available via the Gateway port and was deprecated is now completely removed. You can read more in <https://github.com/ipfs/kubo/issues/10312>.
23
+
24
+If you have a legacy software that relies on this behavior, and want to expose parts of `/api/v0` next to `/ipfs`, use reverse-proxy in front of Kubo to mount both Gateway and RPC on the same port. NOTE: exposing RPC to the internet comes with security risk: make sure to specify access control via [API.Authorizations](https://github.com/ipfs/kubo/blob/master/docs/config.md#apiauthorizations).
25
+
26
### 📝 Changelog
27
28
### 👨👩👧👦 Contributors
docs/gateway.md
-9
@@ -106,12 +106,3 @@ Right now only 'full DAG' implicit selector is implemented.
106
Support for user-provided IPLD selectors is tracked in https://github.com/ipfs/kubo/issues/8769.
107
108
This is a rough equivalent of `ipfs dag export`.
109
-
110
-## Deprecated Subset of RPC API
111
-
112
-For legacy reasons, some gateways may expose a small subset of RPC API under `/api/v0/`.
113
-While this read-only API exposes a read-only, "safe" subset of the normal API,
114
-it is deprecated and should not be used for greenfield projects.
115
-
116
-Where possible, leverage `/ipfs/` and `/ipns/` endpoints.
117
-along with `application/vnd.ipld.*` Content-Types instead.
test/cli/gateway_test.go
-71
@@ -14,7 +14,6 @@ import (
14
15
"github.com/ipfs/kubo/config"
16
"github.com/ipfs/kubo/test/cli/harness"
17
- . "github.com/ipfs/kubo/test/cli/testutils"
17
"github.com/libp2p/go-libp2p/core/peer"
18
"github.com/multiformats/go-multiaddr"
19
manet "github.com/multiformats/go-multiaddr/net"
@@ -344,76 +343,6 @@ func TestGateway(t *testing.T) {
343
})
344
})
345
347
- t.Run("readonly API", func(t *testing.T) {
348
- t.Parallel()
349
-
350
- client := node.GatewayClient()
351
-
352
- fileContents := "12345"
353
- h.WriteFile("readonly/dir/test", fileContents)
354
- cids := node.IPFS("add", "-r", "-q", filepath.Join(h.Dir, "readonly/dir")).Stdout.Lines()
355
-
356
- rootCID := cids[len(cids)-1]
357
- client.TemplateData = map[string]string{"RootCID": rootCID}
358
-
359
- t.Run("Get IPFS directory file through readonly API succeeds", func(t *testing.T) {
360
- t.Parallel()
361
- resp := client.Get("/api/v0/cat?arg={{.RootCID}}/test")
362
- assert.Equal(t, 200, resp.StatusCode)
363
- assert.Equal(t, fileContents, resp.Body)
364
- })
365
-
366
- t.Run("refs IPFS directory file through readonly API succeeds", func(t *testing.T) {
367
- t.Parallel()
368
- resp := client.Get("/api/v0/refs?arg={{.RootCID}}/test")
369
- assert.Equal(t, 200, resp.StatusCode)
370
- })
371
-
372
- t.Run("test gateway API is sanitized", func(t *testing.T) {
373
- t.Parallel()
374
- for _, cmd := range []string{
375
- "add",
376
- "block/put",
377
- "bootstrap",
378
- "config",
379
- "dag/put",
380
- "dag/import",
381
- "dht",
382
- "diag",
383
- "id",
384
- "mount",
385
- "name/publish",
386
- "object/put",
387
- "object/new",
388
- "object/patch",
389
- "pin",
390
- "ping",
391
- "repo",
392
- "stats",
393
- "swarm",
394
- "file",
395
- "update",
396
- "bitswap",
397
- } {
398
- t.Run(cmd, func(t *testing.T) {
399
- cmd := cmd
400
- t.Parallel()
401
- assert.Equal(t, 404, client.Get("/api/v0/"+cmd).StatusCode)
402
- })
403
- }
404
- })
405
- })
406
-
407
- t.Run("refs/local", func(t *testing.T) {
408
- t.Parallel()
409
- gatewayAddr := URLStrToMultiaddr(node.GatewayURL())
410
- res := node.RunIPFS("--api", gatewayAddr.String(), "refs", "local")
411
- assert.Contains(t,
412
- res.Stderr.Trimmed(),
413
- `Error: invalid path "local":`,
414
- )
415
- })
416
-
346
t.Run("raw leaves node", func(t *testing.T) {
347
t.Parallel()
348
contents := "This is RAW!"
test/sharness/t0002-docker-image.sh
+1
-1
@@ -50,7 +50,7 @@ test_expect_success "docker image runs" '
50
'
51
52
test_expect_success "docker container gateway is up" '
53
- pollEndpoint -host=/ip4/127.0.0.1/tcp/8080 -http-url http://localhost:8080/api/v0/version -v -tries 30 -tout 1s
53
+ pollEndpoint -host=/ip4/127.0.0.1/tcp/8080 -http-url http://localhost:8080/ipfs/bafkqaddimvwgy3zao5xxe3debi -v -tries 30 -tout 1s
54
'
55
56
test_expect_success "docker container API is up" '
test/sharness/t0112-gateway-cors.sh
-64
@@ -127,70 +127,6 @@ test_expect_success "Access-Control-Allow-Origin replaces the implicit list" '
127
test_should_contain "< Access-Control-Allow-Origin: localhost" curl_output
128
'
129
130
-# Read-Only /api/v0 RPC API (legacy subset, exposed on the Gateway Port)
131
-# TODO: we want to remove it, but for now this guards the legacy behavior to not go any further
132
-
133
-# also check this, as due to legacy reasons Kubo exposes small subset of /api/v0 on GW port
134
-test_expect_success "Assert the default API.HTTPHeaders config is empty" '
135
- echo "{}" > expected &&
136
- ipfs config --json API.HTTPHeaders > actual &&
137
- test_cmp expected actual
138
-'
139
-
140
-# HTTP GET Request
141
-test_expect_success "Default CORS GET to {gw}/api/v0" '
142
- curl -svX GET -H "Origin: https://example.com" "http://127.0.0.1:$GWAY_PORT/api/v0/cat?arg=$thash" >/dev/null 2>curl_output
143
-'
144
-# HTTP 403 is returned because Kubo has additional protections on top of regular CORS,
145
-# namely it only allows browser requests with localhost Origin header.
146
-test_expect_success "Default CORS GET response from {gw}/api/v0 is 403 Forbidden and has regular CORS headers" '
147
- test_should_contain "HTTP/1.1 403 Forbidden" curl_output &&
148
- test_should_contain "< Access-Control-" curl_output
149
-'
150
-
151
-# HTTP OPTIONS Request
152
-test_expect_success "Default OPTIONS to {gw}/api/v0" '
153
- curl -svX OPTIONS -H "Origin: https://example.com" "http://127.0.0.1:$GWAY_PORT/api/v0/cat?arg=$thash" 2>curl_output
154
-'
155
-# OPTIONS Response from the API should NOT contain CORS headers
156
-test_expect_success "OPTIONS response from {gw}/api/v0 has CORS headers" '
157
- test_should_contain "< Access-Control-" curl_output
158
-'
159
-
160
-test_kill_ipfs_daemon
161
-
162
-# TODO: /api/v0 with CORS headers set in API.HTTPHeaders does not really work,
163
-# as not all headers are correctly set. Below is only a basic regression test that documents
164
-# current state. Fixing CORS on /api/v0 (RPC and Gateway port) is tracked in https://github.com/ipfs/kubo/issues/7667
165
-
166
-test_expect_success "Manually set API.HTTPHeaders config to be as relaxed as Gateway.HTTPHeaders" "
167
- ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '[\"https://example.com\"]'
168
-"
169
-# TODO: ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '[\"GET\",\"POST\"]' &&
170
-# TODO: ipfs config --json API.HTTPHeaders.Access-Control-Allow-Headers '[\"X-Requested-With\", \"Range\", \"User-Agent\"]'
171
-
172
-test_launch_ipfs_daemon
173
-
174
-# HTTP GET Request
175
-test_expect_success "Manually relaxed CORS GET to {gw}/api/v0" '
176
- curl -svX GET -H "Origin: https://example.com" "http://127.0.0.1:$GWAY_PORT/api/v0/cat?arg=$thash" >/dev/null 2>curl_output
177
-'
178
-test_expect_success "Manually relaxed CORS GET response from {gw}/api/v0 is the same as Gateway" '
179
- test_should_contain "HTTP/1.1 200 OK" curl_output &&
180
- test_should_contain "< Access-Control-Allow-Origin: https://example.com" curl_output
181
-'
182
-# TODO: test_should_contain "< Access-Control-Allow-Methods: GET" curl_output
183
-
184
-# HTTP OPTIONS Request
185
-test_expect_success "Manually relaxed OPTIONS to {gw}/api/v0" '
186
- curl -svX OPTIONS -H "Origin: https://example.com" "http://127.0.0.1:$GWAY_PORT/api/v0/cat?arg=$thash" 2>curl_output
187
-'
188
-# OPTIONS Response from the API should NOT contain CORS headers
189
-test_expect_success "Manually relaxed OPTIONS response from {gw}/api/v0 is the same as Gateway" '
190
- test_should_contain "< Access-Control-Allow-Origin: https://example.com" curl_output
191
-'
192
-# TODO: test_should_contain "< Access-Control-Allow-Methods: GET" curl_output
193
-
130
test_kill_ipfs_daemon
131
132
test_done
test/sharness/t0114-gateway-subdomains.sh
-75
@@ -203,25 +203,6 @@ test_localhost_gateway_response_should_contain \
203
204
# end Kubo specific end-to-end test
205
206
-# API on localhost subdomain gateway
207
-
208
-# /api/v0 present on the root hostname
209
-test_localhost_gateway_response_should_contain \
210
- "request for localhost/api" \
211
- "http://localhost:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
212
- "Ref"
213
-
214
-# /api/v0 not mounted on content root subdomains
215
-test_localhost_gateway_response_should_contain \
216
- "request for {cid}.ipfs.localhost/api returns data if present on the content root" \
217
- "http://${DIR_CID}.ipfs.localhost:$GWAY_PORT/api/file.txt" \
218
- "I am a txt file"
219
-
220
-test_localhost_gateway_response_should_contain \
221
- "request for {cid}.ipfs.localhost/api/v0/refs returns 404" \
222
- "http://${DIR_CID}.ipfs.localhost:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
223
- "404 Not Found"
224
-
206
## ============================================================================
207
## Test subdomain-based requests to a local gateway with default config
208
## (origin per content root at http://*.localhost)
@@ -308,14 +289,6 @@ test_localhost_gateway_response_should_contain \
289
"http://$DNSLINK_FQDN.ipns.localhost:$GWAY_PORT" \
290
"$CID_VAL"
291
311
-# api.localhost/api
312
-
313
-# Note: we use DIR_CID so refs -r returns some CIDs for child nodes
314
-test_localhost_gateway_response_should_contain \
315
- "request for api.localhost returns API response" \
316
- "http://api.localhost:$GWAY_PORT/api/v0/refs?arg=$DIR_CID&r=true" \
317
- "Ref"
318
-
292
## ============================================================================
293
## Test DNSLink inlining on HTTP gateways
294
## ============================================================================
@@ -518,54 +491,6 @@ test_hostname_gateway_response_should_contain \
491
"http://127.0.0.1:$GWAY_PORT" \
492
"Location: http://${ED25519_IPNS_IDv1}.ipns.example.com/"
493
521
-# API on subdomain gateway example.com
522
-# ============================================================================
523
-
524
-# present at the root domain
525
-test_hostname_gateway_response_should_contain \
526
- "request for example.com/api/v0/refs returns expected payload when /api is on Paths whitelist" \
527
- "example.com" \
528
- "http://127.0.0.1:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
529
- "Ref"
530
-
531
-# not mounted on content root subdomains
532
-test_hostname_gateway_response_should_contain \
533
- "request for {cid}.ipfs.example.com/api returns data if present on the content root" \
534
- "$DIR_CID.ipfs.example.com" \
535
- "http://127.0.0.1:$GWAY_PORT/api/file.txt" \
536
- "I am a txt file"
537
-
538
-test_hostname_gateway_response_should_contain \
539
- "request for {cid}.ipfs.example.com/api/v0/refs returns 404" \
540
- "$CIDv1.ipfs.example.com" \
541
- "http://127.0.0.1:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
542
- "404 Not Found"
543
-
544
-# disable /api on example.com
545
-ipfs config --json Gateway.PublicGateways '{
546
- "example.com": {
547
- "UseSubdomains": true,
548
- "Paths": ["/ipfs", "/ipns"]
549
- }
550
-}' || exit 1
551
-# restart daemon to apply config changes
552
-test_kill_ipfs_daemon
553
-test_launch_ipfs_daemon_without_network
554
-
555
-# not mounted at the root domain
556
-test_hostname_gateway_response_should_contain \
557
- "request for example.com/api/v0/refs returns 404 if /api not on Paths whitelist" \
558
- "example.com" \
559
- "http://127.0.0.1:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
560
- "404 Not Found"
561
-
562
-# not mounted on content root subdomains
563
-test_hostname_gateway_response_should_contain \
564
- "request for {cid}.ipfs.example.com/api returns data if present on the content root" \
565
- "$DIR_CID.ipfs.example.com" \
566
- "http://127.0.0.1:$GWAY_PORT/api/file.txt" \
567
- "I am a txt file"
568
-
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)