cli/pkg/cmd/attestation/verification/attestation.go
Meredith Lancaster a856a796f0 remove duplicate predicate filtering code
Signed-off-by: Meredith Lancaster <malancas@github.com>
2025-03-24 18:34:54 -06:00

126 lines
3.6 KiB
Go

package verification
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"github.com/cli/cli/v2/pkg/cmd/attestation/api"
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact"
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci"
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
"github.com/sigstore/sigstore-go/pkg/bundle"
)
const SLSAPredicateV1 = "https://slsa.dev/provenance/v1"
var ErrUnrecognisedBundleExtension = errors.New("bundle file extension not supported, must be json or jsonl")
var ErrEmptyBundleFile = errors.New("provided bundle file is empty")
// GetLocalAttestations returns a slice of attestations read from a local bundle file.
func GetLocalAttestations(path string) ([]*api.Attestation, error) {
var attestations []*api.Attestation
var err error
fileExt := filepath.Ext(path)
if fileExt == ".json" {
attestations, err = loadBundleFromJSONFile(path)
} else if fileExt == ".jsonl" {
attestations, err = loadBundlesFromJSONLinesFile(path)
} else {
return nil, ErrUnrecognisedBundleExtension
}
if err != nil {
var pathErr *os.PathError
if errors.As(err, &pathErr) {
return nil, fmt.Errorf("could not load content from file path %s: %w", path, err)
} else if errors.Is(err, bundle.ErrValidation) {
return nil, err
}
return nil, fmt.Errorf("bundle content could not be parsed: %w", err)
}
return attestations, nil
}
func loadBundleFromJSONFile(path string) ([]*api.Attestation, error) {
b, err := bundle.LoadJSONFromPath(path)
if err != nil {
return nil, err
}
return []*api.Attestation{{Bundle: b}}, nil
}
func loadBundlesFromJSONLinesFile(path string) ([]*api.Attestation, error) {
fileContent, err := os.ReadFile(path)
if err != nil {
return nil, err
}
attestations := []*api.Attestation{}
decoder := json.NewDecoder(bytes.NewReader(fileContent))
for decoder.More() {
var b bundle.Bundle
b.Bundle = new(protobundle.Bundle)
if err := decoder.Decode(&b); err != nil {
return nil, err
}
a := api.Attestation{Bundle: &b}
attestations = append(attestations, &a)
}
if len(attestations) == 0 {
return nil, ErrEmptyBundleFile
}
return attestations, nil
}
func GetOCIAttestations(client oci.Client, artifact artifact.DigestedArtifact) ([]*api.Attestation, error) {
attestations, err := client.GetAttestations(artifact.NameRef(), artifact.DigestWithAlg())
if err != nil {
return nil, fmt.Errorf("failed to fetch OCI attestations: %w", err)
}
if len(attestations) == 0 {
return nil, fmt.Errorf("no attestations found in the OCI registry. Retry the command without the --bundle-from-oci flag to check GitHub for the attestation")
}
return attestations, nil
}
type IntotoStatement struct {
PredicateType string `json:"predicateType"`
}
func FilterAttestations(predicateType string, attestations []*api.Attestation) ([]*api.Attestation, error) {
filteredAttestations := []*api.Attestation{}
for _, each := range attestations {
dsseEnvelope := each.Bundle.GetDsseEnvelope()
if dsseEnvelope != nil {
if dsseEnvelope.PayloadType != "application/vnd.in-toto+json" {
// Don't fail just because an entry isn't intoto
continue
}
var intotoStatement IntotoStatement
if err := json.Unmarshal([]byte(dsseEnvelope.Payload), &intotoStatement); err != nil {
// Don't fail just because a single entry can't be unmarshalled
continue
}
if intotoStatement.PredicateType == predicateType {
filteredAttestations = append(filteredAttestations, each)
}
}
}
if len(filteredAttestations) == 0 {
return nil, fmt.Errorf("no attestations found with predicate type: %s", predicateType)
}
return filteredAttestations, nil
}