From fc8c9a2d2f1abf1bc243c726a4f67cffeae3dcb7 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Fri, 11 Nov 2022 13:49:37 +0100 Subject: [PATCH] wip: ipns signed record on gateway --- core/coreapi/coreapi.go | 5 ++ core/coreapi/routing.go | 70 ++++++++++++++++++++ core/corehttp/gateway.go | 4 ++ core/corehttp/gateway_handler.go | 8 +++ core/corehttp/gateway_handler_ipns_record.go | 50 ++++++++++++++ docs/examples/kubo-as-a-library/go.mod | 2 +- docs/examples/kubo-as-a-library/go.sum | 4 +- go.mod | 2 +- go.sum | 4 +- 9 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 core/coreapi/routing.go create mode 100644 core/corehttp/gateway_handler_ipns_record.go diff --git a/core/coreapi/coreapi.go b/core/coreapi/coreapi.go index fb549171a6ce..8a92e863c815 100644 --- a/core/coreapi/coreapi.go +++ b/core/coreapi/coreapi.go @@ -144,6 +144,11 @@ func (api *CoreAPI) PubSub() coreiface.PubSubAPI { return (*PubSubAPI)(api) } +// Routing returns the RoutingAPI interface implementation backed by the go-ipfs node +func (api *CoreAPI) Routing() coreiface.RoutingAPI { + return (*RoutingAPI)(api) +} + // WithOptions returns api with global options applied func (api *CoreAPI) WithOptions(opts ...options.ApiOption) (coreiface.CoreAPI, error) { settings := api.parentOpts // make sure to copy diff --git a/core/coreapi/routing.go b/core/coreapi/routing.go new file mode 100644 index 000000000000..a7285c83804b --- /dev/null +++ b/core/coreapi/routing.go @@ -0,0 +1,70 @@ +package coreapi + +import ( + "context" + "encoding/base64" + "errors" + + "github.com/ipfs/go-path" + peer "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/routing" +) + +type RoutingAPI CoreAPI + +func (r *RoutingAPI) Get(ctx context.Context, key string) ([]byte, error) { + dhtKey, err := ensureIPNSKey(key) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithCancel(ctx) + ctx, events := routing.RegisterForQueryEvents(ctx) + + var getErr error + + go func() { + defer cancel() + var val []byte + val, getErr = r.routing.GetValue(ctx, dhtKey) + if getErr != nil { + routing.PublishQueryEvent(ctx, &routing.QueryEvent{ + Type: routing.QueryError, + Extra: getErr.Error(), + }) + } else { + routing.PublishQueryEvent(ctx, &routing.QueryEvent{ + Type: routing.Value, + Extra: base64.StdEncoding.EncodeToString(val), + }) + } + }() + + for e := range events { + if e.Type == routing.Value { + return []byte(e.Extra), nil + } + } + + if err := ctx.Err(); err != nil { + return nil, err + } + + return nil, errors.New("key not found") +} + +// Shamelessly (mostly) copied from commands/routing.go +func ensureIPNSKey(s string) (string, error) { + parts := path.SplitList(s) + if len(parts) != 3 || + parts[0] != "" || + parts[1] != "ipns" { + return "", errors.New("invalid key") + } + + k, err := peer.Decode(parts[2]) + if err != nil { + return "", err + } + return path.Join(append(parts[:2], string(k))), nil +} diff --git a/core/corehttp/gateway.go b/core/corehttp/gateway.go index 0d0a234d946f..e95946107fa5 100644 --- a/core/corehttp/gateway.go +++ b/core/corehttp/gateway.go @@ -34,6 +34,10 @@ type NodeAPI interface { // Dag returns an implementation of Dag API Dag() coreiface.APIDagService + // Routing returns an implementation of Routing API. + // Used for returning signed IPNS records, see IPIP-0328 + Routing() coreiface.RoutingAPI + // ResolvePath resolves the path using Unixfs resolver ResolvePath(context.Context, path.Path) (path.Resolved, error) } diff --git a/core/corehttp/gateway_handler.go b/core/corehttp/gateway_handler.go index 7f0f11885e69..3672a9ce7edd 100644 --- a/core/corehttp/gateway_handler.go +++ b/core/corehttp/gateway_handler.go @@ -434,6 +434,12 @@ func (i *gatewayHandler) getOrHeadHandler(w http.ResponseWriter, r *http.Request logger.Debugw("serving tar file", "path", contentPath) i.serveTAR(r.Context(), w, r, resolvedPath, contentPath, begin, logger) return + case "application/vnd.ipfs.ipns-record": + // TODO: i.handlePathResolution has been executed here, but we don't really need it. Should we check + // this beforehand? + logger.Debugw("serving ipns record", "path", contentPath) + i.serveIpnsRecord(r.Context(), w, r, contentPath, begin, logger) + return default: // catch-all for unsuported application/vnd.* err := fmt.Errorf("unsupported format %q", responseFormat) webError(w, "failed respond with requested content type", err, http.StatusBadRequest) @@ -866,6 +872,8 @@ func customResponseFormat(r *http.Request) (mediaType string, params map[string] return "application/vnd.ipld.car", nil, nil case "tar": return "application/x-tar", nil, nil + case "ipns-record": + return "application/vnd.ipfs.ipns-record", nil, nil } } // Browsers and other user agents will send Accept header with generic types like: diff --git a/core/corehttp/gateway_handler_ipns_record.go b/core/corehttp/gateway_handler_ipns_record.go new file mode 100644 index 000000000000..36d699aaea6f --- /dev/null +++ b/core/corehttp/gateway_handler_ipns_record.go @@ -0,0 +1,50 @@ +package corehttp + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + "time" + + path "github.com/ipfs/go-path" + ipath "github.com/ipfs/interface-go-ipfs-core/path" + "go.uber.org/zap" +) + +func (i *gatewayHandler) serveIpnsRecord(ctx context.Context, w http.ResponseWriter, r *http.Request, contentPath ipath.Path, begin time.Time, logger *zap.SugaredLogger) { + if contentPath.Namespace() != "ipns" { + err := fmt.Errorf("%s is not an IPNS link", contentPath.String()) + webError(w, err.Error(), err, http.StatusBadRequest) + return + } + + key := contentPath.String() + key = strings.TrimSuffix(key, "/") + if strings.Count(key, "/") > 2 { + err := errors.New("cannot find ipns key for subpath") + webError(w, err.Error(), err, http.StatusBadRequest) + return + } + + record, err := i.api.Routing().Get(ctx, key) + if err != nil { + webError(w, err.Error(), err, http.StatusInternalServerError) + return + } + + // Set Content-Disposition + var name string + if urlFilename := r.URL.Query().Get("filename"); urlFilename != "" { + name = urlFilename + } else { + name = path.SplitList(key)[2] + ".ipns-record" + } + setContentDispositionHeader(w, name, "attachment") + + w.Header().Set("Content-Type", "application/vnd.ipfs.ipns-record") + w.Header().Set("X-Content-Type-Options", "nosniff") + + _, _ = w.Write(record) +} diff --git a/docs/examples/kubo-as-a-library/go.mod b/docs/examples/kubo-as-a-library/go.mod index 57333bfb27ac..208438600b07 100644 --- a/docs/examples/kubo-as-a-library/go.mod +++ b/docs/examples/kubo-as-a-library/go.mod @@ -8,7 +8,7 @@ replace github.com/ipfs/kubo => ./../../.. require ( github.com/ipfs/go-ipfs-files v0.2.0 - github.com/ipfs/interface-go-ipfs-core v0.7.0 + github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221114161843-63169fd5e874 github.com/ipfs/kubo v0.14.0-rc1 github.com/libp2p/go-libp2p v0.23.4 github.com/multiformats/go-multiaddr v0.7.0 diff --git a/docs/examples/kubo-as-a-library/go.sum b/docs/examples/kubo-as-a-library/go.sum index 73797d285018..62100ca77c23 100644 --- a/docs/examples/kubo-as-a-library/go.sum +++ b/docs/examples/kubo-as-a-library/go.sum @@ -657,8 +657,8 @@ github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZ github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs= github.com/ipfs/go-verifcid v0.0.2/go.mod h1:40cD9x1y4OWnFXbLNJYRe7MpNvWlMn3LZAG5Wb4xnPU= github.com/ipfs/interface-go-ipfs-core v0.4.0/go.mod h1:UJBcU6iNennuI05amq3FQ7g0JHUkibHFAfhfUIy927o= -github.com/ipfs/interface-go-ipfs-core v0.7.0 h1:7tb+2upz8oCcjIyjo1atdMk+P+u7wPmI+GksBlLE8js= -github.com/ipfs/interface-go-ipfs-core v0.7.0/go.mod h1:lF27E/nnSPbylPqKVXGZghal2hzifs3MmjyiEjnc9FY= +github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221114161843-63169fd5e874 h1:55uGMOPDlAH5kAqjAjRZfvaBfUCWafY9uRzCKAq9Rag= +github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221114161843-63169fd5e874/go.mod h1:X/udt0qeqxXlgv69JQ8g38gWy4LrCyOuav6f7KDoJMo= github.com/ipfs/tar-utils v0.0.2/go.mod h1:4qlnRWgTVljIMhSG2SqRYn66NT+3wrv/kZt9V+eqxDM= github.com/ipld/edelweiss v0.2.0 h1:KfAZBP8eeJtrLxLhi7r3N0cBCo7JmwSRhOJp3WSpNjk= github.com/ipld/edelweiss v0.2.0/go.mod h1:FJAzJRCep4iI8FOFlRriN9n0b7OuX3T/S9++NpBDmA4= diff --git a/go.mod b/go.mod index fdb2efa083d8..36585fb43d4e 100644 --- a/go.mod +++ b/go.mod @@ -61,7 +61,7 @@ require ( github.com/ipfs/go-unixfs v0.4.1 github.com/ipfs/go-unixfsnode v1.4.0 github.com/ipfs/go-verifcid v0.0.2 - github.com/ipfs/interface-go-ipfs-core v0.7.0 + github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221114161843-63169fd5e874 github.com/ipfs/tar-utils v0.0.2 github.com/ipld/go-car v0.4.0 github.com/ipld/go-car/v2 v2.4.0 diff --git a/go.sum b/go.sum index 11fd7a8b6ca6..9614eae77aa1 100644 --- a/go.sum +++ b/go.sum @@ -646,8 +646,8 @@ github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZ github.com/ipfs/go-verifcid v0.0.2 h1:XPnUv0XmdH+ZIhLGKg6U2vaPaRDXb9urMyNVCE7uvTs= github.com/ipfs/go-verifcid v0.0.2/go.mod h1:40cD9x1y4OWnFXbLNJYRe7MpNvWlMn3LZAG5Wb4xnPU= github.com/ipfs/interface-go-ipfs-core v0.4.0/go.mod h1:UJBcU6iNennuI05amq3FQ7g0JHUkibHFAfhfUIy927o= -github.com/ipfs/interface-go-ipfs-core v0.7.0 h1:7tb+2upz8oCcjIyjo1atdMk+P+u7wPmI+GksBlLE8js= -github.com/ipfs/interface-go-ipfs-core v0.7.0/go.mod h1:lF27E/nnSPbylPqKVXGZghal2hzifs3MmjyiEjnc9FY= +github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221114161843-63169fd5e874 h1:55uGMOPDlAH5kAqjAjRZfvaBfUCWafY9uRzCKAq9Rag= +github.com/ipfs/interface-go-ipfs-core v0.7.1-0.20221114161843-63169fd5e874/go.mod h1:X/udt0qeqxXlgv69JQ8g38gWy4LrCyOuav6f7KDoJMo= github.com/ipfs/tar-utils v0.0.2 h1:UNgHB4x/PPzbMkmJi+7EqC9LNMPDztOVSnx1HAqSNg4= github.com/ipfs/tar-utils v0.0.2/go.mod h1:4qlnRWgTVljIMhSG2SqRYn66NT+3wrv/kZt9V+eqxDM= github.com/ipld/edelweiss v0.2.0 h1:KfAZBP8eeJtrLxLhi7r3N0cBCo7JmwSRhOJp3WSpNjk=