From 29ffa89d810235333df19c64ad9e95e64779818f Mon Sep 17 00:00:00 2001 From: Julian Gruber Date: Tue, 25 Jul 2023 16:59:08 +0200 Subject: [PATCH 1/9] add write `X-Signature` header --- httpipfs.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/httpipfs.go b/httpipfs.go index a12e0cd..b20d9c8 100644 --- a/httpipfs.go +++ b/httpipfs.go @@ -2,6 +2,7 @@ package frisbii import ( "context" + "encoding/json" "errors" "fmt" "io" @@ -23,6 +24,12 @@ type ErrorLogger interface { LogError(status int, err error) } +type RequestSignature struct { + requestId string + cid string + protocol string +} + // HttpIpfs is an http.Handler that serves IPLD data via HTTP according to the // Trustless Gateway specification. type HttpIpfs struct { @@ -119,6 +126,18 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { selNode := unixfsnode.UnixFSPathSelectorBuilder(path.String(), dagScope.TerminalSelectorSpec(), false) + sig := RequestSignature{ + requestId: req.Header.Get("X-Signature"), + cid: rootCid.String(), + protocol: "https", + } + b, err := json.Marshal(sig) + if err != nil { + logError(http.StatusInternalServerError, err) + return + } + res.Header().Set("X-Signature", string(b)) + bytesWrittenCh := make(chan struct{}) writer := newIpfsResponseWriter(res, hi.maxResponseBytes, func() { // called once we start writing blocks into the CAR (on the first Put()) From f6fee038d7fef485b28f2eb967fd9a60a9fc90b0 Mon Sep 17 00:00:00 2001 From: Julian Gruber Date: Tue, 25 Jul 2023 17:07:50 +0200 Subject: [PATCH 2/9] add sign --- cmd/frisbii/main.go | 2 +- frisbii.go | 6 +++++- httpipfs.go | 11 ++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/cmd/frisbii/main.go b/cmd/frisbii/main.go index 1bf11a0..f5405bd 100644 --- a/cmd/frisbii/main.go +++ b/cmd/frisbii/main.go @@ -100,7 +100,7 @@ func action(c *cli.Context) error { unixfsnode.AddUnixFSReificationToLinkSystem(&lsys) lsys.SetReadStorage(multicar) - server, err := frisbii.NewFrisbiiServer(ctx, logWriter, lsys, config.MaxResponseDuration, config.MaxResponseBytes, config.Listen) + server, err := frisbii.NewFrisbiiServer(ctx, logWriter, lsys, config.MaxResponseDuration, config.MaxResponseBytes, config.Listen, privKey) if err != nil { return err } diff --git a/frisbii.go b/frisbii.go index 4f2661a..b4d0a0c 100644 --- a/frisbii.go +++ b/frisbii.go @@ -10,6 +10,7 @@ import ( "github.com/ipfs/go-cid" "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/peer" "github.com/ipld/go-ipld-prime/linking" @@ -34,6 +35,7 @@ type FrisbiiServer struct { listener net.Listener mux *http.ServeMux indexerProvider IndexerProvider + privKey crypto.PrivKey } type IndexerProvider interface { @@ -48,6 +50,7 @@ func NewFrisbiiServer( maxResponseDuration time.Duration, maxResponseBytes int64, address string, + privKey crypto.PrivKey, ) (*FrisbiiServer, error) { listener, err := net.Listen("tcp", address) if err != nil { @@ -61,6 +64,7 @@ func NewFrisbiiServer( maxResponseBytes: maxResponseBytes, listener: listener, + privKey: privKey, }, nil } @@ -70,7 +74,7 @@ func (fs *FrisbiiServer) Addr() net.Addr { func (fs *FrisbiiServer) Serve() error { fs.mux = http.NewServeMux() - fs.mux.Handle("/ipfs/", NewHttpIpfs(fs.ctx, fs.logWriter, fs.lsys, fs.maxResponseDuration, fs.maxResponseBytes)) + fs.mux.Handle("/ipfs/", NewHttpIpfs(fs.ctx, fs.logWriter, fs.lsys, fs.maxResponseDuration, fs.maxResponseBytes, fs.privKey)) server := &http.Server{ Addr: fs.Addr().String(), BaseContext: func(listener net.Listener) context.Context { return fs.ctx }, diff --git a/httpipfs.go b/httpipfs.go index b20d9c8..a0c5b82 100644 --- a/httpipfs.go +++ b/httpipfs.go @@ -16,6 +16,7 @@ import ( "github.com/ipfs/go-unixfsnode" "github.com/ipld/go-ipld-prime/datamodel" "github.com/ipld/go-ipld-prime/linking" + "github.com/libp2p/go-libp2p/core/crypto" ) var _ http.Handler = (*HttpIpfs)(nil) @@ -38,6 +39,7 @@ type HttpIpfs struct { lsys linking.LinkSystem maxResponseDuration time.Duration maxResponseBytes int64 + privKey crypto.PrivKey } func NewHttpIpfs( @@ -46,6 +48,7 @@ func NewHttpIpfs( lsys linking.LinkSystem, maxResponseDuration time.Duration, maxResponseBytes int64, + privKey crypto.PrivKey, ) *HttpIpfs { return &HttpIpfs{ @@ -54,6 +57,7 @@ func NewHttpIpfs( lsys: lsys, maxResponseDuration: maxResponseDuration, maxResponseBytes: maxResponseBytes, + privKey: privKey, } } @@ -136,7 +140,12 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { logError(http.StatusInternalServerError, err) return } - res.Header().Set("X-Signature", string(b)) + sigSigned, err := hi.privKey.Sign(b) + if err != nil { + logError(http.StatusInternalServerError, err) + return + } + res.Header().Set("X-Signature", string(sigSigned)) bytesWrittenCh := make(chan struct{}) writer := newIpfsResponseWriter(res, hi.maxResponseBytes, func() { From bed38e60e844cb791b1a0324fb9647b70d7b7352 Mon Sep 17 00:00:00 2001 From: Julian Gruber Date: Wed, 26 Jul 2023 09:41:03 +0200 Subject: [PATCH 3/9] x-signature -> x-request-id --- httpipfs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httpipfs.go b/httpipfs.go index a0c5b82..1896cf7 100644 --- a/httpipfs.go +++ b/httpipfs.go @@ -131,7 +131,7 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { selNode := unixfsnode.UnixFSPathSelectorBuilder(path.String(), dagScope.TerminalSelectorSpec(), false) sig := RequestSignature{ - requestId: req.Header.Get("X-Signature"), + requestId: req.Header.Get("X-Request-Id"), cid: rootCid.String(), protocol: "https", } @@ -145,7 +145,7 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { logError(http.StatusInternalServerError, err) return } - res.Header().Set("X-Signature", string(sigSigned)) + res.Header().Set("X-Request-Id", string(sigSigned)) bytesWrittenCh := make(chan struct{}) writer := newIpfsResponseWriter(res, hi.maxResponseBytes, func() { From 0c6981350cfbefb33fef5b7034f28eb6fb9147e5 Mon Sep 17 00:00:00 2001 From: Julian Gruber Date: Wed, 26 Jul 2023 09:43:31 +0200 Subject: [PATCH 4/9] include raw signature in header --- httpipfs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpipfs.go b/httpipfs.go index 1896cf7..d99fe91 100644 --- a/httpipfs.go +++ b/httpipfs.go @@ -145,7 +145,7 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { logError(http.StatusInternalServerError, err) return } - res.Header().Set("X-Request-Id", string(sigSigned)) + res.Header().Set("X-Request-Id", fmt.Sprintf("%s.%s", b, string(sigSigned))) bytesWrittenCh := make(chan struct{}) writer := newIpfsResponseWriter(res, hi.maxResponseBytes, func() { From f791a89154b332e98ce1dceea0828ed973119ce9 Mon Sep 17 00:00:00 2001 From: Julian Gruber Date: Wed, 26 Jul 2023 09:46:42 +0200 Subject: [PATCH 5/9] base64 encode payload --- httpipfs.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/httpipfs.go b/httpipfs.go index d99fe91..efb5d07 100644 --- a/httpipfs.go +++ b/httpipfs.go @@ -2,6 +2,7 @@ package frisbii import ( "context" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -145,7 +146,10 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { logError(http.StatusInternalServerError, err) return } - res.Header().Set("X-Request-Id", fmt.Sprintf("%s.%s", b, string(sigSigned))) + res.Header().Set( + "X-Request-Id", + fmt.Sprintf("%s.%s", base64.StdEncoding.EncodeToString(b), string(sigSigned)), + ) bytesWrittenCh := make(chan struct{}) writer := newIpfsResponseWriter(res, hi.maxResponseBytes, func() { From 035578a7e5e6b25c0813def45aab71f0a22f64a2 Mon Sep 17 00:00:00 2001 From: Julian Gruber Date: Wed, 26 Jul 2023 09:47:21 +0200 Subject: [PATCH 6/9] use `x-attestation` response header --- httpipfs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpipfs.go b/httpipfs.go index efb5d07..752d666 100644 --- a/httpipfs.go +++ b/httpipfs.go @@ -147,7 +147,7 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { return } res.Header().Set( - "X-Request-Id", + "X-Attestation", fmt.Sprintf("%s.%s", base64.StdEncoding.EncodeToString(b), string(sigSigned)), ) From 27390ce3eebfe899ed4b5bb939b4c8c0fea7057a Mon Sep 17 00:00:00 2001 From: Julian Gruber Date: Mon, 31 Jul 2023 18:16:02 +0200 Subject: [PATCH 7/9] fix header --- httpipfs.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/httpipfs.go b/httpipfs.go index 752d666..7a2fc36 100644 --- a/httpipfs.go +++ b/httpipfs.go @@ -27,9 +27,9 @@ type ErrorLogger interface { } type RequestSignature struct { - requestId string - cid string - protocol string + RequestId string `json:"requestId"` + Cid string `json:"cid"` + Protocol string `json:"protocol"` } // HttpIpfs is an http.Handler that serves IPLD data via HTTP according to the @@ -132,9 +132,9 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { selNode := unixfsnode.UnixFSPathSelectorBuilder(path.String(), dagScope.TerminalSelectorSpec(), false) sig := RequestSignature{ - requestId: req.Header.Get("X-Request-Id"), - cid: rootCid.String(), - protocol: "https", + RequestId: req.Header.Get("X-Request-Id"), + Cid: rootCid.String(), + Protocol: "https", } b, err := json.Marshal(sig) if err != nil { @@ -148,7 +148,11 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { } res.Header().Set( "X-Attestation", - fmt.Sprintf("%s.%s", base64.StdEncoding.EncodeToString(b), string(sigSigned)), + fmt.Sprintf( + "%s.%s", + base64.StdEncoding.EncodeToString(b), + base64.StdEncoding.EncodeToString(sigSigned), + ), ) bytesWrittenCh := make(chan struct{}) From 68ded2c9e4cf253c29af558991911c0f6e1de640 Mon Sep 17 00:00:00 2001 From: Julian Gruber Date: Mon, 31 Jul 2023 18:32:22 +0200 Subject: [PATCH 8/9] fly.io has issues with this header --- httpipfs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpipfs.go b/httpipfs.go index 7a2fc36..283d7a6 100644 --- a/httpipfs.go +++ b/httpipfs.go @@ -149,7 +149,7 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { res.Header().Set( "X-Attestation", fmt.Sprintf( - "%s.%s", + "\"%s.%s\"", base64.StdEncoding.EncodeToString(b), base64.StdEncoding.EncodeToString(sigSigned), ), From 334077a2b3daca8bc1927325fc263f774e6a53e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 1 Aug 2023 18:04:33 +0200 Subject: [PATCH 9/9] send the attestation after the response body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Miroslav Bajtoš --- httpipfs.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/httpipfs.go b/httpipfs.go index 283d7a6..b9805f7 100644 --- a/httpipfs.go +++ b/httpipfs.go @@ -146,13 +146,10 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { logError(http.StatusInternalServerError, err) return } - res.Header().Set( - "X-Attestation", - fmt.Sprintf( - "\"%s.%s\"", - base64.StdEncoding.EncodeToString(b), - base64.StdEncoding.EncodeToString(sigSigned), - ), + attestation := fmt.Sprintf( + "\"%s.%s\"", + base64.StdEncoding.EncodeToString(b), + base64.StdEncoding.EncodeToString(sigSigned), ) bytesWrittenCh := make(chan struct{}) @@ -165,6 +162,7 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { res.Header().Set("Etag", etag(rootCid, path.String(), dagScope, includeDupes)) res.Header().Set("X-Content-Type-Options", "nosniff") res.Header().Set("X-Ipfs-Path", "/"+datamodel.ParsePath(req.URL.Path).String()) + res.Header().Add("Trailer", "X-Attestation") close(bytesWrittenCh) }) @@ -181,6 +179,8 @@ func (hi *HttpIpfs) ServeHTTP(res http.ResponseWriter, req *http.Request) { } logger.Debugw("error streaming CAR", "cid", rootCid, "err", err) } + + res.Header().Set("X-Attestation", attestation) } var _ io.Writer = (*ipfsResponseWriter)(nil)