Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ Let us know if you find any issue or if you want to contribute and add a new tut

## Examples and Tutorials

- [Gateway backed by a CAR file](./gateway-car)
- [Gateway backed by a CAR file](./gateway/car)
- [Gateway backed by a remote blockstore and IPNS resolver](./gateway/proxy)
File renamed without changes.
21 changes: 3 additions & 18 deletions examples/gateway-car/main.go → examples/gateway/car/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"github.com/ipfs/go-blockservice"
"github.com/ipfs/go-cid"
offline "github.com/ipfs/go-ipfs-exchange-offline"
"github.com/ipfs/go-libipfs/gateway"
"github.com/ipfs/go-libipfs/examples/gateway/common"
carblockstore "github.com/ipld/go-car/v2/blockstore"
)

Expand All @@ -26,12 +26,12 @@ func main() {
}
defer f.Close()

gateway, err := newBlocksGateway(blockService)
gateway, err := common.NewBlocksGateway(blockService, nil)
if err != nil {
log.Fatal(err)
}

handler := newHandler(gateway, *portPtr)
handler := common.NewBlocksHandler(gateway, *portPtr)

address := "127.0.0.1:" + strconv.Itoa(*portPtr)
log.Printf("Listening on http://%s", address)
Expand Down Expand Up @@ -64,18 +64,3 @@ func newBlockServiceFromCAR(filepath string) (blockservice.BlockService, []cid.C
blockService := blockservice.New(bs, offline.Exchange(bs))
return blockService, roots, r, nil
}

func newHandler(gw *blocksGateway, port int) http.Handler {
headers := map[string][]string{}
gateway.AddAccessControlHeaders(headers)

conf := gateway.Config{
Headers: headers,
}

mux := http.NewServeMux()
gwHandler := gateway.NewHandler(conf, gw)
mux.Handle("/ipfs/", gwHandler)
mux.Handle("/ipns/", gwHandler)
return mux
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http/httptest"
"testing"

"github.com/ipfs/go-libipfs/examples/gateway/common"
"github.com/ipld/go-ipld-prime/codec/dagjson"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/stretchr/testify/assert"
Expand All @@ -21,13 +22,13 @@ func newTestServer() (*httptest.Server, io.Closer, error) {
return nil, nil, err
}

gateway, err := newBlocksGateway(blockService)
gateway, err := common.NewBlocksGateway(blockService, nil)
if err != nil {
_ = f.Close()
return nil, nil, err
}

handler := newHandler(gateway, 0)
handler := common.NewBlocksHandler(gateway, 0)
ts := httptest.NewServer(handler)
return ts, f, nil
}
Expand Down
File renamed without changes.
83 changes: 66 additions & 17 deletions examples/gateway-car/api.go → examples/gateway/common/blocks.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package main
package common

