github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/attestation/sbom/rekor.go (about) 1 package sbom 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 9 "github.com/in-toto/in-toto-golang/in_toto" 10 "github.com/samber/lo" 11 "golang.org/x/xerrors" 12 13 "github.com/devseccon/trivy/pkg/attestation" 14 "github.com/devseccon/trivy/pkg/log" 15 "github.com/devseccon/trivy/pkg/rekor" 16 ) 17 18 var ErrNoSBOMAttestation = xerrors.New("no SBOM attestation found") 19 20 type Rekor struct { 21 client *rekor.Client 22 } 23 24 func NewRekor(url string) (Rekor, error) { 25 c, err := rekor.NewClient(url) 26 if err != nil { 27 return Rekor{}, xerrors.Errorf("rekor client error: %w", err) 28 } 29 return Rekor{ 30 client: c, 31 }, nil 32 } 33 34 func (r *Rekor) RetrieveSBOM(ctx context.Context, digest string) ([]byte, error) { 35 entryIDs, err := r.client.Search(ctx, digest) 36 if err != nil { 37 return nil, xerrors.Errorf("failed to search rekor records: %w", err) 38 } else if len(entryIDs) == 0 { 39 return nil, ErrNoSBOMAttestation 40 } 41 42 log.Logger.Debugf("Found matching Rekor entries: %s", entryIDs) 43 44 for _, ids := range lo.Chunk[rekor.EntryID](entryIDs, rekor.MaxGetEntriesLimit) { 45 entries, err := r.client.GetEntries(ctx, ids) 46 if err != nil { 47 return nil, xerrors.Errorf("failed to get entries: %w", err) 48 } 49 50 for _, entry := range entries { 51 ref, err := r.inspectRecord(entry) 52 if errors.Is(err, ErrNoSBOMAttestation) { 53 continue 54 } else if err != nil { 55 return nil, xerrors.Errorf("rekor record inspection error: %w", err) 56 } 57 return ref, nil 58 } 59 } 60 return nil, ErrNoSBOMAttestation 61 } 62 63 func (r *Rekor) inspectRecord(entry rekor.Entry) ([]byte, error) { 64 // TODO: Trivy SBOM should take precedence 65 raw, err := r.parseStatement(entry) 66 if err != nil { 67 return nil, err 68 } 69 return raw, nil 70 } 71 72 func (r *Rekor) parseStatement(entry rekor.Entry) (json.RawMessage, error) { 73 // Skip base64-encoded attestation 74 if bytes.HasPrefix(entry.Statement, []byte(`eyJ`)) { 75 return nil, ErrNoSBOMAttestation 76 } 77 78 // Parse statement of in-toto attestation 79 var raw json.RawMessage 80 statement := &in_toto.Statement{ 81 Predicate: &attestation.CosignPredicate{ 82 Data: &raw, // Extract CycloneDX or SPDX 83 }, 84 } 85 if err := json.Unmarshal(entry.Statement, &statement); err != nil { 86 return nil, xerrors.Errorf("attestation parse error: %w", err) 87 } 88 89 // TODO: add support for SPDX 90 if statement.PredicateType != in_toto.PredicateCycloneDX { 91 return nil, xerrors.Errorf("unsupported predicate type %s: %w", statement.PredicateType, ErrNoSBOMAttestation) 92 } 93 return raw, nil 94 }