Files
slsa-verifier/cli/slsa-verifier/main_regression_test.go
Ramon Petgrave 208ac12589 feat: vsa support (#777)
Fixes #542

Adds support for VSAs.

## Testing process

- added some unit an end-to-end tests
- manually invoking

    ```
    go run ./cli/slsa-verifier/ verify-vsa \
    --subject-digest gce_image_id:8970095005306000053 \
--attestation-path
./cli/slsa-verifier/testdata/vsa/gce/v1/gke-gce-pre.bcid-vsa.jsonl \
--verifier-id
https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1 \
--resource-uri
gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre
\
    --verified-level BCID_L1 \
    --verified-level SLSA_BUILD_LEVEL_2 \
--public-key-path
./cli/slsa-verifier/testdata/vsa/gce/v1/vsa_signing_public_key.pem \
    --public-key-id keystore://76574:prod:vsa_signing_public_key \
    --print-attestation



{"_type":"https://in-toto.io/Statement/v1","predicateType":"https://slsa.dev/verification_summary/v1","predicate":{"timeVerified":"2024-06-12T07:24:34.351608Z","verifier":{"id":"https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"},"verificationResult":"PASSED","verifiedLevels":["BCID_L1","SLSA_BUILD_LEVEL_2"],"resourceUri":"gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre","policy":{"uri":"googlefile:/google_src/files/642513192/depot/google3/production/security/bcid/software/gce_image/gke/vm_images.sw_policy.textproto"}},"subject":[{"name":"_","digest":{"gce_image_id":"8970095005306000053"}}]}
    Verifying VSA: PASSED
    
    PASSED: SLSA verification passed
    ```

TODOS:
- open issue on the in_toto attestations repo about the incorrect json
[fields](36c1129542/go/predicates/vsa/v1/vsa.pb.go (L26-L40))
for vsa 1.0

---------

Signed-off-by: Ramon Petgrave <ramon.petgrave64@gmail.com>
2024-07-10 21:25:16 -04:00

1869 lines
66 KiB
Go

//go:build regression
package main
import (
"bytes"
"context"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"golang.org/x/mod/semver"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/sigstore/cosign/v2/pkg/oci"
"github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier/verify"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils/container"
)
func errCmp(e1, e2 error) bool {
return errors.Is(e1, e2) || errors.Is(e2, e1)
}
func pString(s string) *string {
return &s
}
const TEST_DIR = "./testdata"
var (
GHA_ARTIFACT_PATH_BUILDERS = []string{"gha_go", "gha_generic", "gha_delegator", "gha_maven", "gha_gradle"}
// TODO(https://github.com/slsa-framework/slsa-verifier/issues/485): Merge this with
// GHA_ARTIFACT_PATH_BUILDERS.
GHA_ARTIFACT_CONTAINER_BUILDERS = []string{"gha_container-based"}
GHA_ARTIFACT_IMAGE_BUILDERS = []string{"gha_generic_container"}
GCB_ARTIFACT_IMAGE_BUILDERS = []string{"gcb_container"}
)
func getBuildersAndVersions(t *testing.T,
optionalMinVersion string, specifiedBuilders []string,
defaultBuilders []string,
) []string {
res := []string{}
builders := specifiedBuilders
if len(builders) == 0 {
builders = defaultBuilders
}
// Get versions for each builder.
for _, builder := range builders {
builderDir, err := ioutil.ReadDir(filepath.Join(TEST_DIR, builder))
if err != nil {
t.Error(err)
}
for _, f := range builderDir {
// Builder subfolders are semantic version strings.
// Compare if a min version is given.
if f.IsDir() && (optionalMinVersion == "" ||
semver.Compare(optionalMinVersion, f.Name()) <= 0) {
// These are the supported versions of the builder
res = append(res, filepath.Join(builder, f.Name()))
}
}
}
return res
}
func Test_runVerifyGHAArtifactPath(t *testing.T) {
// We cannot use t.Setenv due to parallelized tests.
// TODO(639): Remove this by regenerating multiple subjects test.
os.Setenv("SLSA_VERIFIER_TESTING", "1")
t.Parallel()
goBuilder := "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml"
genericBuilder := "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml"
delegatorBuilder := "https://github.com/slsa-framework/example-trw/.github/workflows/builder_high-perms_slsa3.yml"
mavenBuilder := "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_maven_slsa3.yml"
gradleBuilder := "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_gradle_slsa3.yml"
tests := []struct {
name string
artifacts []string
source string
pbranch *string
ptag *string
pversiontag *string
pBuilderID *string
outBuilderID string
inputs map[string]string
err error
// noversion is a special case where we are not testing all builder versions
// for example, testdata for the builder at head in trusted repo workflows
// or testdata from malicious untrusted builders.
// When true, this does not iterate over all builder versions.
noversion bool
// minversion is a special case to test a newly added feature into a builder
minversion string
// specifying builders will restrict builders to only the specified ones.
builders []string
// specify provenance path if not the same as artifacts[0]
// useful for testing provenance with multiple artifacts,
// without needing to duplicate provenance
provenancePath string
}{
{
name: "valid main branch default",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
},
{
name: "valid main branch default - invalid builderID",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
pBuilderID: pString("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/not-trusted.yml"),
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "valid main branch set",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
pbranch: pString("main"),
},
{
name: "wrong branch master",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
pbranch: pString("master"),
err: serrors.ErrorMismatchBranch,
},
{
name: "branch master not verified",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
},
{
name: "wrong source append A",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/laurentsimon/slsa-verifier-test-genA",
err: serrors.ErrorMismatchSource,
},
{
name: "wrong source prepend A",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/laurentsimon/slsa-verifier-test-gen",
err: serrors.ErrorMismatchSource,
},
{
name: "wrong source middle A",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/Alaurentsimon/slsa-verifier-test-gen",
err: serrors.ErrorMismatchSource,
},
{
name: "tag no match empty tag workflow_dispatch",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
ptag: pString("v1.2.3"),
err: serrors.ErrorInvalidRef,
},
{
name: "versioned tag no match empty tag workflow_dispatch",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v1"),
err: serrors.ErrorInvalidRef,
},
// Provenance contains tag = v13.0.30.
{
name: "tag v13.0.29 no match v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
ptag: pString("v13.0.29"),
err: serrors.ErrorMismatchTag,
},
{
name: "tag v13.0 no match v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
ptag: pString("v13.0"),
err: serrors.ErrorMismatchTag,
},
{
name: "tag v13 no match v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
ptag: pString("v13"),
err: serrors.ErrorMismatchTag,
},
{
name: "versioned v13.0.30 match push-v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v13.0.30"),
},
{
name: "versioned v13.0 match push-v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v13.0"),
},
{
name: "versioned v13 match push-v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v13"),
},
{
name: "versioned v2 no match push-v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v2"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v0 no match push-v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v0"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v13.1 no match push-v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v13.1"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v12.9 no match push-v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v12.9"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v13.0.29 no match push-v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v13.0.29"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v13.0.31 no match push-v13.0.30",
artifacts: []string{"binary-linux-amd64-push-v13.0.30"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v13.0.31"),
err: serrors.ErrorMismatchVersionedTag,
},
// Provenance contains tag = v14.
{
name: "versioned v14 match push-v14",
artifacts: []string{"binary-linux-amd64-push-v14"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v14"),
},
{
name: "versioned v14.0 match push-v14",
artifacts: []string{"binary-linux-amd64-push-v14"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v14.0"),
},
{
name: "versioned v14.1 no match push-v14",
artifacts: []string{"binary-linux-amd64-push-v14"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v14.1"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v13 no match push-v14",
artifacts: []string{"binary-linux-amd64-push-v14"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v13"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v15 no match push-v14",
artifacts: []string{"binary-linux-amd64-push-v14"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v15"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v13.2 no match push-v14",
artifacts: []string{"binary-linux-amd64-push-v14"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v13.2"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v15 no match push-v14",
artifacts: []string{"binary-linux-amd64-push-v14"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v15"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v0 no match push-v14",
artifacts: []string{"binary-linux-amd64-push-v14"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v0"),
err: serrors.ErrorMismatchVersionedTag,
},
// Provenance contains tag = v14.2
{
name: "versioned v14.2 match push-v14.2",
artifacts: []string{"binary-linux-amd64-push-v14.2"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v14.2"),
},
{
name: "versioned v14.2.1 match push-v14.2",
artifacts: []string{"binary-linux-amd64-push-v14.2"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v14.2.1"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v14.2.3 match push-v14.2",
artifacts: []string{"binary-linux-amd64-push-v14.2"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v14.2.3"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v14 match push-v14.2",
artifacts: []string{"binary-linux-amd64-push-v14.2"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v14"),
},
{
name: "versioned v14.1 no match push-v14.2",
artifacts: []string{"binary-linux-amd64-push-v14.2"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v14.1"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v14.1.1 no match push-v14.2",
artifacts: []string{"binary-linux-amd64-push-v14.2"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v14.1.1"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v14.3.1 no match push-v14.2",
artifacts: []string{"binary-linux-amd64-push-v14.2"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v14.3.1"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v13 no match push-v14.2",
artifacts: []string{"binary-linux-amd64-push-v14.2"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v13"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v15 no match push-v14.2",
artifacts: []string{"binary-linux-amd64-push-v14.2"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v15"),
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned v15.1 no match push-v14.2",
artifacts: []string{"binary-linux-amd64-push-v14.2"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v15.1"),
err: serrors.ErrorMismatchVersionedTag,
},
// Multiple subjects in version v1.2.0+
{
name: "multiple subject first match",
artifacts: []string{"binary-linux-amd64-multi-subject-first"},
source: "github.com/slsa-framework/example-package",
noversion: true,
builders: []string{"gha_generic"},
},
{
name: "multiple subject second match",
artifacts: []string{"binary-linux-amd64-multi-subject-second"},
source: "github.com/slsa-framework/example-package",
noversion: true,
builders: []string{"gha_generic"},
},
{
name: "multiple subject first and second match",
artifacts: []string{"binary-linux-amd64-multi-subject-first", "binary-linux-amd64-multi-subject-second"},
source: "github.com/slsa-framework/example-package",
noversion: true,
builders: []string{"gha_generic"},
},
{
name: "multiple subject second and first match",
artifacts: []string{"binary-linux-amd64-multi-subject-second", "binary-linux-amd64-multi-subject-first"},
source: "github.com/slsa-framework/example-package",
noversion: true,
builders: []string{"gha_generic"},
},
{
name: "multiple subject repeated match",
artifacts: []string{"binary-linux-amd64-multi-subject-first", "binary-linux-amd64-multi-subject-first"},
source: "github.com/slsa-framework/example-package",
noversion: true,
builders: []string{"gha_generic"},
},
{
name: "multiple subject one mismatch",
artifacts: []string{"binary-linux-amd64-multi-subject-first", "binary-linux-amd64-sharded"},
source: "github.com/slsa-framework/example-package",
noversion: true,
err: serrors.ErrorMismatchHash,
},
{
name: "multiple subject no match",
artifacts: []string{"binary-linux-amd64-sharded"},
source: "github.com/slsa-framework/example-package",
noversion: true,
err: serrors.ErrorMismatchHash,
provenancePath: "binary-linux-amd64-multi-subject-first.intoto.jsonl",
},
{
name: "multiple subject second match - builderID",
artifacts: []string{"binary-linux-amd64-multi-subject-second"},
source: "github.com/slsa-framework/example-package",
noversion: true,
builders: []string{"gha_generic"},
pBuilderID: pString("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml"),
outBuilderID: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml",
},
// Special case of the e2e test repository building builder from head.
{
name: "e2e test repository verified with builder at head",
artifacts: []string{"binary-linux-amd64-e2e-builder-repo"},
source: "github.com/slsa-framework/example-package",
pbranch: pString("main"),
noversion: true,
pBuilderID: pString("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml"),
outBuilderID: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml",
},
// Malicious builders and workflows.
{
name: "rekor upload bypassed",
artifacts: []string{"binary-linux-amd64-no-tlog-upload"},
source: "github.com/slsa-framework/example-package",
err: serrors.ErrorRekorSearch,
noversion: true,
},
{
name: "malicious: untrusted builder",
artifacts: []string{"binary-linux-amd64-untrusted-builder"},
source: "github.com/slsa-framework/example-package",
err: serrors.ErrorUntrustedReusableWorkflow,
noversion: true,
},
{
name: "malicious: invalid signature expired certificate",
artifacts: []string{"binary-linux-amd64-expired-cert"},
source: "github.com/slsa-framework/example-package",
err: serrors.ErrorRekorSearch,
noversion: true,
},
// Annotated tags.
{
name: "annotated tag",
artifacts: []string{"annotated-tag"},
source: "github.com/asraa/slsa-on-github-test",
pversiontag: pString("v1.5.0"),
noversion: true,
},
{
name: "no branch",
artifacts: []string{"annotated-tag"},
source: "github.com/asraa/slsa-on-github-test",
pversiontag: pString("v1.5.0"),
pbranch: pString("main"),
err: serrors.ErrorInvalidRef,
noversion: true,
},
// Workflow inputs.
{
name: "workflow inputs match",
artifacts: []string{"workflow-inputs"},
source: "github.com/laurentsimon/slsa-on-github-test",
inputs: map[string]string{
"release_version": "(for example, 0.1.0)",
"some_bool": "true",
"some_integer": "123",
},
noversion: true,
},
{
name: "workflow inputs missing field",
artifacts: []string{"workflow-inputs"},
source: "github.com/laurentsimon/slsa-on-github-test",
inputs: map[string]string{
"release_version": "(for example, 0.1.0)",
"some_bool": "true",
"missing_field": "123",
},
err: serrors.ErrorMismatchWorkflowInputs,
noversion: true,
},
{
name: "workflow inputs mismatch",
artifacts: []string{"workflow-inputs"},
source: "github.com/laurentsimon/slsa-on-github-test",
inputs: map[string]string{
"release_version": "(for example, 0.1.0)",
"some_bool": "true",
"some_integer": "321",
},
err: serrors.ErrorMismatchWorkflowInputs,
noversion: true,
},
}
for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
// Avoid rate limiting by not running the tests in parallel.
// t.Parallel()
checkVersions := getBuildersAndVersions(t, "v1.2.2", tt.builders, GHA_ARTIFACT_PATH_BUILDERS)
if tt.noversion {
checkVersions = []string{""}
}
for _, v := range checkVersions {
var provenancePath string
var byob bool
if tt.provenancePath == "" {
testPath := filepath.Clean(filepath.Join(TEST_DIR, v, tt.artifacts[0]))
if strings.Contains(testPath, "delegator") || strings.Contains(testPath, "maven") || strings.Contains(testPath, "gradle") {
provenancePath = fmt.Sprintf("%s.build.slsa", testPath)
byob = true
} else {
provenancePath = fmt.Sprintf("%s.intoto.jsonl", testPath)
}
} else {
provenancePath = filepath.Clean(filepath.Join(TEST_DIR, v, tt.provenancePath))
}
artifacts := make([]string, len(tt.artifacts))
for i, artifact := range tt.artifacts {
artifacts[i] = filepath.Clean(filepath.Join(TEST_DIR, v, artifact))
}
// TODO(#258): invalid builder ref.
sv := filepath.Base(v)
// For each test, we run 4 sub-tests:
// 1. With the the full builderID including the semver in short form.
// 2. With the the full builderID including the semver in long form.
// 3. With only the name of the builder.
// 4. With no builder ID.
var builder string
// Select the right builder based on directory structure.
parts := strings.Split(v, "/")
name := parts[0]
version := ""
if len(parts) > 1 {
version = parts[1]
}
switch {
case strings.HasSuffix(name, "_go"):
builder = goBuilder
case strings.HasSuffix(name, "_generic"):
builder = genericBuilder
case strings.HasSuffix(name, "_delegator"):
builder = delegatorBuilder
case strings.HasSuffix(name, "_maven"):
builder = mavenBuilder
case strings.HasSuffix(name, "_gradle"):
builder = gradleBuilder
default:
builder = genericBuilder
}
// Default builders to test.
builderIDs := []*string{
pString(builder),
}
// Do not run without explicit builder ID for the delegator,
// because it's hosted on a different repo slsa-framework/example-package.
if builder != delegatorBuilder {
builderIDs = append(builderIDs, nil)
}
// We only add the tags to tests for versions >= 1,
// because we generated them with a builder at `@main`
// before GA. Add the tests for tag verification.
if version != "" && semver.Compare(version, "v1.0.0") > 0 {
builderIDs = append(builderIDs, []*string{
pString(builder + "@" + sv),
pString(builder + "@refs/tags/" + sv),
}...)
}
// If builder ID is set, use it.
if tt.pBuilderID != nil {
builderIDs = []*string{tt.pBuilderID}
}
for _, bid := range builderIDs {
cmd := verify.VerifyArtifactCommand{
ProvenancePath: provenancePath,
SourceURI: tt.source,
SourceBranch: tt.pbranch,
BuilderID: bid,
SourceTag: tt.ptag,
SourceVersionTag: tt.pversiontag,
BuildWorkflowInputs: tt.inputs,
}
// BYOB-based builders ignore the reusable workflow.
if errCmp(tt.err, serrors.ErrorUntrustedReusableWorkflow) && byob {
tt.err = serrors.ErrorMismatchBuilderID
}
// The outBuilderID is the actual builder ID from the provenance.
// This is always long form for the GHA builders.
outBuilderID, err := cmd.Exec(context.Background(), artifacts)
if !errCmp(err, tt.err) {
t.Errorf("%v: %v", v, cmp.Diff(err, tt.err, cmpopts.EquateErrors()))
}
if err != nil {
continue
}
// Validate against test's expected builderID, if provided.
if tt.outBuilderID != "" {
if err := outBuilderID.MatchesLoose(tt.outBuilderID, false); err != nil {
t.Errorf(fmt.Sprintf("matches failed (1): %v", err))
}
}
// Smoke test against the CLI command
cliCmd := verifyArtifactCmd()
args := []string{
"--source-uri", tt.source,
"--provenance-path", provenancePath,
}
args = append(args, artifacts...)
if bid != nil {
args = append(args, "--builder-id", *bid)
}
if tt.pbranch != nil {
args = append(args, "--source-branch", *tt.pbranch)
}
if tt.ptag != nil {
args = append(args, "--source-tag", *tt.ptag)
}
if tt.pversiontag != nil {
args = append(args, "--source-versioned-tag", *tt.pversiontag)
}
if tt.inputs != nil {
for k, v := range tt.inputs {
args = append(args, "--build-workflow-input", fmt.Sprintf("%s=%s", k, v))
}
}
b := bytes.NewBufferString("")
cliCmd.SetOut(b)
cliCmd.SetArgs(args)
cliErr := cliCmd.Execute()
if !errCmp(cliErr, tt.err) {
t.Errorf("%v: %v", v, cmp.Diff(cliErr, tt.err, cmpopts.EquateErrors()))
}
if bid == nil {
continue
}
// If we have a generated a user-provided bid, then validate it against the
// resulting builderID returned by the provenance check.
// Since this a GHA and the certificate ID is in long form,
// we pass `allowRef = true`.
if err := outBuilderID.MatchesLoose(*bid, true); err != nil {
t.Errorf(fmt.Sprintf("matches failed (2): %v", err))
}
}
}
})
}
}
func Test_runVerifyGHAArtifactImage(t *testing.T) {
t.Parallel()
// Override cosign image verification function for local image testing.
container.RunCosignImageVerification = func(ctx context.Context,
image string, co *cosign.CheckOpts,
) ([]oci.Signature, bool, error) {
key := "@sha256:"
i := strings.Index(image, key)
if i < 0 {
return nil, false, fmt.Errorf("cannot find '%v' in '%v'", key, image)
}
image = image[:i]
return cosign.VerifyLocalImageAttestations(ctx, image, co)
}
builder := "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml"
tests := []struct {
name string
artifact string
source string
pbranch *string
ptag *string
pversiontag *string
pBuilderID *string
outBuilderID string
err error
// noversion is a special case where we are not testing all builder versions
// for example, testdata for the builder at head in trusted repo workflows
// or testdata from malicious untrusted builders.
// When true, this does not iterate over all builder versions.
noversion bool
// minversion is a special case to test a newly added feature into a builder.
minversion string
// maxversion is a special case to handle incompatible error changes in the builder.
maxversion string
}{
{
name: "valid main branch default",
artifact: "container_workflow_dispatch",
source: "github.com/slsa-framework/example-package",
},
{
name: "valid main branch default - invalid builderID",
artifact: "container_workflow_dispatch",
source: "github.com/slsa-framework/example-package",
pBuilderID: pString("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/not-trusted.yml"),
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "valid main branch set",
artifact: "container_workflow_dispatch",
source: "github.com/slsa-framework/example-package",
pbranch: pString("main"),
},
{
name: "wrong branch master",
artifact: "container_workflow_dispatch",
source: "github.com/slsa-framework/example-package",
pbranch: pString("master"),
err: serrors.ErrorMismatchBranch,
},
{
name: "wrong source append A",
artifact: "container_workflow_dispatch",
source: "github.com/slsa-framework/example-packageA",
err: serrors.ErrorMismatchSource,
},
{
name: "wrong source prepend A",
artifact: "container_workflow_dispatch",
source: "Agithub.com/slsa-framework/example-package",
err: serrors.ErrorMismatchSource,
},
{
name: "wrong source middle A",
artifact: "container_workflow_dispatch",
source: "github.com/Aslsa-framework/example-package",
err: serrors.ErrorMismatchSource,
},
{
name: "tag no match empty tag workflow_dispatch",
artifact: "container_workflow_dispatch",
source: "github.com/slsa-framework/example-package",
ptag: pString("v1.2.3"),
maxversion: "v1.8.0",
err: serrors.ErrorInvalidRef,
},
{
name: "versioned tag no match empty tag workflow_dispatch",
artifact: "container_workflow_dispatch",
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v1"),
maxversion: "v1.8.0",
err: serrors.ErrorInvalidRef,
},
{
name: "tag no match empty tag workflow_dispatch > v1.9.0",
artifact: "container_workflow_dispatch",
source: "github.com/slsa-framework/example-package",
ptag: pString("v1.2.3"),
minversion: "v1.9.0",
err: serrors.ErrorMismatchTag,
},
{
name: "versioned tag no match empty tag workflow_dispatch > v1.9.0",
artifact: "container_workflow_dispatch",
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v1"),
minversion: "v1.9.0",
err: serrors.ErrorMismatchVersionedTag,
},
}
for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
checkVersions := getBuildersAndVersions(t, "", nil, GHA_ARTIFACT_IMAGE_BUILDERS)
if tt.noversion {
checkVersions = []string{""}
}
for _, v := range checkVersions {
parts := strings.Split(v, "/")
version := ""
if len(parts) > 1 {
version = parts[1]
}
if version != "" && tt.minversion != "" && semver.Compare(version, tt.minversion) <= 0 {
fmt.Println("skiping due to min:", version)
continue
}
if version != "" && tt.maxversion != "" && semver.Compare(version, tt.maxversion) > 0 {
fmt.Println("skiping due to max:", version)
continue
}
image := filepath.Clean(filepath.Join(TEST_DIR, v, tt.artifact))
// TODO(#258): test for tagged builder.
sv := filepath.Base(v)
// For each test, we run 2 sub-tests:
// 1. With the the full builderID including the semver in short form.
// 2. With the the full builderID including the semver in long form.
// 3. With only the name of the builder.
// 4. With no builder ID.
builderIDs := []*string{
pString(builder + "@" + sv),
pString(builder + "@refs/tags/" + sv),
pString(builder),
nil,
}
// If builder ID is set, use it.
if tt.pBuilderID != nil {
builderIDs = []*string{tt.pBuilderID}
}
// Compute the digest and append it to the image so that's it 'immutable'.
digest, err := localDigestCompute(image)
if err != nil {
panic(fmt.Sprintf("digest computation %v", err))
}
image = fmt.Sprintf("%v@sha256:%v", image, digest)
for _, bid := range builderIDs {
cmd := verify.VerifyImageCommand{
SourceURI: tt.source,
SourceBranch: tt.pbranch,
BuilderID: bid,
SourceTag: tt.ptag,
SourceVersionTag: tt.pversiontag,
}
outBuilderID, err := cmd.Exec(context.Background(), []string{image})
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err, cmpopts.EquateErrors()))
}
if err != nil {
continue
}
// Validate against test's expected builderID, if provided.
if tt.outBuilderID != "" {
if err := outBuilderID.MatchesLoose(tt.outBuilderID, false); err != nil {
t.Errorf(fmt.Sprintf("matches failed: %v", err))
}
}
if bid == nil {
continue
}
// If we have a generated a user-provided bid, then validate it against the
// resulting builderID returned by the provenance check.
// Since this a GHA and the certificate ID is in long form,
// we pass `allowRef = true`.
if err := outBuilderID.MatchesLoose(*bid, true); err != nil {
t.Errorf(fmt.Sprintf("matches failed: %v", err))
}
}
}
})
}
}
func localDigestCompute(image string) (string, error) {
filename := image + ".digest"
digest, err := os.ReadFile(filename)
if err != nil {
return "", fmt.Errorf("ReadFile fail: %w", err)
}
return strings.TrimPrefix(string(digest), "sha256:"), nil
}
func Test_runVerifyGCBArtifactImage(t *testing.T) {
t.Parallel()
builder := "https://cloudbuild.googleapis.com/GoogleHostedWorker"
tests := []struct {
name string
artifact string
artifactDigest map[string]string
noDigest bool
remote bool
provenance string
source string
ptag *string
pversiontag *string
pBuilderID *string
outBuilderID string
err error
// noversion is a special case where we are not testing all builder versions
// for example, testdata for the builder at head in trusted repo workflows
// or testdata from malicious untrusted builders.
// When true, this does not iterate over all builder versions.
noversion bool
// minversion is a special case to test a newly added feature into a builder
minversion string
}{
{
name: "valid main branch default",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "github.com/laurentsimon/gcb-tests",
},
{
name: "valid main branch gcs",
artifact: "gcloud-container-gcs",
provenance: "gcloud-container-gcs.json",
minversion: "v0.3",
source: "gs://slsa-tooling_cloudbuild/source/1663616632.078353-fc7db143dcc64b5f9fe71d0497125ca1.tgz",
},
{
name: "mismatch input builder version",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "github.com/laurentsimon/gcb-tests",
pBuilderID: pString(builder + "@v0.4"),
err: serrors.ErrorMismatchBuilderID,
},
{
name: "unsupported builder",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "github.com/laurentsimon/gcb-tests",
pBuilderID: pString(builder + "a"),
err: serrors.ErrorVerifierNotSupported,
},
{
name: "match output builder name",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "github.com/laurentsimon/gcb-tests",
outBuilderID: builder,
},
{
name: "invalid repo name",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "github.com/laurentsimon/name",
err: serrors.ErrorMismatchSource,
},
{
name: "invalie org name",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "github.com/org/gcb-tests",
err: serrors.ErrorMismatchSource,
},
{
name: "invalid cloud git",
artifact: "gcloud-container-github",
provenance: "gcloud-container-github.json",
source: "gitlab.com/laurentsimon/gcb-tests",
err: serrors.ErrorMismatchSource,
},
{
name: "invalid payload digest",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-payload-digest.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid payload builderid",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-payload-builderid.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid summary digest",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-summary-digest.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorMismatchHash,
},
{
name: "invalid text digest",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-text-digest.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorMismatchIntoto,
},
{
name: "invalid text build steps",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-text-steps.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorMismatchIntoto,
},
{
name: "invalid metadata kind",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-metadata-kind.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorInvalidFormat,
},
{
name: "invalid metadata resourceUri sha256",
artifact: "gcloud-container-github",
provenance: "gcloud-container-mismatch-metadata-urisha256.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorMismatchHash,
},
{
name: "invalid signature encoding",
artifact: "gcloud-container-github",
provenance: "gcloud-container-invalid-signature-encoding.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid signature empty",
artifact: "gcloud-container-github",
provenance: "gcloud-container-empty-signature.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid signature none",
artifact: "gcloud-container-github",
provenance: "gcloud-container-no-signature.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorInvalidDssePayload,
},
{
name: "invalid region",
artifact: "gcloud-container-github",
provenance: "gcloud-container-invalid-signature-region.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid empty region",
artifact: "gcloud-container-github",
provenance: "gcloud-container-empty-signature-region.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid keyid",
artifact: "gcloud-container-github",
provenance: "gcloud-container-invalid-keyid.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid keyid empty",
artifact: "gcloud-container-github",
provenance: "gcloud-container-empty-keyid.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid keyid none",
artifact: "gcloud-container-github",
provenance: "gcloud-container-no-keyid.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "invalid signature multiple",
artifact: "gcloud-container-github",
provenance: "gcloud-container-multiple-invalid-signatures.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "signature multiple 2nd valid",
artifact: "gcloud-container-github",
provenance: "gcloud-container-multiple-signatures-2ndvalid.json",
source: "github.com/laurentsimon/gcb-tests",
},
{
name: "signature multiple 3rd valid",
artifact: "gcloud-container-github",
provenance: "gcloud-container-multiple-signatures-3rdvalid.json",
source: "github.com/laurentsimon/gcb-tests",
},
{
name: "invalid multiple provenance",
artifact: "gcloud-container-github",
provenance: "gcloud-container-multiple-invalid-provenance.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorNoValidSignature,
},
{
name: "tag match",
artifact: "gcloud-container-github-tag",
provenance: "gcloud-container-github-tag.json",
source: "github.com/slsa-framework/example-package",
ptag: pString("v33.0.4"),
minversion: "v0.3",
},
{
name: "tag mismatch major",
artifact: "gcloud-container-github-tag",
provenance: "gcloud-container-github-tag.json",
source: "github.com/slsa-framework/example-package",
ptag: pString("v34.0.4"),
minversion: "v0.3",
err: serrors.ErrorMismatchTag,
},
{
name: "tag mismatch minor",
artifact: "gcloud-container-github-tag",
provenance: "gcloud-container-github-tag.json",
source: "github.com/slsa-framework/example-package",
ptag: pString("v33.1.4"),
minversion: "v0.3",
err: serrors.ErrorMismatchTag,
},
{
name: "tag mismatch patch",
artifact: "gcloud-container-github-tag",
provenance: "gcloud-container-github-tag.json",
source: "github.com/slsa-framework/example-package",
ptag: pString("v33.0.5"),
minversion: "v0.3",
err: serrors.ErrorMismatchTag,
},
{
name: "versioned tag match major",
artifact: "gcloud-container-github-tag",
provenance: "gcloud-container-github-tag.json",
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v33"),
minversion: "v0.3",
},
{
name: "versioned tag match minor",
artifact: "gcloud-container-github-tag",
provenance: "gcloud-container-github-tag.json",
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v33.0"),
minversion: "v0.3",
},
{
name: "versioned tag match patch",
artifact: "gcloud-container-github-tag",
provenance: "gcloud-container-github-tag.json",
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v33.0.4"),
minversion: "v0.3",
},
{
name: "versioned tag mismatch patch",
artifact: "gcloud-container-github-tag",
provenance: "gcloud-container-github-tag.json",
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v33.0.5"),
minversion: "v0.3",
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned tag mismatch minor",
artifact: "gcloud-container-github-tag",
provenance: "gcloud-container-github-tag.json",
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v33.1"),
minversion: "v0.3",
err: serrors.ErrorMismatchVersionedTag,
},
{
name: "versioned tag mismatch major",
artifact: "gcloud-container-github-tag",
provenance: "gcloud-container-github-tag.json",
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v35"),
minversion: "v0.3",
err: serrors.ErrorMismatchVersionedTag,
},
// TODO(388): verify the correct provenance is returned.
// This should also be done for all other entries in this test.
{
name: "multiple provenance 2nd valid",
artifact: "gcloud-container-github",
provenance: "gcloud-container-multiple-provenance-2ndvalid.json",
source: "github.com/laurentsimon/gcb-tests",
},
{
name: "multiple provenance 3rd valid",
artifact: "gcloud-container-github",
provenance: "gcloud-container-multiple-provenance-3rdvalid.json",
source: "github.com/laurentsimon/gcb-tests",
},
{
name: "oci valid with tag",
// Image re-tagged and pushed to docker hub. This image is public.
artifact: "laurentsimon/slsa-gcb-%s:test",
artifactDigest: map[string]string{
"v0.2": "1a033b002f89ed2b8ea733162497fb70f1a4049a7f8602d6a33682b4ad9921fd",
"v0.3": "f472ca4b68898c951ac3b476cba919d0d56fca4ced631fabcead51e4b2b690e7",
},
remote: true,
source: "github.com/laurentsimon/gcb-tests",
provenance: "gcloud-container-github.json",
},
{
name: "oci mismatch digest",
artifact: "index.docker.io/laurentsimon/scorecard",
artifactDigest: map[string]string{
"v0.2": "d794817bdf9c7e5ec34758beb90a18113c7dfbd737e760cabf8dd923d49e96f4",
"v0.3": "d794817bdf9c7e5ec34758beb90a18113c7dfbd737e760cabf8dd923d49e96f4",
},
remote: true,
provenance: "gcloud-container-github.json",
source: "github.com/laurentsimon/gcb-tests",
err: serrors.ErrorMismatchHash,
},
{
name: "oci valid no tag",
artifact: "laurentsimon/slsa-gcb-%s",
artifactDigest: map[string]string{
"v0.2": "1a033b002f89ed2b8ea733162497fb70f1a4049a7f8602d6a33682b4ad9921fd",
"v0.3": "f472ca4b68898c951ac3b476cba919d0d56fca4ced631fabcead51e4b2b690e7",
},
remote: true,
source: "github.com/laurentsimon/gcb-tests",
provenance: "gcloud-container-github.json",
},
// No version.
{
name: "oci is mutable",
artifact: "index.docker.io/laurentsimon/scorecard",
noversion: true,
remote: true,
noDigest: true,
source: "github.com/laurentsimon/gcb-tests",
provenance: "gcloud-container-github.json",
pBuilderID: pString(builder + "@v0.2"),
err: serrors.ErrorMutableImage,
},
}
for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
checkVersions := getBuildersAndVersions(t, tt.minversion, nil, GCB_ARTIFACT_IMAGE_BUILDERS)
if tt.noversion {
checkVersions = []string{""}
}
for _, v := range checkVersions {
semver := filepath.Base(v)
// For each test, we run 2 sub-tests:
// 1. With the the full builderID including the semver.
// 2. With only the name of the builder.
builderIDs := []string{builder + "@" + semver, builder}
provenance := filepath.Clean(filepath.Join(TEST_DIR, v, tt.provenance))
image := tt.artifact
digestFn := container.GetImageDigest
// If builder ID is set, use it.
if tt.pBuilderID != nil {
builderIDs = []string{*tt.pBuilderID}
}
// Select the right image according to the builder version we are testing.
if strings.Contains(image, `%s`) {
image = fmt.Sprintf(image, semver)
}
// Add the sha256 digest to the image name, if provided.
if len(tt.artifactDigest) > 0 {
digest, ok := tt.artifactDigest[semver]
if !ok {
panic(fmt.Sprintf("%s not present in artifactDigest %v", semver, tt.artifactDigest))
}
image = fmt.Sprintf("%s@sha256:%s", image, digest)
}
// If it is a local image, change the digest computation.
if !tt.remote {
image = filepath.Clean(filepath.Join(TEST_DIR, v, image))
digestFn = localDigestCompute
}
if len(tt.artifactDigest) == 0 && !tt.noDigest {
// Compute the digest and append it to the image so that's it 'immutable'.
digest, err := digestFn(image)
if err != nil {
panic(fmt.Sprintf("digest computation %v", err))
}
image = fmt.Sprintf("%v@sha256:%v", image, digest)
}
// We run the test for each builderID, in order to test
// a builderID provided by name and one containing both the name
// and semver.
for _, bid := range builderIDs {
cmd := verify.VerifyImageCommand{
SourceURI: tt.source,
SourceBranch: nil,
BuilderID: &bid,
SourceTag: tt.ptag,
SourceVersionTag: tt.pversiontag,
ProvenancePath: &provenance,
}
outBuilderID, err := cmd.Exec(context.Background(), []string{image})
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err, cmpopts.EquateErrors()))
}
if err != nil {
return
}
// Validate against test's expected builderID, if provided.
if tt.outBuilderID != "" {
if err := outBuilderID.MatchesLoose(tt.outBuilderID, false); err != nil {
t.Errorf(fmt.Sprintf("matches failed: %v", err))
}
}
// Validate against builderID we generated automatically.
if err := outBuilderID.MatchesLoose(bid, false); err != nil {
t.Errorf(fmt.Sprintf("matches failed: %v", err))
}
}
}
})
}
}
func Test_runVerifyGHAContainerBased(t *testing.T) {
t.Parallel()
tests := []struct {
name string
artifacts []string
source string
pbranch *string
ptag *string
pversiontag *string
pBuilderID *string
inputs map[string]string
err error
}{
{
name: "valid main branch default",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
},
{
name: "versioned tag no match empty tag workflow_dispatch",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
pversiontag: pString("v1"),
err: serrors.ErrorInvalidRef,
},
{
name: "tag no match empty tag workflow_dispatch",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
ptag: pString("v1.2.3"),
err: serrors.ErrorInvalidRef,
},
{
name: "wrong branch master",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
pbranch: pString("master"),
err: serrors.ErrorMismatchBranch,
},
{
name: "valid main branch set",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
pbranch: pString("main"),
},
{
name: "valid main branch default - invalid builderID",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-package",
pBuilderID: pString("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/not-trusted.yml"),
err: serrors.ErrorUntrustedReusableWorkflow,
},
{
name: "wrong source append A",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/slsa-framework/example-packageA",
err: serrors.ErrorMismatchSource,
},
{
name: "wrong source prepend A",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "Agithub.com/slsa-framework/example-package",
err: serrors.ErrorMismatchSource,
},
{
name: "wrong source middle A",
artifacts: []string{"binary-linux-amd64-workflow_dispatch"},
source: "github.com/Aslsa-framework/example-package",
err: serrors.ErrorMismatchSource,
},
}
for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
checkVersions := getBuildersAndVersions(t, "", nil, GHA_ARTIFACT_CONTAINER_BUILDERS)
for _, v := range checkVersions {
testPath := filepath.Clean(filepath.Join(TEST_DIR, v, tt.artifacts[0]))
sv := filepath.Base(v)
var provenancePath string
if semver.Compare(sv, "v1.8.0") >= 0 {
provenancePath = fmt.Sprintf("%s.intoto.build.slsa", testPath)
} else {
provenancePath = fmt.Sprintf("%s.intoto.sigstore", testPath)
}
artifacts := make([]string, len(tt.artifacts))
for i, artifact := range tt.artifacts {
artifacts[i] = filepath.Clean(filepath.Join(TEST_DIR, v, artifact))
}
// For each test, we run 2 sub-tests:
// 1. With the the full builderID including the semver in short form.
// 2. With the the full builderID including the semver in long form.
// 3. With only the name of the builder.
// 4. With no builder ID.
builder := "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_container-based_slsa3.yml"
refName := "@refs/tags/"
builderIDs := []*string{
pString(builder + refName + sv),
pString(builder),
pString(builder + "@" + sv),
nil,
}
// If builder ID is set, use it.
if tt.pBuilderID != nil {
builderIDs = []*string{tt.pBuilderID}
}
for _, bid := range builderIDs {
cmd := verify.VerifyArtifactCommand{
ProvenancePath: provenancePath,
SourceURI: tt.source,
SourceBranch: tt.pbranch,
BuilderID: bid,
SourceTag: tt.ptag,
SourceVersionTag: tt.pversiontag,
BuildWorkflowInputs: tt.inputs,
}
// The outBuilderID is the actual builder ID from the provenance.
// This is always long form for the GHA builders.
_, err := cmd.Exec(context.Background(), artifacts)
if !errCmp(err, tt.err) {
t.Errorf("%v: %v", v, cmp.Diff(err, tt.err, cmpopts.EquateErrors()))
}
}
}
})
}
}
func Test_runVerifyNpmPackage(t *testing.T) {
// We cannot use t.Setenv due to parallelized tests.
os.Setenv("SLSA_VERIFIER_EXPERIMENTAL", "1")
t.Parallel()
tests := []struct {
name string
artifact string
builderID *string
source string
pkgVersion *string
pkgName *string
err error
}{
// npm CLI with tag.
{
name: "valid npm CLI builder",
artifact: "supreme-googles-cli-v02-tag.tgz",
source: "github.com/trishankatdatadog/supreme-goggles",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@trishankatdatadog/supreme-goggles"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
},
{
name: "valid npm CLI builder short runner name",
artifact: "supreme-googles-cli-v02-tag.tgz",
source: "github.com/trishankatdatadog/supreme-goggles",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@trishankatdatadog/supreme-goggles"),
builderID: pointerTo("https://github.com/actions/runner"),
},
{
name: "valid npm CLI builder no builder",
artifact: "supreme-googles-cli-v02-tag.tgz",
source: "github.com/trishankatdatadog/supreme-goggles",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@trishankatdatadog/supreme-goggles"),
err: serrors.ErrorInvalidBuilderID,
},
{
name: "valid npm CLI builder mismatch builder",
artifact: "supreme-googles-cli-v02-tag.tgz",
source: "github.com/trishankatdatadog/supreme-goggles",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@trishankatdatadog/supreme-goggles"),
builderID: pointerTo("https://github.com/actions/runner2"),
err: serrors.ErrorNotSupported,
},
{
name: "valid npm CLI builder no package name",
artifact: "supreme-googles-cli-v02-tag.tgz",
source: "github.com/trishankatdatadog/supreme-goggles",
pkgVersion: pointerTo("1.0.5"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
},
{
name: "valid npm CLI builder no package version",
artifact: "supreme-googles-cli-v02-tag.tgz",
source: "github.com/trishankatdatadog/supreme-goggles",
pkgName: pointerTo("@trishankatdatadog/supreme-goggles"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
},
{
name: "valid npm CLI builder mismatch source",
artifact: "supreme-googles-cli-v02-tag.tgz",
source: "github.com/trishankatdatadog/supreme-goggleS",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@trishankatdatadog/supreme-goggles"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
err: serrors.ErrorMismatchSource,
},
{
name: "valid npm CLI builder mismatch package version",
artifact: "supreme-googles-cli-v02-tag.tgz",
source: "github.com/trishankatdatadog/supreme-goggles",
pkgVersion: pointerTo("1.0.4"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
err: serrors.ErrorMismatchPackageVersion,
},
{
name: "valid npm CLI builder mismatch package name",
artifact: "supreme-googles-cli-v02-tag.tgz",
source: "github.com/trishankatdatadog/supreme-goggles",
pkgName: pointerTo("@trishankatdatadog/supreme-goggleS"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
err: serrors.ErrorMismatchPackageName,
},
{
name: "invalid signature provenance npm CLI",
artifact: "supreme-googles-cli-v02-tag-invalidsigprov.tgz",
source: "github.com/trishankatdatadog/supreme-goggles",
pkgName: pointerTo("@trishankatdatadog/supreme-goggles"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
err: serrors.ErrorInvalidSignature,
},
{
name: "invalid signature provenance npm CLI",
artifact: "supreme-googles-cli-v02-tag-invalidsigpub.tgz",
source: "github.com/trishankatdatadog/supreme-goggles",
pkgName: pointerTo("@trishankatdatadog/supreme-goggles"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
err: serrors.ErrorInvalidSignature,
},
// npm CLI with main branch.
{
name: "valid npm CLI builder",
artifact: "provenance-npm-test-cli-v02-prega.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.3"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
},
{
name: "valid npm CLI builder short runner name",
artifact: "provenance-npm-test-cli-v02-prega.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.3"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/actions/runner"),
},
{
name: "valid npm CLI builder no builder",
artifact: "provenance-npm-test-cli-v02-prega.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.3"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
err: serrors.ErrorInvalidBuilderID,
},
{
name: "valid npm CLI builder mismatch builder",
artifact: "provenance-npm-test-cli-v02-prega.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.3"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/actions/runner2"),
err: serrors.ErrorNotSupported,
},
{
name: "valid npm CLI builder no package name",
artifact: "provenance-npm-test-cli-v02-prega.tgz",
pkgVersion: pointerTo("1.0.3"),
source: "github.com/laurentsimon/provenance-npm-test",
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
},
{
name: "valid npm CLI builder no package version",
artifact: "provenance-npm-test-cli-v02-prega.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
},
{
name: "valid npm CLI builder mismatch source",
artifact: "provenance-npm-test-cli-v02-prega.tgz",
source: "github.com/laurentsimon/provenance-npm-test2",
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
err: serrors.ErrorMismatchSource,
},
{
name: "valid npm CLI builder mismatch package version",
artifact: "provenance-npm-test-cli-v02-prega.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.4"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
err: serrors.ErrorMismatchPackageVersion,
},
{
name: "valid npm CLI builder mismatch package name",
artifact: "provenance-npm-test-cli-v02-prega.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgName: pointerTo("@laurentsimon/provenance-npm-test2"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
err: serrors.ErrorMismatchPackageName,
},
{
name: "invalid signature provenance npm CLI",
artifact: "provenance-npm-test-cli-v02-prega-invalidsigprov.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
err: serrors.ErrorInvalidSignature,
},
{
name: "invalid signature publish npm CLI",
artifact: "provenance-npm-test-cli-v02-prega-invalidsigpub.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/actions/runner/github-hosted"),
err: serrors.ErrorInvalidSignature,
},
// OSSF builder.
{
name: "valid npm OSSF builder",
artifact: "provenance-npm-test-ossf.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml"),
},
{
name: "valid npm OSSF builder no builder",
artifact: "provenance-npm-test-ossf.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
err: serrors.ErrorInvalidBuilderID,
},
{
name: "valid npm OSSF builder mismatch builder",
artifact: "provenance-npm-test-ossf.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa.yml"),
err: serrors.ErrorMismatchBuilderID,
},
{
name: "valid npm OSSF builder no package name",
artifact: "provenance-npm-test-ossf.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.5"),
builderID: pointerTo("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml"),
},
{
name: "valid npm OSSF builder no package version",
artifact: "provenance-npm-test-ossf.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml"),
},
{
name: "valid npm OSSF builder mismatch package name",
artifact: "provenance-npm-test-ossf.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test2"),
builderID: pointerTo("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml"),
err: serrors.ErrorMismatchPackageName,
},
{
name: "valid npm OSSF builder mismatch package version",
artifact: "provenance-npm-test-ossf.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.6"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml"),
err: serrors.ErrorMismatchPackageVersion,
},
{
name: "valid npm OSSF builder mismatch mismatch source",
artifact: "provenance-npm-test-ossf.tgz",
source: "github.com/laurentsimon/provenance-npm-test2",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml"),
err: serrors.ErrorMismatchSource,
},
{
name: "invalid signature provenance npm OSSF builder",
artifact: "provenance-npm-test-ossf-invalidsigprov.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml"),
err: serrors.ErrorInvalidSignature,
},
{
name: "invalid signature publish npm OSSF builder",
artifact: "provenance-npm-test-ossf-invalidsigpub.tgz",
source: "github.com/laurentsimon/provenance-npm-test",
pkgVersion: pointerTo("1.0.5"),
pkgName: pointerTo("@laurentsimon/provenance-npm-test"),
builderID: pointerTo("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml"),
err: serrors.ErrorInvalidSignature,
},
}
for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
artifactPath := filepath.Clean(filepath.Join(TEST_DIR, "npm", "gha", tt.artifact))
attestationsPath := fmt.Sprintf("%s.json", artifactPath)
cmd := verify.VerifyNpmPackageCommand{
AttestationsPath: attestationsPath,
BuilderID: tt.builderID,
SourceURI: tt.source,
PackageName: tt.pkgName,
PackageVersion: tt.pkgVersion,
}
_, err := cmd.Exec(context.Background(), []string{artifactPath})
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error (-want +got): \n%s", diff)
}
})
}
}
// Test_runVerifyVSA tests the CLI inputes of verify-vsa. More extensive tests are in
// slsa-verifier/verifiers/internal/vsa/verifier_test.go
func Test_runVerifyVSA(t *testing.T) {
t.Parallel()
tests := []struct {
name string
attestationPath *string
subjectDigests *[]string
verifierID *string
resourceURI *string
verifiedLevels *[]string
publicKeyPath *string
publicKeyID *string
err error
}{
{
name: "success: gke",
attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"),
subjectDigests: pointerTo([]string{"gce_image_id:8970095005306000053"}),
verifierID: pointerTo("https://bcid.corp.google.com/verifier/bcid_package_enforcer/v0.1"),
resourceURI: pointerTo("gce_image://gke-node-images:gke-12615-gke1418000-cos-101-17162-463-29-c-cgpv1-pre"),
verifiedLevels: pointerTo([]string{"BCID_L1", "SLSA_BUILD_LEVEL_2"}),
publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"),
publicKeyID: pointerTo("keystore://76574:prod:vsa_signing_public_key"),
},
{
name: "fail: gke, empty public key id",
attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"),
publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"),
publicKeyID: pointerTo(""),
err: serrors.ErrorNoValidSignature,
},
{
name: "fail: gke, wrong key id",
attestationPath: pointerTo("gce/v1/gke-gce-pre.bcid-vsa.jsonl"),
publicKeyPath: pointerTo("gce/v1/vsa_signing_public_key.pem"),
publicKeyID: pointerTo("my_key_id"),
err: serrors.ErrorNoValidSignature,
},
}
for _, tt := range tests {
tt := tt // Re-initializing variable so it is not changed while executing the closure below
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
attestationPath := filepath.Clean(filepath.Join(TEST_DIR, "vsa", *tt.attestationPath))
publicKeyPath := filepath.Clean(filepath.Join(TEST_DIR, "vsa", *tt.publicKeyPath))
cmd := verify.VerifyVSACommand{
AttestationPath: &attestationPath,
SubjectDigests: tt.subjectDigests,
VerifierID: tt.verifierID,
ResourceURI: tt.resourceURI,
VerifiedLevels: tt.verifiedLevels,
PublicKeyPath: &publicKeyPath,
PublicKeyID: tt.publicKeyID,
}
err := cmd.Exec(context.Background())
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error (-want +got): \n%s", diff)
}
})
}
}
func pointerTo[K any](object K) *K {
return &object
}