diff --git a/pkg/cmd/attestation/verification/attestation.go b/pkg/cmd/attestation/verification/attestation.go index 50542a6b3..0ea91c2f7 100644 --- a/pkg/cmd/attestation/verification/attestation.go +++ b/pkg/cmd/attestation/verification/attestation.go @@ -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") diff --git a/pkg/cmd/attestation/verification/mock_verifier.go b/pkg/cmd/attestation/verification/mock_verifier.go index cb3a4c061..e22142ed5 100644 --- a/pkg/cmd/attestation/verification/mock_verifier.go +++ b/pkg/cmd/attestation/verification/mock_verifier.go @@ -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{ diff --git a/pkg/cmd/attestation/verification/sigstore.go b/pkg/cmd/attestation/verification/sigstore.go index e237a3eb9..5b4f4a79b 100644 --- a/pkg/cmd/attestation/verification/sigstore.go +++ b/pkg/cmd/attestation/verification/sigstore.go @@ -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 { diff --git a/pkg/cmd/attestation/verify/verify.go b/pkg/cmd/attestation/verify/verify.go index d14081dd8..206001f9b 100644 --- a/pkg/cmd/attestation/verify/verify.go +++ b/pkg/cmd/attestation/verify/verify.go @@ -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 /") 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")) diff --git a/pkg/cmd/attestation/verify/verify_integration_test.go b/pkg/cmd/attestation/verify/verify_integration_test.go index 0b15e823e..781cb4df1 100644 --- a/pkg/cmd/attestation/verify/verify_integration_test.go +++ b/pkg/cmd/attestation/verify/verify_integration_test.go @@ -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), } diff --git a/pkg/cmd/attestation/verify/verify_test.go b/pkg/cmd/attestation/verify/verify_test.go index 93ad4bdbc..9a2e9f18c 100644 --- a/pkg/cmd/attestation/verify/verify_test.go +++ b/pkg/cmd/attestation/verify/verify_test.go @@ -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) {