Add support to attestation command for more predicate types.

Before, we required all attestations have predicateType
https://slsa.dev/provenance/v1. This allows you to use other predicate
types, and adds the ability to filter responses from the API for a
particular predicate type.

Signed-off-by: Zach Steindler <steiza@github.com>
This commit is contained in:
Zach Steindler 2024-04-09 17:15:21 -04:00
parent a76230454a
commit 643f4031b2
7 changed files with 51 additions and 41 deletions

View file

@ -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 <owner>/<repo>")
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)

View file

@ -22,6 +22,7 @@ type Options struct {
Store MetadataStore
OCIClient oci.Client
Owner string
PredicateType string
Repo string
}

View file

@ -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
}

View file

@ -21,6 +21,7 @@ type Options struct {
NoPublicGood bool
OIDCIssuer string
Owner string
PredicateType string
Repo string
SAN string
SANRegex string

View file

@ -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"
)

View file

@ -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 <owner>/<repo>")
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
}

View file

@ -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)
}