diff --git a/pkg/cmd/attestation/download/download.go b/pkg/cmd/attestation/download/download.go index 2f40970fc..ebc884590 100644 --- a/pkg/cmd/attestation/download/download.go +++ b/pkg/cmd/attestation/download/download.go @@ -102,6 +102,7 @@ func NewDownloadCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Comman downloadCmd.Flags().StringVarP(&opts.Repo, "repo", "R", "", "Repository name in the format /") downloadCmd.MarkFlagsMutuallyExclusive("owner", "repo") downloadCmd.MarkFlagsOneRequired("owner", "repo") + downloadCmd.Flags().StringVarP(&opts.PredicateType, "predicate-type", "", "", "Filter attestations by provided predicate type") cmdutil.StringEnumFlag(downloadCmd, &opts.DigestAlgorithm, "digest-alg", "d", "sha256", []string{"sha256", "sha512"}, "The algorithm used to compute a digest of the artifact") downloadCmd.Flags().IntVarP(&opts.Limit, "limit", "L", api.DefaultLimit, "Maximum number of attestations to fetch") @@ -132,6 +133,17 @@ func runDownload(opts *Options) error { return fmt.Errorf("failed to fetch attestations: %v", err) } + // Apply predicate type filter to returned attestations + if opts.PredicateType != "" { + filteredAttestations := verification.FilterAttestations(opts.PredicateType, attestations) + + if len(filteredAttestations) == 0 { + return fmt.Errorf("no attestations found with predicate type: %s", opts.PredicateType) + } + + attestations = filteredAttestations + } + metadataFilePath, err := opts.Store.createMetadataFile(artifact.DigestWithAlg(), attestations) if err != nil { return fmt.Errorf("failed to write attestation: %v", err) diff --git a/pkg/cmd/attestation/download/options.go b/pkg/cmd/attestation/download/options.go index 6a64ecc30..cc1c4d3b5 100644 --- a/pkg/cmd/attestation/download/options.go +++ b/pkg/cmd/attestation/download/options.go @@ -22,6 +22,7 @@ type Options struct { Store MetadataStore OCIClient oci.Client Owner string + PredicateType string Repo string } diff --git a/pkg/cmd/attestation/verification/attestation.go b/pkg/cmd/attestation/verification/attestation.go index 7fb4a1615..7c93817be 100644 --- a/pkg/cmd/attestation/verification/attestation.go +++ b/pkg/cmd/attestation/verification/attestation.go @@ -2,6 +2,7 @@ package verification import ( "bufio" + "encoding/json" "errors" "fmt" "os" @@ -113,3 +114,27 @@ func GetRemoteAttestations(c FetchAttestationsConfig) ([]*api.Attestation, error } return nil, fmt.Errorf("owner or repo must be provided") } + +type DssePayload struct { + PredicateType string `json:"predicateType"` +} + +func FilterAttestations(predicateType string, attestations []*api.Attestation) []*api.Attestation { + filteredAttestations := []*api.Attestation{} + + for _, each := range attestations { + dsseEnvelope := each.Bundle.GetDsseEnvelope() + if dsseEnvelope != nil { + var dssePayload DssePayload + if err := json.Unmarshal([]byte(dsseEnvelope.Payload), &dssePayload); err != nil { + // Don't fail just because a single entry can't be unmarshalled + continue + } + if dssePayload.PredicateType == predicateType { + filteredAttestations = append(filteredAttestations, each) + } + } + } + + return filteredAttestations +} diff --git a/pkg/cmd/attestation/verify/options.go b/pkg/cmd/attestation/verify/options.go index d7742bf3a..dc6acd124 100644 --- a/pkg/cmd/attestation/verify/options.go +++ b/pkg/cmd/attestation/verify/options.go @@ -21,6 +21,7 @@ type Options struct { NoPublicGood bool OIDCIssuer string Owner string + PredicateType string Repo string SAN string SANRegex string diff --git a/pkg/cmd/attestation/verify/policy.go b/pkg/cmd/attestation/verify/policy.go index 83d1f62a7..b3d8448f5 100644 --- a/pkg/cmd/attestation/verify/policy.go +++ b/pkg/cmd/attestation/verify/policy.go @@ -12,7 +12,6 @@ import ( const ( GitHubOIDCIssuer = "https://token.actions.githubusercontent.com" - SLSAPredicateType = "https://slsa.dev/provenance/v1" // represents the GitHub hosted runner in the certificate RunnerEnvironment extension GitHubRunner = "github-hosted" ) diff --git a/pkg/cmd/attestation/verify/verify.go b/pkg/cmd/attestation/verify/verify.go index 718f893a2..d8b93dd3f 100644 --- a/pkg/cmd/attestation/verify/verify.go +++ b/pkg/cmd/attestation/verify/verify.go @@ -1,7 +1,6 @@ package verify import ( - // "encoding/json" "errors" "fmt" @@ -17,8 +16,6 @@ import ( "github.com/spf13/cobra" ) -var ErrNoMatchingSLSAPredicate = fmt.Errorf("the attestation does not have the expected SLSA predicate type: %s", SLSAPredicateType) - func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command { opts := &Options{} verifyCmd := &cobra.Command{ @@ -120,6 +117,7 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command verifyCmd.Flags().StringVarP(&opts.Repo, "repo", "R", "", "Repository name in the format /") verifyCmd.MarkFlagsMutuallyExclusive("owner", "repo") verifyCmd.MarkFlagsOneRequired("owner", "repo") + verifyCmd.Flags().StringVarP(&opts.PredicateType, "predicate-type", "", "", "Filter attestations by provided predicate type") verifyCmd.Flags().BoolVarP(&opts.NoPublicGood, "no-public-good", "", false, "Only verify attestations signed with GitHub's Sigstore instance") verifyCmd.Flags().StringVarP(&opts.CustomTrustedRoot, "custom-trusted-root", "", "", "Path to a custom trustedroot.json file to use for verification") verifyCmd.Flags().IntVarP(&opts.Limit, "limit", "L", api.DefaultLimit, "Maximum number of attestations to fetch") @@ -158,6 +156,17 @@ func runVerify(opts *Options) error { return fmt.Errorf("failed to fetch attestations for subject: %s", artifact.DigestWithAlg()) } + // Apply predicate type filter to returned attestations + if opts.PredicateType != "" { + filteredAttestations := verification.FilterAttestations(opts.PredicateType, attestations) + + if len(filteredAttestations) == 0 { + return fmt.Errorf("no attestations found with predicate type: %s", opts.PredicateType) + } + + attestations = filteredAttestations + } + policy, err := buildVerifyPolicy(opts, *artifact) if err != nil { return fmt.Errorf("failed to build policy: %v", err) @@ -183,11 +192,6 @@ func runVerify(opts *Options) error { "Successfully verified all attestations against Sigstore!\n", )) - // Try verifying the attestation's predicate type against the expect SLSA predicate type - if err = verifySLSAPredicateType(opts.Logger, sigstoreRes.VerifyResults); err != nil { - return fmt.Errorf("at least one attestation failed to verify predicate type verification: %v", err) - } - opts.Logger.VerbosePrint(opts.Logger.ColorScheme.Green("Successfully verified the SLSA predicate type of all attestations!\n")) opts.Logger.Println(opts.Logger.ColorScheme.Green("All attestations have been successfully verified!")) @@ -202,15 +206,3 @@ func runVerify(opts *Options) error { // All attestations passed verification and policy evaluation return nil } - -func verifySLSAPredicateType(logger *io.Handler, apr []*verification.AttestationProcessingResult) error { - logger.VerbosePrint("Evaluating attestations have valid SLSA predicate type") - - for _, result := range apr { - if result.VerificationResult.Statement.PredicateType != SLSAPredicateType { - return ErrNoMatchingSLSAPredicate - } - } - - return nil -} diff --git a/pkg/cmd/attestation/verify/verify_test.go b/pkg/cmd/attestation/verify/verify_test.go index b4cd864fc..d1849c30a 100644 --- a/pkg/cmd/attestation/verify/verify_test.go +++ b/pkg/cmd/attestation/verify/verify_test.go @@ -19,9 +19,6 @@ import ( "github.com/cli/cli/v2/pkg/iostreams" "github.com/stretchr/testify/assert" - "github.com/in-toto/in-toto-golang/in_toto" - "github.com/sigstore/sigstore-go/pkg/verify" - "github.com/stretchr/testify/require" ) @@ -382,20 +379,3 @@ func TestRunVerify(t *testing.T) { require.Error(t, runVerify(&customOpts)) }) } - -func TestVerifySLSAPredicateType_InvalidPredicate(t *testing.T) { - statement := &in_toto.Statement{} - statement.PredicateType = "some-other-predicate-type" - - apr := []*verification.AttestationProcessingResult{ - { - VerificationResult: &verify.VerificationResult{ - Statement: statement, - }, - }, - } - - err := verifySLSAPredicateType(io.NewTestHandler(), apr) - require.Error(t, err) - require.ErrorIs(t, err, ErrNoMatchingSLSAPredicate) -}