import (
"context"
"errors"
"fmt"
"net/http"
gopath "path"

"github.com/ipfs/go-blockservice"
Expand All @@ -13,7 +13,10 @@ import (
format "github.com/ipfs/go-ipld-format"
"github.com/ipfs/go-libipfs/blocks"
"github.com/ipfs/go-libipfs/files"
"github.com/ipfs/go-libipfs/gateway"
"github.com/ipfs/go-merkledag"
"github.com/ipfs/go-namesys"
"github.com/ipfs/go-namesys/resolve"
ipfspath "github.com/ipfs/go-path"
"github.com/ipfs/go-path/resolver"
"github.com/ipfs/go-unixfs"
Expand All @@ -26,16 +29,36 @@ import (
"github.com/ipld/go-ipld-prime"
"github.com/ipld/go-ipld-prime/node/basicnode"
"github.com/ipld/go-ipld-prime/schema"
"github.com/libp2p/go-libp2p/core/routing"
)

type blocksGateway struct {
func NewBlocksHandler(gw *BlocksGateway, port int) http.Handler {
headers := map[string][]string{}
gateway.AddAccessControlHeaders(headers)

conf := gateway.Config{
Headers: headers,
}

mux := http.NewServeMux()
gwHandler := gateway.NewHandler(conf, gw)
mux.Handle("/ipfs/", gwHandler)
mux.Handle("/ipns/", gwHandler)
return mux
}

type BlocksGateway struct {
blockStore blockstore.Blockstore
blockService blockservice.BlockService
dagService format.DAGService
resolver resolver.Resolver

// Optional routing system to handle /ipns addresses.
namesys namesys.NameSystem
routing routing.ValueStore
}

func newBlocksGateway(blockService blockservice.BlockService) (*blocksGateway, error) {
func NewBlocksGateway(blockService blockservice.BlockService, routing routing.ValueStore) (*BlocksGateway, error) {
// Setup the DAG services, which use the CAR block store.
dagService := merkledag.NewDAGService(blockService)

Expand All @@ -50,15 +73,29 @@ func newBlocksGateway(blockService blockservice.BlockService) (*blocksGateway, e
fetcher := fetcherConfig.WithReifier(unixfsnode.Reify)
resolver := resolver.NewBasicResolver(fetcher)

return &blocksGateway{
// Setup a name system so that we are able to resolve /ipns links.
var (
ns namesys.NameSystem
err error
)
if routing != nil {
ns, err = namesys.NewNameSystem(routing)
if err != nil {
return nil, err
}
}

return &BlocksGateway{
blockStore: blockService.Blockstore(),
blockService: blockService,
dagService: dagService,
resolver: resolver,
routing: routing,
namesys: ns,
}, nil
}

func (api *blocksGateway) GetUnixFsNode(ctx context.Context, p ifacepath.Resolved) (files.Node, error) {
func (api *BlocksGateway) GetUnixFsNode(ctx context.Context, p ifacepath.Resolved) (files.Node, error) {
nd, err := api.resolveNode(ctx, p)
if err != nil {
return nil, err
Expand All @@ -67,7 +104,7 @@ func (api *blocksGateway) GetUnixFsNode(ctx context.Context, p ifacepath.Resolve
return ufile.NewUnixfsFile(ctx, api.dagService, nd)
}

func (api *blocksGateway) LsUnixFsDir(ctx context.Context, p ifacepath.Resolved) (<-chan iface.DirEntry, error) {
func (api *BlocksGateway) LsUnixFsDir(ctx context.Context, p ifacepath.Resolved) (<-chan iface.DirEntry, error) {
node, err := api.resolveNode(ctx, p)
if err != nil {
return nil, err
Expand All @@ -94,15 +131,19 @@ func (api *blocksGateway) LsUnixFsDir(ctx context.Context, p ifacepath.Resolved)
return out, nil
}

func (api *blocksGateway) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, error) {
func (api *BlocksGateway) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, error) {
return api.blockService.GetBlock(ctx, c)
}

func (api *blocksGateway) GetIPNSRecord(context.Context, cid.Cid) ([]byte, error) {
return nil, errors.New("not implemented")
func (api *BlocksGateway) GetIPNSRecord(ctx context.Context, c cid.Cid) ([]byte, error) {
if api.routing != nil {
return api.routing.GetValue(ctx, "/ipns/"+c.String())
}

return nil, routing.ErrNotSupported
}

func (api *blocksGateway) IsCached(ctx context.Context, p ifacepath.Path) bool {
func (api *BlocksGateway) IsCached(ctx context.Context, p ifacepath.Path) bool {
rp, err := api.ResolvePath(ctx, p)
if err != nil {
return false
Expand All @@ -112,20 +153,28 @@ func (api *blocksGateway) IsCached(ctx context.Context, p ifacepath.Path) bool {
return has
}

func (api *blocksGateway) ResolvePath(ctx context.Context, p ifacepath.Path) (ifacepath.Resolved, error) {
func (api *BlocksGateway) ResolvePath(ctx context.Context, p ifacepath.Path) (ifacepath.Resolved, error) {
if _, ok := p.(ifacepath.Resolved); ok {
return p.(ifacepath.Resolved), nil
}

if err := p.IsValid(); err != nil {
err := p.IsValid()
if err != nil {
return nil, err
}

if p.Namespace() != "ipfs" {
ipath := ipfspath.Path(p.String())
if ipath.Segments()[0] == "ipns" {
ipath, err = resolve.ResolveIPNS(ctx, api.namesys, ipath)
if err != nil {
return nil, err
}
}

if ipath.Segments()[0] != "ipfs" {
return nil, fmt.Errorf("unsupported path namespace: %s", p.Namespace())
}

ipath := ipfspath.Path(p.String())
node, rest, err := api.resolver.ResolveToLastNode(ctx, ipath)
if err != nil {
return nil, err
Expand All @@ -139,7 +188,7 @@ func (api *blocksGateway) ResolvePath(ctx context.Context, p ifacepath.Path) (if
return ifacepath.NewResolvedPath(ipath, node, root, gopath.Join(rest...)), nil
}

func (api *blocksGateway) resolveNode(ctx context.Context, p ifacepath.Path) (format.Node, error) {
func (api *BlocksGateway) resolveNode(ctx context.Context, p ifacepath.Path) (format.Node, error) {
rp, err := api.ResolvePath(ctx, p)
if err != nil {
return nil, err
Expand All @@ -152,7 +201,7 @@ func (api *blocksGateway) resolveNode(ctx context.Context, p ifacepath.Path) (fo
return node, nil
}

func (api *blocksGateway) processLink(ctx context.Context, result unixfs.LinkResult) iface.DirEntry {
func (api *BlocksGateway) processLink(ctx context.Context, result unixfs.LinkResult) iface.DirEntry {
if result.Err != nil {
return iface.DirEntry{Err: result.Err}
}
Expand Down
36 changes: 36 additions & 0 deletions examples/gateway/proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Gateway as a Verifying Proxy for Untrusted Remote Blockstore

This is an example of building a Gateway that uses `application/vnd.ipld.raw`
responses from another gateway acting as a remote blockstore and IPNS resolver.

Key benefits:
1. Verifies raw blocks and IPNS records fetched from untrusted third-party gateways.
2. The proxy provides web gateway functionalities: returns deserialized files and websites, including index.html support, while the remote gateway only needs to support block responses.

In this example, we implement two major structures:

- [Block Store](./blockstore.go), which forwards the block requests to the backend
gateway using `?format=raw`, and
- [Routing System](./routing.go), which forwards the IPNS requests to the backend
gateway using `?format=ipns-record`. In addition, DNSLink lookups are done locally.
- Note: `ipns-record` was introduced just recently in [IPIP-351](https://github.com/ipfs/specs/pull/351) and reference support for it will ship in Kubo 0.19. Until that happens, it may not be supported by public gateways yet.

## Build

```bash
> go build -o verifying-proxy
```

## Usage

First, you need a compliant gateway that supports both [RAW Block](https://www.iana.org/assignments/media-types/application/vnd.ipld.raw) and IPNS Record response
types. Once you have it, run the proxy gateway with its address as the host parameter:


```
./verifying-proxy -h https://ipfs.io -p 8040
```

Now you can access the gateway in [127.0.0.1:8040](http://127.0.0.1:8040). It will
behave like a regular IPFS Gateway, except for the fact that it runs no libp2p, and has no local blockstore.
All contents are provided by a remote gateway and fetched as RAW Blocks and Records, and verified locally.
Loading