core/corehttp: wrap gateway with headers, deprecate gateway /api/v0
Henrique Dias committed
Jan 24, 2024 at 10:33 UTC
e166af975613407739d2c8f65b5b1430fa90475b
14 files changed
+52
-32
cmd/ipfs/kubo/daemon.go
+1
@@ -850,6 +850,7 @@ 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),
855
}
856
core/corehttp/commands.go
+8
@@ -9,6 +9,7 @@ import (
9
"strconv"
10
"strings"
11
12
+ "github.com/ipfs/boxo/gateway"
13
cmds "github.com/ipfs/go-ipfs-cmds"
14
cmdsHttp "github.com/ipfs/go-ipfs-cmds/http"
15
version "github.com/ipfs/kubo"
@@ -149,6 +150,13 @@ func commandsOption(cctx oldcmds.Context, command *cmds.Command, allowGet bool)
150
cmdHandler = withAuthSecrets(authorizations, cmdHandler)
151
}
152
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
+
160
cmdHandler = otelhttp.NewHandler(cmdHandler, "corehttp.cmdsHandler")
161
mux.Handle(APIPath+"/", cmdHandler)
162
return mux, nil
core/corehttp/gateway.go
+7
-13
@@ -28,7 +28,7 @@ import (
28
29
func GatewayOption(paths ...string) ServeOption {
30
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
31
- config, err := getGatewayConfig(n)
31
+ config, headers, err := getGatewayConfig(n)
32
if err != nil {
33
return nil, err
34
}
@@ -39,6 +39,7 @@ func GatewayOption(paths ...string) ServeOption {
39
}
40
41
handler := gateway.NewHandler(config, backend)
42
+ handler = gateway.NewHeaders(headers).ApplyCors().Wrap(handler)
43
handler = otelhttp.NewHandler(handler, "Gateway")
44
45
for _, p := range paths {
@@ -51,7 +52,7 @@ func GatewayOption(paths ...string) ServeOption {
52
53
func HostnameOption() ServeOption {
54
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
54
- config, err := getGatewayConfig(n)
55
+ config, headers, err := getGatewayConfig(n)
56
if err != nil {
57
return nil, err
58
}
@@ -65,6 +66,7 @@ func HostnameOption() ServeOption {
66
67
var handler http.Handler
68
handler = gateway.NewHostnameHandler(config, backend, childMux)
69
+ handler = gateway.NewHeaders(headers).ApplyCors().Wrap(handler)
70
handler = otelhttp.NewHandler(handler, "HostnameGateway")
71
72
mux.Handle("/", handler)
@@ -240,22 +242,14 @@ var defaultKnownGateways = map[string]*gateway.PublicGateway{
242
"localhost": subdomainGatewaySpec,
243
}
244
243
-func getGatewayConfig(n *core.IpfsNode) (gateway.Config, error) {
245
+func getGatewayConfig(n *core.IpfsNode) (gateway.Config, map[string][]string, error) {
246
cfg, err := n.Repo.Config()
247
if err != nil {
246
- return gateway.Config{}, err
248
+ return gateway.Config{}, nil, err
249
}
250
249
- // Parse configuration headers and add the default Access Control Headers.
250
- headers := make(map[string][]string, len(cfg.Gateway.HTTPHeaders))
251
- for h, v := range cfg.Gateway.HTTPHeaders {
252
- headers[http.CanonicalHeaderKey(h)] = v
253
- }
254
- gateway.AddAccessControlHeaders(headers)
255
-
251
// Initialize gateway configuration, with empty PublicGateways, handled after.
252
gwCfg := gateway.Config{
258
- Headers: headers,
253
DeserializedResponses: cfg.Gateway.DeserializedResponses.WithDefault(config.DefaultDeserializedResponses),
254
DisableHTMLErrors: cfg.Gateway.DisableHTMLErrors.WithDefault(config.DefaultDisableHTMLErrors),
255
NoDNSLink: cfg.Gateway.NoDNSLink,
@@ -285,5 +279,5 @@ func getGatewayConfig(n *core.IpfsNode) (gateway.Config, error) {
279
}
280
}
281
288
- return gwCfg, nil
282
+ return gwCfg, cfg.Gateway.HTTPHeaders, nil
283
}
core/corehttp/gateway_test.go
+1
-1
@@ -206,7 +206,7 @@ func TestDeserializedResponsesInheritance(t *testing.T) {
206
n, err := core.NewNode(context.Background(), &core.BuildCfg{Repo: r})
207
assert.NoError(t, err)
208
209
- gwCfg, err := getGatewayConfig(n)
209
+ gwCfg, _, err := getGatewayConfig(n)
210
assert.NoError(t, err)
211
212
assert.Contains(t, gwCfg.PublicGateways, "example.com")
core/corehttp/routing.go
+7
@@ -6,6 +6,7 @@ import (
6
"net/http"
7
"time"
8
9
+ "github.com/ipfs/boxo/gateway"
10
"github.com/ipfs/boxo/ipns"
11
"github.com/ipfs/boxo/routing/http/server"
12
"github.com/ipfs/boxo/routing/http/types"
@@ -18,7 +19,13 @@ import (
19
20
func RoutingOption() ServeOption {
21
return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
22
+ _, headers, err := getGatewayConfig(n)
23
+ if err != nil {
24
+ return nil, err
25
+ }
26
+
27
handler := server.Handler(&contentRouter{n})
28
+ handler = gateway.NewHeaders(headers).ApplyCors().Wrap(handler)
29
mux.Handle("/routing/v1/", handler)
30
return mux, nil
31
}
docs/changelogs/v0.27.md
+7
@@ -6,6 +6,7 @@
6
7
- [Overview](#overview)
8
- [🔦 Highlights](#-highlights)
9
+ - [Gateway: support for `/api/v0` is deprecated](#gateway-support-for-apiv0-is-deprecated)
10
- [📝 Changelog](#-changelog)
11
- [👨👩👧👦 Contributors](#-contributors)
12
@@ -13,6 +14,12 @@
14
15
### 🔦 Highlights
16
17
+#### Gateway: support for `/api/v0` is deprecated
18
+
19
+Support for exposing the legacy subset of Kubo RPC via the Gateway port is deprecated and should not be used. It will be removed in the next version. You can read more in <https://github.com/ipfs/kubo/issues/10312>.
20
+
21
+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).
22
+
23
### 📝 Changelog
24
25
### 👨👩👧👦 Contributors
docs/config.md
+6
-5
@@ -716,6 +716,8 @@ Toggle and configure experimental features of Kubo. Experimental features are li
716
717
Options for the HTTP gateway.
718
719
+**NOTE:** support for `/api/v0` under the gateway path is now deprecated. It will be removed in future versions: https://github.com/ipfs/kubo/issues/10312.
720
+
721
### `Gateway.NoFetch`
722
723
When set to true, the gateway will only serve content already in the local repo
@@ -819,14 +821,14 @@ Example:
821
"Gateway": {
822
"PublicGateways": {
823
"example.com": {
822
- "Paths": ["/ipfs", "/ipns"],
824
+ "Paths": ["/ipfs"],
825
}
826
}
827
}
828
}
829
```
830
829
-Above enables `http://example.com/ipfs/*` and `http://example.com/ipns/*` but not `http://example.com/api/*`
831
+Above enables `http://example.com/ipfs/*` but not `http://example.com/ipns/*`
832
833
Default: `[]`
834
@@ -851,7 +853,6 @@ between content roots.
853
}
854
```
855
- **Backward-compatible:** requests for content paths such as `http://{hostname}/ipfs/{cid}` produce redirect to `http://{cid}.ipfs.{hostname}`
854
- - **API:** if `/api` is on the `Paths` whitelist, `http://{hostname}/api/{cmd}` produces redirect to `http://api.{hostname}/api/{cmd}`
856
857
- `false` - enables [path gateway](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#path-gateway) at `http://{hostname}/*`
858
- Example:
@@ -860,7 +861,7 @@ between content roots.
861
"PublicGateways": {
862
"ipfs.io": {
863
"UseSubdomains": false,
863
- "Paths": ["/ipfs", "/ipns", "/api"]
864
+ "Paths": ["/ipfs", "/ipns"]
865
}
866
}
867
}
@@ -969,7 +970,7 @@ Below is a list of the most common public gateway setups.
970
$ ipfs config --json Gateway.PublicGateways '{
971
"ipfs.io": {
972
"UseSubdomains": false,
972
- "Paths": ["/ipfs", "/ipns", "/api"]
973
+ "Paths": ["/ipfs", "/ipns"]
974
}
975
}'
976
```
docs/examples/kubo-as-a-library/go.mod
+1
-1
@@ -7,7 +7,7 @@ go 1.20
7
replace github.com/ipfs/kubo => ./../../..
8
9
require (
10
- github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5
10
+ github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c
11
github.com/ipfs/kubo v0.0.0-00010101000000-000000000000
12
github.com/libp2p/go-libp2p v0.32.2
13
github.com/multiformats/go-multiaddr v0.12.1
docs/examples/kubo-as-a-library/go.sum
+2
-2
@@ -260,8 +260,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c h1:7Uy
260
github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c/go.mod h1:6EekK/jo+TynwSE/ZOiOJd4eEvRXoavEC3vquKtv4yI=
261
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
262
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
263
-github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5 h1:qGPYOK8flU2YzHGq9Cb2Yeo0jjOwompAOzxOv3VSGx8=
264
-github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80=
263
+github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c h1:A18UHDQ4V2Ai6/YsrH7kfGjA1r5SrwjQR1Lqiq68YQU=
264
+github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80=
265
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
266
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
267
github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk=
go.mod
+1
-1
@@ -17,7 +17,7 @@ require (
17
github.com/hashicorp/go-multierror v1.1.1
18
github.com/ipfs-shipyard/nopfs v0.0.12
19
github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c
20
- github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5
20
+ github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c
21
github.com/ipfs/go-block-format v0.2.0
22
github.com/ipfs/go-cid v0.4.1
23
github.com/ipfs/go-cidutil v0.1.0
go.sum
+2
-2
@@ -325,8 +325,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c h1:7Uy
325
github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c/go.mod h1:6EekK/jo+TynwSE/ZOiOJd4eEvRXoavEC3vquKtv4yI=
326
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
327
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
328
-github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5 h1:qGPYOK8flU2YzHGq9Cb2Yeo0jjOwompAOzxOv3VSGx8=
329
-github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80=
328
+github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c h1:A18UHDQ4V2Ai6/YsrH7kfGjA1r5SrwjQR1Lqiq68YQU=
329
+github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80=
330
github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA=
331
github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU=
332
github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ=
test/dependencies/go.mod
+1
-1
@@ -103,7 +103,7 @@ require (
103
github.com/hexops/gotextdiff v1.0.3 // indirect
104
github.com/inconshreveable/mousetrap v1.1.0 // indirect
105
github.com/ipfs/bbloom v0.0.4 // indirect
106
- github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5 // indirect
106
+ github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c // indirect
107
github.com/ipfs/go-block-format v0.2.0 // indirect
108
github.com/ipfs/go-cid v0.4.1 // indirect
109
github.com/ipfs/go-datastore v0.6.0 // indirect
test/dependencies/go.sum
+2
-2
@@ -342,8 +342,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
342
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
343
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
344
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
345
-github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5 h1:qGPYOK8flU2YzHGq9Cb2Yeo0jjOwompAOzxOv3VSGx8=
346
-github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80=
345
+github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c h1:A18UHDQ4V2Ai6/YsrH7kfGjA1r5SrwjQR1Lqiq68YQU=
346
+github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80=
347
github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs=
348
github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM=
349
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
test/sharness/t0112-gateway-cors.sh
+6
-4
@@ -141,9 +141,11 @@ test_expect_success "Assert the default API.HTTPHeaders config is empty" '
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
-test_expect_success "Default CORS GET response from {gw}/api/v0 is 403 Forbidden and has no CORS headers" '
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 &&
146
- test_should_not_contain "< Access-Control-" curl_output
148
+ test_should_contain "< Access-Control-" curl_output
149
'
150
151
# HTTP OPTIONS Request
@@ -151,8 +153,8 @@ 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
154
-test_expect_success "OPTIONS response from {gw}/api/v0 has no CORS header" '
155
- test_should_not_contain "< Access-Control-" curl_output
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