initial pass at fetching bundles with sas urls

Signed-off-by: Meredith Lancaster <malancas@github.com>
This commit is contained in:
Meredith Lancaster 2024-11-06 07:57:18 -07:00
parent 30066b0042
commit bfd140c0e5
6 changed files with 71 additions and 2 deletions

View file

@ -26,6 +26,7 @@ func newErrNoAttestations(name, digest string) ErrNoAttestations {
type Attestation struct {
Bundle *bundle.Bundle `json:"bundle"`
SASUrl string `json:"signedAccessSignatureUrl"`
}
type AttestationsResponse struct {

View file

@ -1,16 +1,21 @@
package api
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/cli/cli/v2/api"
ioconfig "github.com/cli/cli/v2/pkg/cmd/attestation/io"
"github.com/golang/snappy"
v1 "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
"github.com/sigstore/sigstore-go/pkg/bundle"
)
const (
@ -25,6 +30,7 @@ type apiClient interface {
}
type Client interface {
FetchAttestationsWithSASURL(attestations []*Attestation) ([]*Attestation, error)
GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error)
GetByOwnerAndDigest(owner, digest string, limit int) ([]*Attestation, error)
GetTrustDomain() (string, error)
@ -130,6 +136,49 @@ func (c *LiveClient) getAttestations(url, name, digest string, limit int) ([]*At
return attestations, nil
}
func (c *LiveClient) FetchAttestationsWithSASURL(attestations []*Attestation) ([]*Attestation, error) {
fetched := make([]*Attestation, len(attestations))
for i, a := range attestations {
b, err := c.fetchAttestationWithSASURL(a)
if err != nil {
return nil, fmt.Errorf("failed to fetch attestation with SAS URL: %w", err)
}
fetched[i] = &Attestation{
Bundle: b,
}
}
return fetched, nil
}
func (c *LiveClient) fetchAttestationWithSASURL(a *Attestation) (*bundle.Bundle, error) {
if a.SASUrl == "" {
return a.Bundle, nil
}
parsed, err := url.Parse(a.SASUrl)
if err != nil {
return nil, err
}
var resp []byte
if err = c.api.REST(parsed.Host, http.MethodGet, parsed.Path, nil, &resp); err != nil {
return nil, err
}
var decompressedBytes []byte
_, err = snappy.Decode(decompressedBytes, resp)
if err != nil {
return nil, err
}
var pbBundle *v1.Bundle
if err = json.Unmarshal(decompressedBytes, pbBundle); err != nil {
return nil, err
}
return bundle.NewBundle(pbBundle)
}
func shouldRetry(err error) bool {
var httpError api.HTTPError
if errors.As(err, &httpError) {

View file

@ -12,6 +12,10 @@ type MockClient struct {
OnGetTrustDomain func() (string, error)
}
func (m MockClient) FetchAttestationsWithSASURL(attestations []*Attestation) ([]*Attestation, error) {
return nil, nil
}
func (m MockClient) GetByRepoAndDigest(repo, digest string, limit int) ([]*Attestation, error) {
return m.OnGetByRepoAndDigest(repo, digest, limit)
}

View file

@ -127,13 +127,25 @@ func GetRemoteAttestations(c FetchAttestationsConfig) ([]*api.Attestation, error
if err != nil {
return nil, fmt.Errorf("failed to fetch attestations from %s: %w", c.Repo, err)
}
return attestations, nil
fetched, err := c.APIClient.FetchAttestationsWithSASURL(attestations)
if err != nil {
return nil, fmt.Errorf("failed to fetch bundles from SAS URL: %w", err)
}
return fetched, nil
} else if c.Owner != "" {
attestations, err := c.APIClient.GetByOwnerAndDigest(c.Owner, c.Digest, c.Limit)
if err != nil {
return nil, fmt.Errorf("failed to fetch attestations from %s: %w", c.Owner, err)
}
return attestations, nil
fetched, err := c.APIClient.FetchAttestationsWithSASURL(attestations)
if err != nil {
return nil, fmt.Errorf("failed to fetch bundles from SAS URL: %w", err)
}
return fetched, nil
}
return nil, fmt.Errorf("owner or repo must be provided")
}