1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-05-17 23:16:11 +08:00

core/corehttp!: remove /api/v0 from gateway port

This commit is contained in:
Henrique Dias
2024-03-05 09:21:13 +01:00
committed by GitHub
parent d77a9e69f7
commit e22f47ae4b
13 changed files with 12 additions and 367 deletions

View File

@ -850,8 +850,6 @@ 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),
}
if cfg.Experimental.P2pHttpProxy {

View File

@ -9,7 +9,7 @@ const (
type GatewaySpec struct {
// Paths is explicit list of path prefixes that should be handled by
// this gateway. Example: `["/ipfs", "/ipns", "/api"]`
// this gateway. Example: `["/ipfs", "/ipns"]`
Paths []string
// UseSubdomains indicates whether or not this gateway uses subdomains

View File

@ -15,63 +15,6 @@ func collectPaths(prefix string, cmd *cmds.Command, out map[string]struct{}) {
}
}
func TestROCommands(t *testing.T) {
list := []string{
"/block",
"/block/get",
"/block/stat",
"/cat",
"/commands",
"/commands/completion",
"/commands/completion/bash",
"/commands/completion/fish",
"/commands/completion/zsh",
"/dag",
"/dag/get",
"/dag/resolve",
"/dag/stat",
"/dag/export",
"/get",
"/ls",
"/name",
"/name/resolve",
"/object",
"/object/data",
"/object/get",
"/object/links",
"/object/stat",
"/refs",
"/resolve",
"/version",
}
cmdSet := make(map[string]struct{})
collectPaths("", RootRO, cmdSet)
for _, path := range list {
if _, ok := cmdSet[path]; !ok {
t.Errorf("%q not in result", path)
} else {
delete(cmdSet, path)
}
}
for path := range cmdSet {
t.Errorf("%q in result but shouldn't be", path)
}
for _, path := range list {
path = path[1:] // remove leading slash
split := strings.Split(path, "/")
sub, err := RootRO.Get(split)
if err != nil {
t.Errorf("error getting subcommand %q: %v", path, err)
} else if sub == nil {
t.Errorf("subcommand %q is nil even though there was no error", path)
}
}
}
func TestCommands(t *testing.T) {
list := []string{
"/add",

View File

@ -162,72 +162,9 @@ var rootSubcommands = map[string]*cmds.Command{
"multibase": MbaseCmd,
}
// RootRO is the readonly version of Root
var RootRO = &cmds.Command{}
var CommandsDaemonROCmd = CommandsCmd(RootRO)
// RefsROCmd is `ipfs refs` command
var RefsROCmd = &cmds.Command{}
// VersionROCmd is `ipfs version` command (without deps).
var VersionROCmd = &cmds.Command{}
var rootROSubcommands = map[string]*cmds.Command{
"commands": CommandsDaemonROCmd,
"cat": CatCmd,
"block": {
Subcommands: map[string]*cmds.Command{
"stat": blockStatCmd,
"get": blockGetCmd,
},
},
"get": GetCmd,
"ls": LsCmd,
"name": {
Subcommands: map[string]*cmds.Command{
"resolve": name.IpnsCmd,
},
},
"object": {
Subcommands: map[string]*cmds.Command{
"data": ocmd.ObjectDataCmd,
"links": ocmd.ObjectLinksCmd,
"get": ocmd.ObjectGetCmd,
"stat": ocmd.ObjectStatCmd,
},
},
"dag": {
Subcommands: map[string]*cmds.Command{
"get": dag.DagGetCmd,
"resolve": dag.DagResolveCmd,
"stat": dag.DagStatCmd,
"export": dag.DagExportCmd,
},
},
"resolve": ResolveCmd,
}
func init() {
Root.ProcessHelp()
*RootRO = *Root
// this was in the big map definition above before,
// but if we leave it there lgc.NewCommand will be executed
// before the value is updated (:/sanitize readonly refs command/)
// sanitize readonly refs command
*RefsROCmd = *RefsCmd
RefsROCmd.Subcommands = map[string]*cmds.Command{}
rootROSubcommands["refs"] = RefsROCmd
// sanitize readonly version command (no need to expose precise deps)
*VersionROCmd = *VersionCmd
VersionROCmd.Subcommands = map[string]*cmds.Command{}
rootROSubcommands["version"] = VersionROCmd
Root.Subcommands = rootSubcommands
RootRO.Subcommands = rootROSubcommands
}
type MessageOutput struct {

View File

@ -18,5 +18,4 @@ func TestCommandTree(t *testing.T) {
}
}
printErrors(Root.DebugValidate())
printErrors(RootRO.DebugValidate())
}

View File

@ -9,7 +9,6 @@ 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"
@ -122,14 +121,10 @@ func patchCORSVars(c *cmdsHttp.ServerConfig, addr net.Addr) {
c.SetAllowedOrigins(newOrigins...)
}
func commandsOption(cctx oldcmds.Context, command *cmds.Command, allowGet bool) ServeOption {
func commandsOption(cctx oldcmds.Context, command *cmds.Command) ServeOption {
return func(n *core.IpfsNode, l net.Listener, mux *http.ServeMux) (*http.ServeMux, error) {
cfg := cmdsHttp.NewServerConfig()
cfg.AllowGet = allowGet
corsAllowedMethods := []string{http.MethodPost}
if allowGet {
corsAllowedMethods = append(corsAllowedMethods, http.MethodGet)
}
cfg.SetAllowedMethods(corsAllowedMethods...)
cfg.APIPath = APIPath
@ -150,13 +145,6 @@ 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": {`<https://github.com/ipfs/kubo/issues/10312>; rel="deprecation"; type="text/html"`},
}).Wrap(cmdHandler)
}
cmdHandler = otelhttp.NewHandler(cmdHandler, "corehttp.cmdsHandler")
mux.Handle(APIPath+"/", cmdHandler)
return mux, nil
@ -211,13 +199,7 @@ func withAuthSecrets(authorizations map[string]rpcAuthScopeWithUser, next http.H
// CommandsOption constructs a ServerOption for hooking the commands into the
// HTTP server. It will NOT allow GET requests.
func CommandsOption(cctx oldcmds.Context) ServeOption {
return commandsOption(cctx, corecommands.Root, false)
}
// CommandsROOption constructs a ServerOption for hooking the read-only commands
// into the HTTP server. It will allow GET requests.
func CommandsROOption(cctx oldcmds.Context) ServeOption {
return commandsOption(cctx, corecommands.RootRO, true)
return commandsOption(cctx, corecommands.Root)
}
// 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/`

View File

@ -235,7 +235,7 @@ func (o *offlineGatewayErrWrapper) GetDNSLinkRecord(ctx context.Context, s strin
var _ gateway.IPFSBackend = (*offlineGatewayErrWrapper)(nil)
var defaultPaths = []string{"/ipfs/", "/ipns/", "/api/", "/p2p/"}
var defaultPaths = []string{"/ipfs/", "/ipns/", "/p2p/"}
var subdomainGatewaySpec = &gateway.PublicGateway{
Paths: defaultPaths,

View File

@ -7,17 +7,22 @@
- [Overview](#overview)
- [🔦 Highlights](#-highlights)
- [RPC client: removed deprecated DHT API](#rpc-client-removed-deprecated-dht-api)
- [Gateway: `/api/v0` is removed](#gateway-apiv0-is-removed)
- [📝 Changelog](#-changelog)
- [👨‍👩‍👧‍👦 Contributors](#-contributors)
### Overview
### 🔦 Highlights
#### RPC client: removed deprecated DHT API
The deprecated DHT API commands in the RPC client have been removed. Instead, use the Routing API.
#### Gateway: `/api/v0` is removed
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>.
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

View File

@ -106,12 +106,3 @@ Right now only 'full DAG' implicit selector is implemented.
Support for user-provided IPLD selectors is tracked in https://github.com/ipfs/kubo/issues/8769.
This is a rough equivalent of `ipfs dag export`.
## Deprecated Subset of RPC API
For legacy reasons, some gateways may expose a small subset of RPC API under `/api/v0/`.
While this read-only API exposes a read-only, "safe" subset of the normal API,
it is deprecated and should not be used for greenfield projects.
Where possible, leverage `/ipfs/` and `/ipns/` endpoints.
along with `application/vnd.ipld.*` Content-Types instead.

View File

@ -14,7 +14,6 @@ import (
"github.com/ipfs/kubo/config"
"github.com/ipfs/kubo/test/cli/harness"
. "github.com/ipfs/kubo/test/cli/testutils"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-multiaddr"
manet "github.com/multiformats/go-multiaddr/net"
@ -344,76 +343,6 @@ func TestGateway(t *testing.T) {
})
})
t.Run("readonly API", func(t *testing.T) {
t.Parallel()
client := node.GatewayClient()
fileContents := "12345"
h.WriteFile("readonly/dir/test", fileContents)
cids := node.IPFS("add", "-r", "-q", filepath.Join(h.Dir, "readonly/dir")).Stdout.Lines()
rootCID := cids[len(cids)-1]
client.TemplateData = map[string]string{"RootCID": rootCID}
t.Run("Get IPFS directory file through readonly API succeeds", func(t *testing.T) {
t.Parallel()
resp := client.Get("/api/v0/cat?arg={{.RootCID}}/test")
assert.Equal(t, 200, resp.StatusCode)
assert.Equal(t, fileContents, resp.Body)
})
t.Run("refs IPFS directory file through readonly API succeeds", func(t *testing.T) {
t.Parallel()
resp := client.Get("/api/v0/refs?arg={{.RootCID}}/test")
assert.Equal(t, 200, resp.StatusCode)
})
t.Run("test gateway API is sanitized", func(t *testing.T) {
t.Parallel()
for _, cmd := range []string{
"add",
"block/put",
"bootstrap",
"config",
"dag/put",
"dag/import",
"dht",
"diag",
"id",
"mount",
"name/publish",
"object/put",
"object/new",
"object/patch",
"pin",
"ping",
"repo",
"stats",
"swarm",
"file",
"update",
"bitswap",
} {
t.Run(cmd, func(t *testing.T) {
cmd := cmd
t.Parallel()
assert.Equal(t, 404, client.Get("/api/v0/"+cmd).StatusCode)
})
}
})
})
t.Run("refs/local", func(t *testing.T) {
t.Parallel()
gatewayAddr := URLStrToMultiaddr(node.GatewayURL())
res := node.RunIPFS("--api", gatewayAddr.String(), "refs", "local")
assert.Contains(t,
res.Stderr.Trimmed(),
`Error: invalid path "local":`,
)
})
t.Run("raw leaves node", func(t *testing.T) {
t.Parallel()
contents := "This is RAW!"

View File

@ -50,7 +50,7 @@ test_expect_success "docker image runs" '
'
test_expect_success "docker container gateway is up" '
pollEndpoint -host=/ip4/127.0.0.1/tcp/8080 -http-url http://localhost:8080/api/v0/version -v -tries 30 -tout 1s
pollEndpoint -host=/ip4/127.0.0.1/tcp/8080 -http-url http://localhost:8080/ipfs/bafkqaddimvwgy3zao5xxe3debi -v -tries 30 -tout 1s
'
test_expect_success "docker container API is up" '

View File

@ -127,70 +127,6 @@ test_expect_success "Access-Control-Allow-Origin replaces the implicit list" '
test_should_contain "< Access-Control-Allow-Origin: localhost" curl_output
'
# Read-Only /api/v0 RPC API (legacy subset, exposed on the Gateway Port)
# TODO: we want to remove it, but for now this guards the legacy behavior to not go any further
# also check this, as due to legacy reasons Kubo exposes small subset of /api/v0 on GW port
test_expect_success "Assert the default API.HTTPHeaders config is empty" '
echo "{}" > expected &&
ipfs config --json API.HTTPHeaders > actual &&
test_cmp expected actual
'
# HTTP GET Request
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
'
# 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_contain "< Access-Control-" curl_output
'
# HTTP OPTIONS Request
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 CORS headers" '
test_should_contain "< Access-Control-" curl_output
'
test_kill_ipfs_daemon
# TODO: /api/v0 with CORS headers set in API.HTTPHeaders does not really work,
# as not all headers are correctly set. Below is only a basic regression test that documents
# current state. Fixing CORS on /api/v0 (RPC and Gateway port) is tracked in https://github.com/ipfs/kubo/issues/7667
test_expect_success "Manually set API.HTTPHeaders config to be as relaxed as Gateway.HTTPHeaders" "
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin '[\"https://example.com\"]'
"
# TODO: ipfs config --json API.HTTPHeaders.Access-Control-Allow-Methods '[\"GET\",\"POST\"]' &&
# TODO: ipfs config --json API.HTTPHeaders.Access-Control-Allow-Headers '[\"X-Requested-With\", \"Range\", \"User-Agent\"]'
test_launch_ipfs_daemon
# HTTP GET Request
test_expect_success "Manually relaxed 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 "Manually relaxed CORS GET response from {gw}/api/v0 is the same as Gateway" '
test_should_contain "HTTP/1.1 200 OK" curl_output &&
test_should_contain "< Access-Control-Allow-Origin: https://example.com" curl_output
'
# TODO: test_should_contain "< Access-Control-Allow-Methods: GET" curl_output
# HTTP OPTIONS Request
test_expect_success "Manually relaxed 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 "Manually relaxed OPTIONS response from {gw}/api/v0 is the same as Gateway" '
test_should_contain "< Access-Control-Allow-Origin: https://example.com" curl_output
'
# TODO: test_should_contain "< Access-Control-Allow-Methods: GET" curl_output
test_kill_ipfs_daemon
test_done

View File

@ -203,25 +203,6 @@ test_localhost_gateway_response_should_contain \
# end Kubo specific end-to-end test
# API on localhost subdomain gateway
# /api/v0 present on the root hostname
test_localhost_gateway_response_should_contain \
"request for localhost/api" \
"http://localhost:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
"Ref"
# /api/v0 not mounted on content root subdomains
test_localhost_gateway_response_should_contain \
"request for {cid}.ipfs.localhost/api returns data if present on the content root" \
"http://${DIR_CID}.ipfs.localhost:$GWAY_PORT/api/file.txt" \
"I am a txt file"
test_localhost_gateway_response_should_contain \
"request for {cid}.ipfs.localhost/api/v0/refs returns 404" \
"http://${DIR_CID}.ipfs.localhost:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
"404 Not Found"
## ============================================================================
## Test subdomain-based requests to a local gateway with default config
## (origin per content root at http://*.localhost)
@ -308,14 +289,6 @@ test_localhost_gateway_response_should_contain \
"http://$DNSLINK_FQDN.ipns.localhost:$GWAY_PORT" \
"$CID_VAL"
# api.localhost/api
# Note: we use DIR_CID so refs -r returns some CIDs for child nodes
test_localhost_gateway_response_should_contain \
"request for api.localhost returns API response" \
"http://api.localhost:$GWAY_PORT/api/v0/refs?arg=$DIR_CID&r=true" \
"Ref"
## ============================================================================
## Test DNSLink inlining on HTTP gateways
## ============================================================================
@ -518,54 +491,6 @@ test_hostname_gateway_response_should_contain \
"http://127.0.0.1:$GWAY_PORT" \
"Location: http://${ED25519_IPNS_IDv1}.ipns.example.com/"
# API on subdomain gateway example.com
# ============================================================================
# present at the root domain
test_hostname_gateway_response_should_contain \
"request for example.com/api/v0/refs returns expected payload when /api is on Paths whitelist" \
"example.com" \
"http://127.0.0.1:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
"Ref"
# not mounted on content root subdomains
test_hostname_gateway_response_should_contain \
"request for {cid}.ipfs.example.com/api returns data if present on the content root" \
"$DIR_CID.ipfs.example.com" \
"http://127.0.0.1:$GWAY_PORT/api/file.txt" \
"I am a txt file"
test_hostname_gateway_response_should_contain \
"request for {cid}.ipfs.example.com/api/v0/refs returns 404" \
"$CIDv1.ipfs.example.com" \
"http://127.0.0.1:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
"404 Not Found"
# disable /api on example.com
ipfs config --json Gateway.PublicGateways '{
"example.com": {
"UseSubdomains": true,
"Paths": ["/ipfs", "/ipns"]
}
}' || exit 1
# restart daemon to apply config changes
test_kill_ipfs_daemon
test_launch_ipfs_daemon_without_network
# not mounted at the root domain
test_hostname_gateway_response_should_contain \
"request for example.com/api/v0/refs returns 404 if /api not on Paths whitelist" \
"example.com" \
"http://127.0.0.1:$GWAY_PORT/api/v0/refs?arg=${DIR_CID}&r=true" \
"404 Not Found"
# not mounted on content root subdomains
test_hostname_gateway_response_should_contain \
"request for {cid}.ipfs.example.com/api returns data if present on the content root" \
"$DIR_CID.ipfs.example.com" \
"http://127.0.0.1:$GWAY_PORT/api/file.txt" \
"I am a txt file"
# DNSLink: <dnslink-fqdn>.ipns.example.com
# (not really useful outside of localhost, as setting TLS for more than one
# level of wildcard is a pain, but we support it if someone really wants it)