From 56d924d25b39bb30497ba26c268d71428d794880 Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Tue, 1 Apr 2025 12:58:37 -0600 Subject: [PATCH] getAttestations unit tests Signed-off-by: Meredith Lancaster --- pkg/cmd/attestation/verify/attestation.go | 18 +-- .../verify/attestation_integration_test.go | 119 ------------------ .../attestation/verify/attestation_test.go | 53 ++++++++ 3 files changed, 63 insertions(+), 127 deletions(-) delete mode 100644 pkg/cmd/attestation/verify/attestation_integration_test.go create mode 100644 pkg/cmd/attestation/verify/attestation_test.go diff --git a/pkg/cmd/attestation/verify/attestation.go b/pkg/cmd/attestation/verify/attestation.go index 2a935a56c..68d38f985 100644 --- a/pkg/cmd/attestation/verify/attestation.go +++ b/pkg/cmd/attestation/verify/attestation.go @@ -41,30 +41,32 @@ func getAttestations(o *Options, a artifact.DigestedArtifact) ([]*api.Attestatio // Predicate type filtering is done after the attestations are fetched var attestations []*api.Attestation var err error - var errMsg string + var msg string if o.BundlePath != "" { attestations, err = verification.GetLocalAttestations(o.BundlePath) if err != nil { - errMsg = fmt.Sprintf("✗ Loading attestations from %s failed", a.URL) + pluralAttestation := text.Pluralize(len(attestations), "attestation") + msg = fmt.Sprintf("Loaded %s from %s", pluralAttestation, o.BundlePath) + } else { + msg = fmt.Sprintf("Loaded %d attestations from %s", len(attestations), o.BundlePath) } } else if o.UseBundleFromRegistry { attestations, err = verification.GetOCIAttestations(o.OCIClient, a) if err != nil { - errMsg = "✗ Loading attestations from OCI registry failed" + msg = "✗ Loading attestations from OCI registry failed" + } else { + pluralAttestation := text.Pluralize(len(attestations), "attestation") + msg = fmt.Sprintf("Loaded %s from OCI registry", pluralAttestation) } } - if err != nil { - return nil, errMsg, err + return nil, msg, err } filtered, err := verification.FilterAttestations(o.PredicateType, attestations) if err != nil { return nil, err.Error(), err } - - pluralAttestation := text.Pluralize(len(filtered), "attestation") - msg := fmt.Sprintf("Loaded %s from %s", pluralAttestation, o.BundlePath) return filtered, msg, nil } diff --git a/pkg/cmd/attestation/verify/attestation_integration_test.go b/pkg/cmd/attestation/verify/attestation_integration_test.go deleted file mode 100644 index 9ff174141..000000000 --- a/pkg/cmd/attestation/verify/attestation_integration_test.go +++ /dev/null @@ -1,119 +0,0 @@ -//go:build integration - -package verify - -import ( - "testing" - - "github.com/cli/cli/v2/pkg/cmd/attestation/api" - "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" - "github.com/cli/cli/v2/pkg/cmd/attestation/io" - "github.com/cli/cli/v2/pkg/cmd/attestation/test" - "github.com/cli/cli/v2/pkg/cmd/attestation/verification" - o "github.com/cli/cli/v2/pkg/option" - "github.com/sigstore/sigstore-go/pkg/fulcio/certificate" - "github.com/stretchr/testify/require" -) - -func getAttestationsFor(t *testing.T, bundlePath string) []*api.Attestation { - t.Helper() - - attestations, err := verification.GetLocalAttestations(bundlePath) - require.NoError(t, err) - - return attestations -} - -func TestVerifyAttestations(t *testing.T) { - sgVerifier := verification.NewLiveSigstoreVerifier(verification.SigstoreConfig{ - Logger: io.NewTestHandler(), - TUFMetadataDir: o.Some(t.TempDir()), - }) - - certSummary := certificate.Summary{} - certSummary.SourceRepositoryOwnerURI = "https://github.com/sigstore" - certSummary.SourceRepositoryURI = "https://github.com/sigstore/sigstore-js" - certSummary.Issuer = verification.GitHubOIDCIssuer - - ec := verification.EnforcementCriteria{ - Certificate: certSummary, - PredicateType: verification.SLSAPredicateV1, - SANRegex: "^https://github.com/sigstore/", - } - require.NoError(t, ec.Valid()) - - artifactPath := test.NormalizeRelativePath("../test/data/sigstore-js-2.1.0.tgz") - a, err := artifact.NewDigestedArtifact(nil, artifactPath, "sha512") - require.NoError(t, err) - - t.Run("all attestations pass verification", func(t *testing.T) { - attestations := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl") - require.Len(t, attestations, 2) - results, errMsg, err := verifyAttestations(*a, attestations, sgVerifier, ec) - require.NoError(t, err) - require.Zero(t, errMsg) - require.Len(t, results, 2) - }) - - t.Run("passes verification with 2/3 attestations passing Sigstore verification", func(t *testing.T) { - invalidBundle := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0-bundle-v0.1.json") - attestations := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl") - attestations = append(attestations, invalidBundle[0]) - require.Len(t, attestations, 3) - - results, errMsg, err := verifyAttestations(*a, attestations, sgVerifier, ec) - require.NoError(t, err) - require.Zero(t, errMsg) - require.Len(t, results, 2) - }) - - t.Run("fails verification when Sigstore verification fails", func(t *testing.T) { - invalidBundle := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0-bundle-v0.1.json") - invalidBundle2 := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0-bundle-v0.1.json") - attestations := append(invalidBundle, invalidBundle2...) - require.Len(t, attestations, 2) - - results, errMsg, err := verifyAttestations(*a, attestations, sgVerifier, ec) - require.Error(t, err) - require.Contains(t, errMsg, "✗ Sigstore verification failed") - require.Nil(t, results) - }) - - t.Run("attestations fail to verify when cert extensions don't match enforcement criteria", func(t *testing.T) { - sgjAttestation := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl") - reusableWorkflowAttestations := getAttestationsFor(t, "../test/data/reusable-workflow-attestation.sigstore.json") - attestations := []*api.Attestation{sgjAttestation[0], reusableWorkflowAttestations[0], sgjAttestation[1]} - require.Len(t, attestations, 3) - - rwfResult := verification.BuildMockResult(reusableWorkflowAttestations[0].Bundle, "", "", "https://github.com/malancas", "", verification.GitHubOIDCIssuer) - sgjResult := verification.BuildSigstoreJsMockResult(t) - mockResults := []*verification.AttestationProcessingResult{&sgjResult, &rwfResult, &sgjResult} - mockSgVerifier := verification.NewMockSigstoreVerifierWithMockResults(t, mockResults) - - // we want to test that attestations that pass Sigstore verification but fail - // cert extension verification are filtered out properly in the second step - // in verifyAttestations. By using a mock Sigstore verifier, we can ensure - // that the call to verification.VerifyCertExtensions in verifyAttestations - // is filtering out attestations as expected - results, errMsg, err := verifyAttestations(*a, attestations, mockSgVerifier, ec) - require.NoError(t, err) - require.Zero(t, errMsg) - require.Len(t, results, 2) - for _, result := range results { - require.NotEqual(t, result.Attestation.Bundle, reusableWorkflowAttestations[0].Bundle) - } - }) - - t.Run("fails verification when cert extension verification fails", func(t *testing.T) { - attestations := getAttestationsFor(t, "../test/data/sigstore-js-2.1.0_with_2_bundles.jsonl") - require.Len(t, attestations, 2) - - expectedCriteria := ec - expectedCriteria.Certificate.SourceRepositoryOwnerURI = "https://github.com/wrong" - - results, errMsg, err := verifyAttestations(*a, attestations, sgVerifier, expectedCriteria) - require.Error(t, err) - require.Contains(t, errMsg, "✗ Policy verification failed") - require.Nil(t, results) - }) -} diff --git a/pkg/cmd/attestation/verify/attestation_test.go b/pkg/cmd/attestation/verify/attestation_test.go new file mode 100644 index 000000000..93ac7d327 --- /dev/null +++ b/pkg/cmd/attestation/verify/attestation_test.go @@ -0,0 +1,53 @@ +package verify + +import ( + "testing" + + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" + "github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci" + "github.com/cli/cli/v2/pkg/cmd/attestation/verification" + "github.com/stretchr/testify/require" +) + +func TestGetAttestations_OCIRegistry_PredicateTypeFiltering(t *testing.T) { + artifact, err := artifact.NewDigestedArtifact(nil, "../test/data/gh_2.60.1_windows_arm64.zip", "sha256") + require.NoError(t, err) + + o := &Options{ + OCIClient: oci.MockClient{}, + PredicateType: verification.SLSAPredicateV1, + Repo: "cli/cli", + UseBundleFromRegistry: true, + } + attestations, msg, err := getAttestations(o, *artifact) + require.NoError(t, err) + require.Contains(t, msg, "Loaded 2 attestations from OCI registry") + require.Len(t, attestations, 2) + + o.PredicateType = "custom predicate type" + attestations, msg, err = getAttestations(o, *artifact) + require.Error(t, err) + require.Contains(t, msg, "no attestations found with predicate type") + require.Nil(t, attestations) +} + +func TestGetAttestations_LocalBundle_PredicateTypeFiltering(t *testing.T) { + artifact, err := artifact.NewDigestedArtifact(nil, "../test/data/gh_2.60.1_windows_arm64.zip", "sha256") + require.NoError(t, err) + + o := &Options{ + BundlePath: "../test/data/sigstore-js-2.1.0-bundle.json", + PredicateType: verification.SLSAPredicateV1, + Repo: "sigstore/sigstore-js", + } + attestations, msg, err := getAttestations(o, *artifact) + require.NoError(t, err) + require.Contains(t, msg, "Loaded 1 attestation from ../test/data/gh_2.60.1_windows_arm64.zip") + require.Len(t, attestations, 1) + + o.PredicateType = "custom predicate type" + attestations, msg, err = getAttestations(o, *artifact) + require.Error(t, err) + require.Contains(t, msg, "no attestations found with predicate type") + require.Nil(t, attestations) +}