diff --git a/cmd/ipfs/kubo/daemon.go b/cmd/ipfs/kubo/daemon.go index ae755648e..82f240897 100644 --- a/cmd/ipfs/kubo/daemon.go +++ b/cmd/ipfs/kubo/daemon.go @@ -850,6 +850,7 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e corehttp.GatewayOption("/ipfs", "/ipns"), corehttp.VersionOption(), corehttp.CheckVersionOption(), + // TODO[api-on-gw]: remove for 0.28.0: https://github.com/ipfs/kubo/issues/10312 corehttp.CommandsROOption(cmdctx), } diff --git a/core/corehttp/commands.go b/core/corehttp/commands.go index 314822ff2..4feef3359 100644 --- a/core/corehttp/commands.go +++ b/core/corehttp/commands.go @@ -9,6 +9,7 @@ import ( "strconv" "strings" + "github.com/ipfs/boxo/gateway" cmds "github.com/ipfs/go-ipfs-cmds" cmdsHttp "github.com/ipfs/go-ipfs-cmds/http" version "github.com/ipfs/kubo" @@ -149,6 +150,13 @@ func commandsOption(cctx oldcmds.Context, command *cmds.Command, allowGet bool) cmdHandler = withAuthSecrets(authorizations, cmdHandler) } + // TODO[api-on-gw]: remove for Kubo 0.28 + if command == corecommands.RootRO && allowGet { + cmdHandler = gateway.NewHeaders(map[string][]string{ + "Link": {`; rel="deprecation"; type="text/html"`}, + }).Wrap(cmdHandler) + } + cmdHandler = otelhttp.NewHandler(cmdHandler, "corehttp.cmdsHandler") mux.Handle(APIPath+"/", cmdHandler) return mux, nil diff --git a/core/corehttp/gateway.go b/core/corehttp/gateway.go index a15765176..c08d71806 100644 --- a/core/corehttp/gateway.go +++ b/core/corehttp/gateway.go @@ -28,7 +28,7 @@ import ( func GatewayOption(paths ...string) ServeOption { return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { - config, err := getGatewayConfig(n) + config, headers, err := getGatewayConfig(n) if err != nil { return nil, err } @@ -39,6 +39,7 @@ func GatewayOption(paths ...string) ServeOption { } handler := gateway.NewHandler(config, backend) + handler = gateway.NewHeaders(headers).ApplyCors().Wrap(handler) handler = otelhttp.NewHandler(handler, "Gateway") for _, p := range paths { @@ -51,7 +52,7 @@ func GatewayOption(paths ...string) ServeOption { func HostnameOption() ServeOption { return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { - config, err := getGatewayConfig(n) + config, headers, err := getGatewayConfig(n) if err != nil { return nil, err } @@ -65,6 +66,7 @@ func HostnameOption() ServeOption { var handler http.Handler handler = gateway.NewHostnameHandler(config, backend, childMux) + handler = gateway.NewHeaders(headers).ApplyCors().Wrap(handler) handler = otelhttp.NewHandler(handler, "HostnameGateway") mux.Handle("/", handler) @@ -240,22 +242,14 @@ var defaultKnownGateways = map[string]*gateway.PublicGateway{ "localhost": subdomainGatewaySpec, } -func getGatewayConfig(n *core.IpfsNode) (gateway.Config, error) { +func getGatewayConfig(n *core.IpfsNode) (gateway.Config, map[string][]string, error) { cfg, err := n.Repo.Config() if err != nil { - return gateway.Config{}, err + return gateway.Config{}, nil, err } - // Parse configuration headers and add the default Access Control Headers. - headers := make(map[string][]string, len(cfg.Gateway.HTTPHeaders)) - for h, v := range cfg.Gateway.HTTPHeaders { - headers[http.CanonicalHeaderKey(h)] = v - } - gateway.AddAccessControlHeaders(headers) - // Initialize gateway configuration, with empty PublicGateways, handled after. gwCfg := gateway.Config{ - Headers: headers, DeserializedResponses: cfg.Gateway.DeserializedResponses.WithDefault(config.DefaultDeserializedResponses), DisableHTMLErrors: cfg.Gateway.DisableHTMLErrors.WithDefault(config.DefaultDisableHTMLErrors), NoDNSLink: cfg.Gateway.NoDNSLink, @@ -285,5 +279,5 @@ func getGatewayConfig(n *core.IpfsNode) (gateway.Config, error) { } } - return gwCfg, nil + return gwCfg, cfg.Gateway.HTTPHeaders, nil } diff --git a/core/corehttp/gateway_test.go b/core/corehttp/gateway_test.go index c2e0073d9..df307ef73 100644 --- a/core/corehttp/gateway_test.go +++ b/core/corehttp/gateway_test.go @@ -206,7 +206,7 @@ func TestDeserializedResponsesInheritance(t *testing.T) { n, err := core.NewNode(context.Background(), &core.BuildCfg{Repo: r}) assert.NoError(t, err) - gwCfg, err := getGatewayConfig(n) + gwCfg, _, err := getGatewayConfig(n) assert.NoError(t, err) assert.Contains(t, gwCfg.PublicGateways, "example.com") diff --git a/core/corehttp/routing.go b/core/corehttp/routing.go index 88d9de886..9a2591d32 100644 --- a/core/corehttp/routing.go +++ b/core/corehttp/routing.go @@ -6,6 +6,7 @@ import ( "net/http" "time" + "github.com/ipfs/boxo/gateway" "github.com/ipfs/boxo/ipns" "github.com/ipfs/boxo/routing/http/server" "github.com/ipfs/boxo/routing/http/types" @@ -18,7 +19,13 @@ import ( func RoutingOption() ServeOption { return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { + _, headers, err := getGatewayConfig(n) + if err != nil { + return nil, err + } + handler := server.Handler(&contentRouter{n}) + handler = gateway.NewHeaders(headers).ApplyCors().Wrap(handler) mux.Handle("/routing/v1/", handler) return mux, nil } diff --git a/docs/changelogs/v0.27.md b/docs/changelogs/v0.27.md index 5ca025612..1d7c5b101 100644 --- a/docs/changelogs/v0.27.md +++ b/docs/changelogs/v0.27.md @@ -6,6 +6,7 @@ - [Overview](#overview) - [๐Ÿ”ฆ Highlights](#-highlights) + - [Gateway: support for `/api/v0` is deprecated](#gateway-support-for-apiv0-is-deprecated) - [๐Ÿ“ Changelog](#-changelog) - [๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Contributors](#-contributors) @@ -13,6 +14,12 @@ ### ๐Ÿ”ฆ Highlights +#### Gateway: support for `/api/v0` is deprecated + +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 . + +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). + ### ๐Ÿ“ Changelog ### ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Contributors diff --git a/docs/config.md b/docs/config.md index 6a336a66c..d6c1567f1 100644 --- a/docs/config.md +++ b/docs/config.md @@ -716,6 +716,8 @@ Toggle and configure experimental features of Kubo. Experimental features are li Options for the HTTP gateway. +**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. + ### `Gateway.NoFetch` When set to true, the gateway will only serve content already in the local repo @@ -819,14 +821,14 @@ Example: "Gateway": { "PublicGateways": { "example.com": { - "Paths": ["/ipfs", "/ipns"], + "Paths": ["/ipfs"], } } } } ``` -Above enables `http://example.com/ipfs/*` and `http://example.com/ipns/*` but not `http://example.com/api/*` +Above enables `http://example.com/ipfs/*` but not `http://example.com/ipns/*` Default: `[]` @@ -851,7 +853,6 @@ between content roots. } ``` - **Backward-compatible:** requests for content paths such as `http://{hostname}/ipfs/{cid}` produce redirect to `http://{cid}.ipfs.{hostname}` - - **API:** if `/api` is on the `Paths` whitelist, `http://{hostname}/api/{cmd}` produces redirect to `http://api.{hostname}/api/{cmd}` - `false` - enables [path gateway](https://docs.ipfs.tech/how-to/address-ipfs-on-web/#path-gateway) at `http://{hostname}/*` - Example: @@ -860,7 +861,7 @@ between content roots. "PublicGateways": { "ipfs.io": { "UseSubdomains": false, - "Paths": ["/ipfs", "/ipns", "/api"] + "Paths": ["/ipfs", "/ipns"] } } } @@ -969,7 +970,7 @@ Below is a list of the most common public gateway setups. $ ipfs config --json Gateway.PublicGateways '{ "ipfs.io": { "UseSubdomains": false, - "Paths": ["/ipfs", "/ipns", "/api"] + "Paths": ["/ipfs", "/ipns"] } }' ``` diff --git a/docs/examples/kubo-as-a-library/go.mod b/docs/examples/kubo-as-a-library/go.mod index 27ddf9a47..fefedc845 100644 --- a/docs/examples/kubo-as-a-library/go.mod +++ b/docs/examples/kubo-as-a-library/go.mod @@ -7,7 +7,7 @@ go 1.20 replace github.com/ipfs/kubo => ./../../.. require ( - github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5 + github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c github.com/ipfs/kubo v0.0.0-00010101000000-000000000000 github.com/libp2p/go-libp2p v0.32.2 github.com/multiformats/go-multiaddr v0.12.1 diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index 18a52c6f2..103cd2d79 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -260,8 +260,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c h1:7Uy github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c/go.mod h1:6EekK/jo+TynwSE/ZOiOJd4eEvRXoavEC3vquKtv4yI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5 h1:qGPYOK8flU2YzHGq9Cb2Yeo0jjOwompAOzxOv3VSGx8= -github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= +github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c h1:A18UHDQ4V2Ai6/YsrH7kfGjA1r5SrwjQR1Lqiq68YQU= +github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= github.com/ipfs/go-block-format v0.0.3/go.mod h1:4LmD4ZUw0mhO+JSKdpWwrzATiEfM7WWgQ8H5l6P8MVk= diff --git a/go.mod b/go.mod index 1c19ce5f5..d2da7eb60 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/ipfs-shipyard/nopfs v0.0.12 github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c - github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5 + github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c github.com/ipfs/go-block-format v0.2.0 github.com/ipfs/go-cid v0.4.1 github.com/ipfs/go-cidutil v0.1.0 diff --git a/go.sum b/go.sum index 390b51ec8..91f63eedb 100644 --- a/go.sum +++ b/go.sum @@ -325,8 +325,8 @@ github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c h1:7Uy github.com/ipfs-shipyard/nopfs/ipfs v0.13.2-0.20231027223058-cde3b5ba964c/go.mod h1:6EekK/jo+TynwSE/ZOiOJd4eEvRXoavEC3vquKtv4yI= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5 h1:qGPYOK8flU2YzHGq9Cb2Yeo0jjOwompAOzxOv3VSGx8= -github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= +github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c h1:A18UHDQ4V2Ai6/YsrH7kfGjA1r5SrwjQR1Lqiq68YQU= +github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= github.com/ipfs/go-bitfield v1.1.0 h1:fh7FIo8bSwaJEh6DdTWbCeZ1eqOaOkKFI74SCnsWbGA= github.com/ipfs/go-bitfield v1.1.0/go.mod h1:paqf1wjq/D2BBmzfTVFlJQ9IlFOZpg422HL0HqsGWHU= github.com/ipfs/go-bitswap v0.11.0 h1:j1WVvhDX1yhG32NTC9xfxnqycqYIlhzEzLXG/cU1HyQ= diff --git a/test/dependencies/go.mod b/test/dependencies/go.mod index 359552807..3d8f3654a 100644 --- a/test/dependencies/go.mod +++ b/test/dependencies/go.mod @@ -103,7 +103,7 @@ require ( github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/bbloom v0.0.4 // indirect - github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5 // indirect + github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c // indirect github.com/ipfs/go-block-format v0.2.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-datastore v0.6.0 // indirect diff --git a/test/dependencies/go.sum b/test/dependencies/go.sum index dd39149f9..0917aef21 100644 --- a/test/dependencies/go.sum +++ b/test/dependencies/go.sum @@ -342,8 +342,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs= github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0= -github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5 h1:qGPYOK8flU2YzHGq9Cb2Yeo0jjOwompAOzxOv3VSGx8= -github.com/ipfs/boxo v0.17.1-0.20240112124340-bcb321c857c5/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= +github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c h1:A18UHDQ4V2Ai6/YsrH7kfGjA1r5SrwjQR1Lqiq68YQU= +github.com/ipfs/boxo v0.17.1-0.20240124092521-3d57bce7998c/go.mod h1:pIZgTWdm3k3pLF9Uq6MB8JEcW07UDwNJjlXW1HELW80= github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s= diff --git a/test/sharness/t0112-gateway-cors.sh b/test/sharness/t0112-gateway-cors.sh index e4fb57122..37027c188 100755 --- a/test/sharness/t0112-gateway-cors.sh +++ b/test/sharness/t0112-gateway-cors.sh @@ -141,9 +141,11 @@ test_expect_success "Assert the default API.HTTPHeaders config is empty" ' test_expect_success "Default CORS GET to {gw}/api/v0" ' 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 ' -test_expect_success "Default CORS GET response from {gw}/api/v0 is 403 Forbidden and has no CORS headers" ' +# HTTP 403 is returned because Kubo has additional protections on top of regular CORS, +# namely it only allows browser requests with localhost Origin header. +test_expect_success "Default CORS GET response from {gw}/api/v0 is 403 Forbidden and has regular CORS headers" ' test_should_contain "HTTP/1.1 403 Forbidden" curl_output && - test_should_not_contain "< Access-Control-" curl_output + test_should_contain "< Access-Control-" curl_output ' # HTTP OPTIONS Request @@ -151,8 +153,8 @@ test_expect_success "Default OPTIONS to {gw}/api/v0" ' curl -svX OPTIONS -H "Origin: https://example.com" "http://127.0.0.1:$GWAY_PORT/api/v0/cat?arg=$thash" 2>curl_output ' # OPTIONS Response from the API should NOT contain CORS headers -test_expect_success "OPTIONS response from {gw}/api/v0 has no CORS header" ' - test_should_not_contain "< Access-Control-" curl_output +test_expect_success "OPTIONS response from {gw}/api/v0 has CORS headers" ' + test_should_contain "< Access-Control-" curl_output ' test_kill_ipfs_daemon