@cryptotaxi247 / kubo / commits / e22f47ae4

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)