Merge pull request #9825 from malancas/verify-provenance-predicate-by-default

`gh attestation verify` should only verify provenance attestations by default
This commit is contained in:
Meredith Lancaster 2024-10-30 15:45:18 -06:00 committed by GitHub
commit 1652051fc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 76 additions and 37 deletions

View file

@ -15,6 +15,8 @@ import (
"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")

View file

@ -12,15 +12,13 @@ import (
"github.com/sigstore/sigstore-go/pkg/verify"
)
const SLSAPredicateType = "https://slsa.dev/provenance/v1"
type MockSigstoreVerifier struct {
t *testing.T
}
func (v *MockSigstoreVerifier) Verify(attestations []*api.Attestation, policy verify.PolicyBuilder) *SigstoreResults {
statement := &in_toto.Statement{}
statement.PredicateType = SLSAPredicateType
statement.PredicateType = SLSAPredicateV1
result := AttestationProcessingResult{
Attestation: &api.Attestation{

View file

@ -113,7 +113,7 @@ func (v *LiveSigstoreVerifier) chooseVerifier(b *bundle.Bundle) (*verify.SignedE
// issuer. We *must* use the trusted root provided.
if issuer == PublicGoodIssuerOrg {
if v.config.NoPublicGood {
return nil, "", fmt.Errorf("Detected public good instance but requested verification without public good instance")
return nil, "", fmt.Errorf("detected public good instance but requested verification without public good instance")
}
verifier, err := newPublicGoodVerifierWithTrustedRoot(trustedRoot)
if err != nil {

View file

@ -52,10 +52,15 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command
The %[1]s--owner%[1]s flag value must match the name of the GitHub organization
that the artifact's linked repository belongs to.
By default, the verify command will attempt to fetch attestations associated
with the provided artifact from the GitHub API. If you would prefer to verify
the artifact using attestations stored on disk (c.f. the %[1]sdownload%[1]s command),
provide a path to the %[1]s--bundle%[1]s flag.
By default, the verify command will:
- only verify provenance attestations
- attempt to fetch relevant attestations via the GitHub API.
To verify other types of attestations, use the %[1]s--predicate-type%[1]s flag.
To use your artifact's OCI registry instead of GitHub's API, use the
%[1]s--bundle-from-oci%[1]s flag. For offline verification, using attestations
stored on desk (c.f. the download command), provide a path to the %[1]s--bundle%[1]s flag.
To see the full results that are generated upon successful verification, i.e.
for use with a policy engine, provide the %[1]s--format=json%[1]s flag.
@ -179,7 +184,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().StringVarP(&opts.PredicateType, "predicate-type", "", verification.SLSAPredicateV1, "Filter attestations by provided predicate type")
verifyCmd.Flags().BoolVarP(&opts.NoPublicGood, "no-public-good", "", false, "Do not verify attestations signed with Sigstore public good instance")
verifyCmd.Flags().StringVarP(&opts.TrustedRoot, "custom-trusted-root", "", "", "Path to a trusted_root.jsonl file; likely for offline verification")
verifyCmd.Flags().IntVarP(&opts.Limit, "limit", "L", api.DefaultLimit, "Maximum number of attestations to fetch")
@ -244,16 +249,12 @@ func runVerify(opts *Options) error {
}
// Apply predicate type filter to returned attestations
if opts.PredicateType != "" {
filteredAttestations := verification.FilterAttestations(opts.PredicateType, attestations)
if len(filteredAttestations) == 0 {
opts.Logger.Printf(opts.Logger.ColorScheme.Red("✗ No attestations found with predicate type: %s\n"), opts.PredicateType)
return err
}
attestations = filteredAttestations
filteredAttestations := verification.FilterAttestations(opts.PredicateType, attestations)
if len(filteredAttestations) == 0 {
opts.Logger.Printf(opts.Logger.ColorScheme.Red("✗ No attestations found with predicate type: %s\n"), opts.PredicateType)
return err
}
attestations = filteredAttestations
policy, err := buildVerifyPolicy(opts, *artifact)
if err != nil {
@ -261,6 +262,8 @@ func runVerify(opts *Options) error {
return err
}
opts.Logger.VerbosePrintf("Verifying attestations with predicate type: %s\n", opts.PredicateType)
sigstoreRes := opts.SigstoreVerifier.Verify(attestations, policy)
if sigstoreRes.Error != nil {
opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Verification failed"))

View file

@ -40,6 +40,7 @@ func TestVerifyIntegration(t *testing.T) {
OCIClient: oci.NewLiveClient(),
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SANRegex: "^https://github.com/sigstore/",
SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
}
@ -139,6 +140,7 @@ func TestVerifyIntegrationCustomIssuer(t *testing.T) {
Logger: logger,
OCIClient: oci.NewLiveClient(),
OIDCIssuer: "https://token.actions.githubusercontent.com/hammer-time",
PredicateType: verification.SLSAPredicateV1,
SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
}
@ -208,6 +210,7 @@ func TestVerifyIntegrationReusableWorkflow(t *testing.T) {
Logger: logger,
OCIClient: oci.NewLiveClient(),
OIDCIssuer: verification.GitHubOIDCIssuer,
PredicateType: verification.SLSAPredicateV1,
SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
}
@ -298,6 +301,7 @@ func TestVerifyIntegrationReusableWorkflowSignerWorkflow(t *testing.T) {
OCIClient: oci.NewLiveClient(),
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "malancas",
PredicateType: verification.SLSAPredicateV1,
Repo: "malancas/attest-demo",
SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
}

View file

@ -70,11 +70,12 @@ func TestNewVerifyCmd(t *testing.T) {
ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
DigestAlgorithm: "sha384",
Hostname: "github.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "github.com",
},
wantsErr: true,
},
@ -85,12 +86,13 @@ func TestNewVerifyCmd(t *testing.T) {
ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
DigestAlgorithm: "sha256",
Hostname: "github.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SANRegex: "(?i)^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "github.com",
},
wantsErr: false,
},
@ -101,12 +103,13 @@ func TestNewVerifyCmd(t *testing.T) {
ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
DigestAlgorithm: "sha256",
Hostname: "foo.ghe.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SANRegex: "(?i)^https://foo.ghe.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "foo.ghe.com",
},
wantsErr: false,
},
@ -117,12 +120,13 @@ func TestNewVerifyCmd(t *testing.T) {
ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
DigestAlgorithm: "sha256",
Hostname: "foo.ghe.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SANRegex: "(?i)^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "foo.ghe.com",
},
wantsErr: true,
},
@ -133,12 +137,13 @@ func TestNewVerifyCmd(t *testing.T) {
ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
BundlePath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0-bundle.json"),
DigestAlgorithm: "sha512",
Hostname: "github.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SANRegex: "(?i)^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "github.com",
},
wantsErr: false,
},
@ -148,12 +153,13 @@ func TestNewVerifyCmd(t *testing.T) {
wants: Options{
ArtifactPath: test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz"),
DigestAlgorithm: "sha256",
Hostname: "github.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
Limit: 30,
PredicateType: verification.SLSAPredicateV1,
SANRegex: "(?i)^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "github.com",
},
wantsErr: true,
},
@ -163,12 +169,13 @@ func TestNewVerifyCmd(t *testing.T) {
wants: Options{
ArtifactPath: artifactPath,
DigestAlgorithm: "sha256",
Hostname: "github.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
Repo: "sigstore/sigstore-js",
Limit: 30,
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "github.com",
},
wantsErr: true,
},
@ -178,12 +185,13 @@ func TestNewVerifyCmd(t *testing.T) {
wants: Options{
ArtifactPath: artifactPath,
DigestAlgorithm: "sha256",
Hostname: "github.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SANRegex: "(?i)^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "github.com",
},
wantsErr: false,
},
@ -193,12 +201,13 @@ func TestNewVerifyCmd(t *testing.T) {
wants: Options{
ArtifactPath: artifactPath,
DigestAlgorithm: "sha256",
Hostname: "github.com",
Limit: 101,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
Limit: 101,
PredicateType: verification.SLSAPredicateV1,
SANRegex: "(?i)^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "github.com",
},
wantsErr: false,
},
@ -208,12 +217,13 @@ func TestNewVerifyCmd(t *testing.T) {
wants: Options{
ArtifactPath: artifactPath,
DigestAlgorithm: "sha256",
Hostname: "github.com",
Limit: 0,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
Limit: 0,
PredicateType: verification.SLSAPredicateV1,
SANRegex: "(?i)^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "github.com",
},
wantsErr: true,
},
@ -223,13 +233,14 @@ func TestNewVerifyCmd(t *testing.T) {
wants: Options{
ArtifactPath: artifactPath,
DigestAlgorithm: "sha256",
Hostname: "github.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SAN: "https://github.com/sigstore/",
SANRegex: "(?i)^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
Hostname: "github.com",
},
wantsErr: true,
},
@ -240,12 +251,30 @@ func TestNewVerifyCmd(t *testing.T) {
ArtifactPath: artifactPath,
BundlePath: bundlePath,
DigestAlgorithm: "sha256",
Hostname: "github.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SANRegex: "(?i)^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
},
wantsExporter: true,
},
{
name: "Use specified predicate type",
cli: fmt.Sprintf("%s --bundle %s --owner sigstore --predicate-type https://spdx.dev/Document/v2.3 --format json", artifactPath, bundlePath),
wants: Options{
ArtifactPath: artifactPath,
BundlePath: bundlePath,
DigestAlgorithm: "sha256",
Hostname: "github.com",
Limit: 30,
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: "https://spdx.dev/Document/v2.3",
SANRegex: "(?i)^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
},
wantsExporter: true,
},
@ -273,17 +302,18 @@ func TestNewVerifyCmd(t *testing.T) {
assert.Equal(t, tc.wants.ArtifactPath, opts.ArtifactPath)
assert.Equal(t, tc.wants.BundlePath, opts.BundlePath)
assert.Equal(t, tc.wants.TrustedRoot, opts.TrustedRoot)
assert.Equal(t, tc.wants.DenySelfHostedRunner, opts.DenySelfHostedRunner)
assert.Equal(t, tc.wants.DigestAlgorithm, opts.DigestAlgorithm)
assert.Equal(t, tc.wants.Hostname, opts.Hostname)
assert.Equal(t, tc.wants.Limit, opts.Limit)
assert.Equal(t, tc.wants.NoPublicGood, opts.NoPublicGood)
assert.Equal(t, tc.wants.OIDCIssuer, opts.OIDCIssuer)
assert.Equal(t, tc.wants.Owner, opts.Owner)
assert.Equal(t, tc.wants.PredicateType, opts.PredicateType)
assert.Equal(t, tc.wants.Repo, opts.Repo)
assert.Equal(t, tc.wants.SAN, opts.SAN)
assert.Equal(t, tc.wants.SANRegex, opts.SANRegex)
assert.Equal(t, tc.wants.Hostname, opts.Hostname)
assert.Equal(t, tc.wants.TrustedRoot, opts.TrustedRoot)
assert.NotNil(t, opts.APIClient)
assert.NotNil(t, opts.Logger)
assert.NotNil(t, opts.OCIClient)
@ -333,11 +363,12 @@ func TestJSONOutput(t *testing.T) {
OCIClient: oci.MockClient{},
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SANRegex: "^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
exporter: cmdutil.NewJSONExporter(),
}
require.Nil(t, runVerify(&opts))
require.NoError(t, runVerify(&opts))
var target []*verification.AttestationProcessingResult
err := json.Unmarshal(out.Bytes(), &target)
@ -356,12 +387,13 @@ func TestRunVerify(t *testing.T) {
OCIClient: oci.MockClient{},
OIDCIssuer: verification.GitHubOIDCIssuer,
Owner: "sigstore",
PredicateType: verification.SLSAPredicateV1,
SANRegex: "^https://github.com/sigstore/",
SigstoreVerifier: verification.NewMockSigstoreVerifier(t),
}
t.Run("with valid artifact and bundle", func(t *testing.T) {
require.Nil(t, runVerify(&publicGoodOpts))
require.NoError(t, runVerify(&publicGoodOpts))
})
t.Run("with failing OCI artifact fetch", func(t *testing.T) {