diff --git a/cli/slsa-verifier/main.go b/cli/slsa-verifier/main.go index 4b9f276..f38d355 100644 --- a/cli/slsa-verifier/main.go +++ b/cli/slsa-verifier/main.go @@ -13,6 +13,7 @@ import ( serrors "github.com/slsa-framework/slsa-verifier/errors" "github.com/slsa-framework/slsa-verifier/options" "github.com/slsa-framework/slsa-verifier/verifiers" + "github.com/slsa-framework/slsa-verifier/verifiers/container" ) type workflowInputs struct { @@ -23,6 +24,7 @@ var ( provenancePath string builderID string artifactPath string + artifactImage string source string branch string tag string @@ -58,6 +60,7 @@ func main() { } flag.StringVar(&provenancePath, "provenance", "", "path to a provenance file") flag.StringVar(&artifactPath, "artifact-path", "", "path to an artifact to verify") + flag.StringVar(&artifactImage, "artifact-image", "", "name of the OCI image to verify") flag.StringVar(&source, "source", "", "expected source repository that should have produced the binary, e.g. github.com/some/repo") flag.StringVar(&branch, "branch", "", "[optional] expected branch the binary was compiled from") @@ -71,7 +74,19 @@ func main() { "[optional] a workflow input provided by a user at trigger time in the format 'key=value'. (Only for 'workflow_dispatch' events).") flag.Parse() - if provenancePath == "" || artifactPath == "" || source == "" { + if (provenancePath == "" || artifactPath == "") && artifactImage == "" { + fmt.Fprintf(os.Stderr, "either 'provenance' and 'artifact-path' or 'artifact-image' must be specified\n") + flag.Usage() + os.Exit(1) + } + + if artifactImage != "" && (provenancePath != "" || artifactPath != "") { + fmt.Fprintf(os.Stderr, "'provenance' and 'artifact-path' should not be specified when 'artifact-image' is provided\n") + flag.Usage() + os.Exit(1) + } + + if source == "" { flag.Usage() os.Exit(1) } @@ -97,7 +112,7 @@ func main() { os.Exit(1) } - verifiedProvenance, _, err := runVerify(artifactPath, provenancePath, source, + verifiedProvenance, _, err := runVerify(artifactImage, artifactPath, provenancePath, source, pbranch, pbuilderID, ptag, pversiontag, inputs.AsMap()) if err != nil { fmt.Fprintf(os.Stderr, "FAILED: SLSA verification failed: %v\n", err) @@ -105,7 +120,6 @@ func main() { } fmt.Fprintf(os.Stderr, "PASSED: Verified SLSA provenance\n") - if printProvenance { fmt.Fprintf(os.Stdout, "%s\n", string(verifiedProvenance)) } @@ -121,25 +135,16 @@ func isFlagPassed(name string) bool { return found } -func runVerify(artifactPath, provenancePath, source string, +func runVerify(artifactImage, artifactPath, provenancePath, source string, branch, builderID, ptag, pversiontag *string, inputs map[string]string, ) ([]byte, string, error) { - f, err := os.Open(artifactPath) + ctx := context.Background() + + // Artifact hash retrieval depends on the artifact type. + artifactHash, err := getArtifactHash(artifactImage, artifactPath) if err != nil { return nil, "", err } - defer f.Close() - - provenance, err := os.ReadFile(provenancePath) - if err != nil { - return nil, "", err - } - - h := sha256.New() - if _, err := io.Copy(h, f); err != nil { - return nil, "", err - } - artifactHash := hex.EncodeToString(h.Sum(nil)) provenanceOpts := &options.ProvenanceOpts{ ExpectedSourceURI: source, @@ -154,7 +159,30 @@ func runVerify(artifactPath, provenancePath, source string, ExpectedID: builderID, } - ctx := context.Background() - return verifiers.Verify(ctx, provenance, - artifactHash, provenanceOpts, builderOpts) + var provenance []byte + if provenancePath != "" { + provenance, err = os.ReadFile(provenancePath) + if err != nil { + return nil, "", err + } + } + + return verifiers.Verify(ctx, artifactImage, provenance, artifactHash, provenanceOpts, builderOpts) +} + +func getArtifactHash(artifactImage, artifactPath string) (string, error) { + if artifactPath != "" { + f, err := os.Open(artifactPath) + if err != nil { + return "", err + } + defer f.Close() + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil + } + // Retrieve image digest + return container.GetImageDigest(artifactImage) } diff --git a/cli/slsa-verifier/main_test.go b/cli/slsa-verifier/main_test.go index a4832f5..9fb8f78 100644 --- a/cli/slsa-verifier/main_test.go +++ b/cli/slsa-verifier/main_test.go @@ -1,18 +1,25 @@ package main import ( + "context" "errors" "fmt" "io/ioutil" "path/filepath" + "strings" "testing" "golang.org/x/mod/semver" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/sigstore/cosign/pkg/cosign" + "github.com/sigstore/cosign/pkg/oci" + "github.com/sigstore/cosign/pkg/oci/layout" serrors "github.com/slsa-framework/slsa-verifier/errors" + "github.com/slsa-framework/slsa-verifier/verifiers/container" ) func errCmp(e1, e2 error) bool { @@ -23,29 +30,51 @@ func pString(s string) *string { return &s } -// Versions of the builders to test. -// TODO: Enable v1.0.0 for go builder -var generatorVersions = map[string][]string{ - "v0.0.2": {"go"}, - "v1.1.1": {"go"}, - "v1.2.0": {"generic"}, -} - const TEST_DIR = "./testdata" -func Test_runVerify(t *testing.T) { +var ARTIFACT_PATH_BUILDERS = []string{"go", "generic"} +var ARTIFACT_IMAGE_BUILDERS = []string{"generic_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_runVerifyArtifactPath(t *testing.T) { t.Parallel() tests := []struct { - name string - artifact string - source string - pbranch *string - ptag *string - pversiontag *string - pbuilderID *string - builderID string - inputs map[string]string - err error + name string + artifact 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. @@ -56,11 +85,6 @@ func Test_runVerify(t *testing.T) { // specifying builders will restrict builders to only the specified ones. builders []string }{ - { - name: "valid main branch default", - artifact: "binary-linux-amd64-workflow_dispatch", - source: "github.com/slsa-framework/example-package", - }, { name: "valid main branch default", artifact: "binary-linux-amd64-workflow_dispatch", @@ -345,22 +369,22 @@ func Test_runVerify(t *testing.T) { builders: []string{"generic"}, }, { - name: "multiple subject second match - builderID", - artifact: "binary-linux-amd64-multi-subject-second", - source: "github.com/slsa-framework/example-package", - minversion: "v1.2.0", - builders: []string{"generic"}, - pbuilderID: pString("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml"), - builderID: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml", + name: "multiple subject second match - builderID", + artifact: "binary-linux-amd64-multi-subject-second", + source: "github.com/slsa-framework/example-package", + minversion: "v1.2.0", + builders: []string{"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", - artifact: "binary-linux-amd64-e2e-builder-repo", - source: "github.com/slsa-framework/example-package", - pbranch: pString("main"), - noversion: true, - builderID: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml", + name: "e2e test repository verified with builder at head", + artifact: "binary-linux-amd64-e2e-builder-repo", + source: "github.com/slsa-framework/example-package", + pbranch: pString("main"), + noversion: true, + outBuilderID: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml", }, // Malicious builders and workflows. { @@ -450,39 +474,8 @@ func Test_runVerify(t *testing.T) { 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() - getBuildersAndVersions := func(minversion string, ttBuilders []string) []string { - res := []string{} - builders := tt.builders - if len(builders) == 0 { - testdataDir, err := ioutil.ReadDir(TEST_DIR) - if err != nil { - t.Error(err) - } - for _, f := range testdataDir { - if f.IsDir() { - // These are the builder subfolders - builders = append(builders, f.Name()) - } - } - } - 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() && semver.Compare(minversion, f.Name()) <= 0 { - // These are the supported versions of the builder - res = append(res, filepath.Join(builder, f.Name())) - } - } - } - return res - } - checkVersions := getBuildersAndVersions(tt.minversion, tt.builders) + checkVersions := getBuildersAndVersions(t, tt.minversion, tt.builders, ARTIFACT_PATH_BUILDERS) if tt.noversion { checkVersions = []string{""} } @@ -492,7 +485,7 @@ func Test_runVerify(t *testing.T) { artifactPath := filepath.Clean(filepath.Join(TEST_DIR, v, tt.artifact)) provenancePath := fmt.Sprintf("%s.intoto.jsonl", artifactPath) - _, builderID, err := runVerify(artifactPath, + _, outBuilderId, err := runVerify("", artifactPath, provenancePath, tt.source, tt.pbranch, tt.pbuilderID, tt.ptag, tt.pversiontag, tt.inputs) @@ -505,8 +498,163 @@ func Test_runVerify(t *testing.T) { return } - if tt.builderID != "" && builderID != tt.builderID { - t.Errorf(cmp.Diff(builderID, tt.builderID)) + if tt.outBuilderID != "" && outBuilderId != tt.outBuilderID { + t.Errorf(cmp.Diff(outBuilderId, tt.outBuilderID)) + } + } + }) + } +} + +func Test_runVerifyArtifactImage(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) { + return cosign.VerifyLocalImageAttestations(ctx, image, co) + } + + // TODO: Is there a more uniform way of handling getting image digest for both + // remote and local images? + container.GetImageDigest = func(image string) (string, error) { + // This is copied from cosign's VerifyLocalImageAttestation code: + // https://github.com/sigstore/cosign/blob/fdceee4825dc5d56b130f3f431aab93137359e79/pkg/cosign/verify.go#L654 + se, err := layout.SignedImageIndex(image) + if err != nil { + return "", err + } + + var h v1.Hash + // Verify either an image index or image. + ii, err := se.SignedImageIndex(v1.Hash{}) + if err != nil { + return "", err + } + i, err := se.SignedImage(v1.Hash{}) + if err != nil { + return "", err + } + switch { + case ii != nil: + h, err = ii.Digest() + if err != nil { + return "", err + } + case i != nil: + h, err = i.Digest() + if err != nil { + return "", err + } + default: + return "", errors.New("must verify either an image index or image") + } + return strings.TrimPrefix(h.String(), "sha256:"), nil + } + + 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 + }{ + { + 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"), + err: serrors.ErrorMismatchTag, + }, + { + name: "versioned tag no match empty tag workflow_dispatch", + artifact: "container_workflow_dispatch", + source: "github.com/slsa-framework/example-package", + pversiontag: pString("v1"), + err: serrors.ErrorInvalidSemver, + }, + } + 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, ARTIFACT_IMAGE_BUILDERS) + if tt.noversion { + checkVersions = []string{""} + } + + for _, v := range checkVersions { + image := filepath.Clean(filepath.Join(TEST_DIR, v, tt.artifact)) + + _, outBuilderID, err := runVerify(image, "", "", + tt.source, tt.pbranch, tt.pbuilderID, + tt.ptag, tt.pversiontag, nil) + + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err, cmpopts.EquateErrors())) + } + + if err != nil { + return + } + + if tt.outBuilderID != "" && outBuilderID != tt.outBuilderID { + t.Errorf(cmp.Diff(outBuilderID, tt.outBuilderID)) } } }) diff --git a/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/2bc8d7da45cdfb2902cef3292a3211351079b15b0f14f0ae9d5f9eea98b8b2d2 b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/2bc8d7da45cdfb2902cef3292a3211351079b15b0f14f0ae9d5f9eea98b8b2d2 new file mode 100644 index 0000000..abddc5a --- /dev/null +++ b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/2bc8d7da45cdfb2902cef3292a3211351079b15b0f14f0ae9d5f9eea98b8b2d2 @@ -0,0 +1,21 @@ +{ + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "schemaVersion": 2, + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "digest": "sha256:5a00797e3d24011ca4f8b7942afa9687af250a8318767844471a2fdbaf631ed0", + "size": 2217 + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "digest": "sha256:b9f88661235d25835ef747dab426861d51c4e9923b92623d422d7ac58eb123e9", + "size": 804101 + }, + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "digest": "sha256:34ec90d105be03c3da0796a0f1077e9ba958f03490866030e7cddb821b89b170", + "size": 608695 + } + ] +} \ No newline at end of file diff --git a/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/34ec90d105be03c3da0796a0f1077e9ba958f03490866030e7cddb821b89b170 b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/34ec90d105be03c3da0796a0f1077e9ba958f03490866030e7cddb821b89b170 new file mode 100644 index 0000000..3181f30 Binary files /dev/null and b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/34ec90d105be03c3da0796a0f1077e9ba958f03490866030e7cddb821b89b170 differ diff --git a/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/5a00797e3d24011ca4f8b7942afa9687af250a8318767844471a2fdbaf631ed0 b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/5a00797e3d24011ca4f8b7942afa9687af250a8318767844471a2fdbaf631ed0 new file mode 100644 index 0000000..a3b3ccc --- /dev/null +++ b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/5a00797e3d24011ca4f8b7942afa9687af250a8318767844471a2fdbaf631ed0 @@ -0,0 +1 @@ +{"architecture":"amd64","author":"Bazel","config":{"User":"0","Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt"],"Entrypoint":["/app"],"WorkingDir":"/","Labels":{"org.opencontainers.image.created":"2022-08-05T03:44:18.437Z","org.opencontainers.image.description":"","org.opencontainers.image.licenses":"Apache-2.0","org.opencontainers.image.revision":"450428f4a68587015e502b9173a9fc017a1f7245","org.opencontainers.image.source":"https://github.com/slsa-framework/example-package","org.opencontainers.image.title":"example-package","org.opencontainers.image.url":"https://github.com/slsa-framework/example-package","org.opencontainers.image.version":"main"},"OnBuild":null},"created":"2022-08-05T03:44:39.163563554Z","history":[{"created":"1970-01-01T00:00:00Z","created_by":"bazel build ...","author":"Bazel"},{"created":"2022-08-05T03:44:39.163563554Z","created_by":"COPY /app/app /app # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2022-08-05T03:44:39.163563554Z","created_by":"ENTRYPOINT [\"/app\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}],"moby.buildkit.buildinfo.v1":"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvZ29sYW5nQHNoYTI1Njo5MzQ5ZWQ4ODlhZGI5MDZlZmE1ZWJjMDY0ODVmZTFiNmExMmZiMjY1YTAxYzkyNjZhMTM3YmIxMzUyNTY1NTYwIiwicGluIjoic2hhMjU2OjkzNDllZDg4OWFkYjkwNmVmYTVlYmMwNjQ4NWZlMWI2YTEyZmIyNjVhMDFjOTI2NmExMzdiYjEzNTI1NjU1NjAifSx7InR5cGUiOiJkb2NrZXItaW1hZ2UiLCJyZWYiOiJnY3IuaW8vZGlzdHJvbGVzcy9zdGF0aWNAc2hhMjU2OjIxZDNmODRhNGYzN2MzNjE5OWZkMDdhZDU1NDRkY2FmZWNjMTc3NzZlM2YzNjI4YmFmOWE1N2M4YzAxODFiM2YiLCJwaW4iOiJzaGEyNTY6MjFkM2Y4NGE0ZjM3YzM2MTk5ZmQwN2FkNTU0NGRjYWZlY2MxNzc3NmUzZjM2MjhiYWY5YTU3YzhjMDE4MWIzZiJ9LHsidHlwZSI6ImdpdCIsInJlZiI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UuZ2l0IzQ1MDQyOGY0YTY4NTg3MDE1ZTUwMmI5MTczYTlmYzAxN2ExZjcyNDUiLCJwaW4iOiI0NTA0MjhmNGE2ODU4NzAxNWU1MDJiOTE3M2E5ZmMwMTdhMWY3MjQ1In1dfQ==","os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:8d7366c22fd8219bfcfb61ed28457854c80e310b0d736b67861b2ea7fcd77843","sha256:b0e43299e29b2852f7ad0a20b4b531f4917c4e21c2954a0cc8971ee52ef1deb1"]}} \ No newline at end of file diff --git a/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/a2ece3a040c3cc7a511e60ef56bc2bfac79118e207e6297aaeaf9aa8fec5c015 b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/a2ece3a040c3cc7a511e60ef56bc2bfac79118e207e6297aaeaf9aa8fec5c015 new file mode 100644 index 0000000..4c106ad --- /dev/null +++ b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/a2ece3a040c3cc7a511e60ef56bc2bfac79118e207e6297aaeaf9aa8fec5c015 @@ -0,0 +1 @@ +{"payloadType":"application/vnd.in-toto+json","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsInN1YmplY3QiOlt7Im5hbWUiOiJnaGNyLmlvL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS5lMmUuY29udGFpbmVyLndvcmtmbG93X2Rpc3BhdGNoLm1haW4uZGVmYXVsdC5zbHNhMyIsImRpZ2VzdCI6eyJzaGEyNTYiOiIyYmM4ZDdkYTQ1Y2RmYjI5MDJjZWYzMjkyYTMyMTEzNTEwNzliMTViMGYxNGYwYWU5ZDVmOWVlYTk4YjhiMmQyIn19XSwicHJlZGljYXRlIjp7ImJ1aWxkZXIiOnsiaWQiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvc2xzYS1naXRodWItZ2VuZXJhdG9yLy5naXRodWIvd29ya2Zsb3dzL2dlbmVyYXRvcl9jb250YWluZXJfc2xzYTMueW1sQHJlZnMvaGVhZHMvbWFpbiJ9LCJidWlsZFR5cGUiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvc2xzYS1naXRodWItZ2VuZXJhdG9yL2dlbmVyaWNAdjEiLCJpbnZvY2F0aW9uIjp7ImNvbmZpZ1NvdXJjZSI6eyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZUByZWZzL2hlYWRzL21haW4iLCJkaWdlc3QiOnsic2hhMSI6IjQ1MDQyOGY0YTY4NTg3MDE1ZTUwMmI5MTczYTlmYzAxN2ExZjcyNDUifSwiZW50cnlQb2ludCI6Ii5naXRodWIvd29ya2Zsb3dzL2UyZS5jb250YWluZXIud29ya2Zsb3dfZGlzcGF0Y2gubWFpbi5kZWZhdWx0LnNsc2EzLnltbCJ9LCJwYXJhbWV0ZXJzIjp7fSwiZW52aXJvbm1lbnQiOnsiZ2l0aHViX2FjdG9yIjoiaWFubGV3aXMiLCJnaXRodWJfYWN0b3JfaWQiOiI0OTI4OSIsImdpdGh1Yl9iYXNlX3JlZiI6IiIsImdpdGh1Yl9ldmVudF9uYW1lIjoid29ya2Zsb3dfZGlzcGF0Y2giLCJnaXRodWJfZXZlbnRfcGF5bG9hZCI6eyJpbnB1dHMiOm51bGwsIm9yZ2FuaXphdGlvbiI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzgwNDMxMTg3P3Y9NCIsImRlc2NyaXB0aW9uIjoiU3VwcGx5LWNoYWluIExldmVscyBmb3IgU29mdHdhcmUgQXJ0aWZhY3RzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vb3Jncy9zbHNhLWZyYW1ld29yay9ldmVudHMiLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3Mvc2xzYS1mcmFtZXdvcmsvaG9va3MiLCJpZCI6ODA0MzExODcsImlzc3Vlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3Mvc2xzYS1mcmFtZXdvcmsvaXNzdWVzIiwibG9naW4iOiJzbHNhLWZyYW1ld29yayIsIm1lbWJlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9vcmdzL3Nsc2EtZnJhbWV3b3JrL21lbWJlcnN7L21lbWJlcn0iLCJub2RlX2lkIjoiTURFeU9rOXlaMkZ1YVhwaGRHbHZiamd3TkRNeE1UZzMiLCJwdWJsaWNfbWVtYmVyc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3Mvc2xzYS1mcmFtZXdvcmsvcHVibGljX21lbWJlcnN7L21lbWJlcn0iLCJyZXBvc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3Mvc2xzYS1mcmFtZXdvcmsvcmVwb3MiLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL29yZ3Mvc2xzYS1mcmFtZXdvcmsifSwicmVmIjoicmVmcy9oZWFkcy9tYWluIiwicmVwb3NpdG9yeSI6eyJhbGxvd19mb3JraW5nIjp0cnVlLCJhcmNoaXZlX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL3thcmNoaXZlX2Zvcm1hdH17L3JlZn0iLCJhcmNoaXZlZCI6ZmFsc2UsImFzc2lnbmVlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9hc3NpZ25lZXN7L3VzZXJ9IiwiYmxvYnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvZ2l0L2Jsb2Jzey9zaGF9IiwiYnJhbmNoZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvYnJhbmNoZXN7L2JyYW5jaH0iLCJjbG9uZV91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlLmdpdCIsImNvbGxhYm9yYXRvcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvY29sbGFib3JhdG9yc3svY29sbGFib3JhdG9yfSIsImNvbW1lbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2NvbW1lbnRzey9udW1iZXJ9IiwiY29tbWl0c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9jb21taXRzey9zaGF9IiwiY29tcGFyZV91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9jb21wYXJlL3tiYXNlfS4uLntoZWFkfSIsImNvbnRlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2NvbnRlbnRzL3srcGF0aH0iLCJjb250cmlidXRvcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvY29udHJpYnV0b3JzIiwiY3JlYXRlZF9hdCI6IjIwMjItMDQtMjdUMTk6MzA6NDNaIiwiZGVmYXVsdF9icmFuY2giOiJtYWluIiwiZGVwbG95bWVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvZGVwbG95bWVudHMiLCJkZXNjcmlwdGlvbiI6bnVsbCwiZGlzYWJsZWQiOmZhbHNlLCJkb3dubG9hZHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvZG93bmxvYWRzIiwiZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2V2ZW50cyIsImZvcmsiOmZhbHNlLCJmb3JrcyI6OCwiZm9ya3NfY291bnQiOjgsImZvcmtzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2ZvcmtzIiwiZnVsbF9uYW1lIjoic2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlIiwiZ2l0X2NvbW1pdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvZ2l0L2NvbW1pdHN7L3NoYX0iLCJnaXRfcmVmc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9naXQvcmVmc3svc2hhfSIsImdpdF90YWdzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2dpdC90YWdzey9zaGF9IiwiZ2l0X3VybCI6ImdpdDovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlLmdpdCIsImhhc19kb3dubG9hZHMiOnRydWUsImhhc19pc3N1ZXMiOnRydWUsImhhc19wYWdlcyI6ZmFsc2UsImhhc19wcm9qZWN0cyI6dHJ1ZSwiaGFzX3dpa2kiOnRydWUsImhvbWVwYWdlIjpudWxsLCJob29rc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9ob29rcyIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZSIsImlkIjo0ODYzMjU4MDksImlzX3RlbXBsYXRlIjpmYWxzZSwiaXNzdWVfY29tbWVudF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9pc3N1ZXMvY29tbWVudHN7L251bWJlcn0iLCJpc3N1ZV9ldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvaXNzdWVzL2V2ZW50c3svbnVtYmVyfSIsImlzc3Vlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9pc3N1ZXN7L251bWJlcn0iLCJrZXlzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2tleXN7L2tleV9pZH0iLCJsYWJlbHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvbGFiZWxzey9uYW1lfSIsImxhbmd1YWdlIjoiU3RhcmxhcmsiLCJsYW5ndWFnZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvbGFuZ3VhZ2VzIiwibGljZW5zZSI6eyJrZXkiOiJhcGFjaGUtMi4wIiwibmFtZSI6IkFwYWNoZSBMaWNlbnNlIDIuMCIsIm5vZGVfaWQiOiJNRGM2VEdsalpXNXpaVEk9Iiwic3BkeF9pZCI6IkFwYWNoZS0yLjAiLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL2xpY2Vuc2VzL2FwYWNoZS0yLjAifSwibWVyZ2VzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL21lcmdlcyIsIm1pbGVzdG9uZXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvbWlsZXN0b25lc3svbnVtYmVyfSIsIm1pcnJvcl91cmwiOm51bGwsIm5hbWUiOiJleGFtcGxlLXBhY2thZ2UiLCJub2RlX2lkIjoiUl9rZ0RPSFB5LU1RIiwibm90aWZpY2F0aW9uc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9ub3RpZmljYXRpb25zez9zaW5jZSxhbGwscGFydGljaXBhdGluZ30iLCJvcGVuX2lzc3VlcyI6MTcsIm9wZW5faXNzdWVzX2NvdW50IjoxNywib3duZXIiOnsiYXZhdGFyX3VybCI6Imh0dHBzOi8vYXZhdGFycy5naXRodWJ1c2VyY29udGVudC5jb20vdS84MDQzMTE4Nz92PTQiLCJldmVudHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9zbHNhLWZyYW1ld29yay9ldmVudHN7L3ByaXZhY3l9IiwiZm9sbG93ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvc2xzYS1mcmFtZXdvcmsvZm9sbG93ZXJzIiwiZm9sbG93aW5nX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvc2xzYS1mcmFtZXdvcmsvZm9sbG93aW5ney9vdGhlcl91c2VyfSIsImdpc3RzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvc2xzYS1mcmFtZXdvcmsvZ2lzdHN7L2dpc3RfaWR9IiwiZ3JhdmF0YXJfaWQiOiIiLCJodG1sX3VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yayIsImlkIjo4MDQzMTE4NywibG9naW4iOiJzbHNhLWZyYW1ld29yayIsIm5vZGVfaWQiOiJNREV5T2s5eVoyRnVhWHBoZEdsdmJqZ3dORE14TVRnMyIsIm9yZ2FuaXphdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9zbHNhLWZyYW1ld29yay9vcmdzIiwicmVjZWl2ZWRfZXZlbnRzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvc2xzYS1mcmFtZXdvcmsvcmVjZWl2ZWRfZXZlbnRzIiwicmVwb3NfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9zbHNhLWZyYW1ld29yay9yZXBvcyIsInNpdGVfYWRtaW4iOmZhbHNlLCJzdGFycmVkX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvc2xzYS1mcmFtZXdvcmsvc3RhcnJlZHsvb3duZXJ9ey9yZXBvfSIsInN1YnNjcmlwdGlvbnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9zbHNhLWZyYW1ld29yay9zdWJzY3JpcHRpb25zIiwidHlwZSI6Ik9yZ2FuaXphdGlvbiIsInVybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvc2xzYS1mcmFtZXdvcmsifSwicHJpdmF0ZSI6ZmFsc2UsInB1bGxzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL3B1bGxzey9udW1iZXJ9IiwicHVzaGVkX2F0IjoiMjAyMi0wOC0wNVQwMzozODo1MloiLCJyZWxlYXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9yZWxlYXNlc3svaWR9Iiwic2l6ZSI6MzE0MCwic3NoX3VybCI6ImdpdEBnaXRodWIuY29tOnNsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS5naXQiLCJzdGFyZ2F6ZXJzX2NvdW50Ijo0LCJzdGFyZ2F6ZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL3N0YXJnYXplcnMiLCJzdGF0dXNlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9zdGF0dXNlcy97c2hhfSIsInN1YnNjcmliZXJzX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL3N1YnNjcmliZXJzIiwic3Vic2NyaXB0aW9uX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vcmVwb3Mvc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL3N1YnNjcmlwdGlvbiIsInN2bl91cmwiOiJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlIiwidGFnc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS90YWdzIiwidGVhbXNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvdGVhbXMiLCJ0b3BpY3MiOltdLCJ0cmVlc191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9naXQvdHJlZXN7L3NoYX0iLCJ1cGRhdGVkX2F0IjoiMjAyMi0wNi0xMFQxNTozNDo1NVoiLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZSIsInZpc2liaWxpdHkiOiJwdWJsaWMiLCJ3YXRjaGVycyI6NCwid2F0Y2hlcnNfY291bnQiOjQsIndlYl9jb21taXRfc2lnbm9mZl9yZXF1aXJlZCI6dHJ1ZX0sInNlbmRlciI6eyJhdmF0YXJfdXJsIjoiaHR0cHM6Ly9hdmF0YXJzLmdpdGh1YnVzZXJjb250ZW50LmNvbS91LzQ5Mjg5P3Y9NCIsImV2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2lhbmxld2lzL2V2ZW50c3svcHJpdmFjeX0iLCJmb2xsb3dlcnNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9pYW5sZXdpcy9mb2xsb3dlcnMiLCJmb2xsb3dpbmdfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9pYW5sZXdpcy9mb2xsb3dpbmd7L290aGVyX3VzZXJ9IiwiZ2lzdHNfdXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9pYW5sZXdpcy9naXN0c3svZ2lzdF9pZH0iLCJncmF2YXRhcl9pZCI6IiIsImh0bWxfdXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL2lhbmxld2lzIiwiaWQiOjQ5Mjg5LCJsb2dpbiI6Imlhbmxld2lzIiwibm9kZV9pZCI6Ik1EUTZWWE5sY2pRNU1qZzUiLCJvcmdhbml6YXRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvaWFubGV3aXMvb3JncyIsInJlY2VpdmVkX2V2ZW50c191cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2lhbmxld2lzL3JlY2VpdmVkX2V2ZW50cyIsInJlcG9zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvaWFubGV3aXMvcmVwb3MiLCJzaXRlX2FkbWluIjpmYWxzZSwic3RhcnJlZF91cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3VzZXJzL2lhbmxld2lzL3N0YXJyZWR7L293bmVyfXsvcmVwb30iLCJzdWJzY3JpcHRpb25zX3VybCI6Imh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvaWFubGV3aXMvc3Vic2NyaXB0aW9ucyIsInR5cGUiOiJVc2VyIiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS91c2Vycy9pYW5sZXdpcyJ9LCJ3b3JrZmxvdyI6Ii5naXRodWIvd29ya2Zsb3dzL2UyZS5jb250YWluZXIud29ya2Zsb3dfZGlzcGF0Y2gubWFpbi5kZWZhdWx0LnNsc2EzLnltbCJ9LCJnaXRodWJfaGVhZF9yZWYiOiIiLCJnaXRodWJfcmVmIjoicmVmcy9oZWFkcy9tYWluIiwiZ2l0aHViX3JlZl90eXBlIjoiYnJhbmNoIiwiZ2l0aHViX3JlcG9zaXRvcnlfaWQiOiI0ODYzMjU4MDkiLCJnaXRodWJfcmVwb3NpdG9yeV9vd25lciI6InNsc2EtZnJhbWV3b3JrIiwiZ2l0aHViX3JlcG9zaXRvcnlfb3duZXJfaWQiOiI4MDQzMTE4NyIsImdpdGh1Yl9ydW5fYXR0ZW1wdCI6IjIiLCJnaXRodWJfcnVuX2lkIjoiMjgwMTExODU3OSIsImdpdGh1Yl9ydW5fbnVtYmVyIjoiMSIsImdpdGh1Yl9zaGExIjoiNDUwNDI4ZjRhNjg1ODcwMTVlNTAyYjkxNzNhOWZjMDE3YTFmNzI0NSJ9fSwibWV0YWRhdGEiOnsiYnVpbGRJbnZvY2F0aW9uSUQiOiIyODAxMTE4NTc5LTIiLCJjb21wbGV0ZW5lc3MiOnsicGFyYW1ldGVycyI6dHJ1ZSwiZW52aXJvbm1lbnQiOmZhbHNlLCJtYXRlcmlhbHMiOmZhbHNlfSwicmVwcm9kdWNpYmxlIjpmYWxzZX0sIm1hdGVyaWFscyI6W3sidXJpIjoiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2VAcmVmcy9oZWFkcy9tYWluIiwiZGlnZXN0Ijp7InNoYTEiOiI0NTA0MjhmNGE2ODU4NzAxNWU1MDJiOTE3M2E5ZmMwMTdhMWY3MjQ1In19XX19","signatures":[{"keyid":"","sig":"MEYCIQCluOaA1P8bWxa8qPVXamI/0UoVQOc8+kI7VtrDWydq4wIhAIb8n+1cl2JHho8dB+pBN1ePLHked+vj7htgxIZik9Kl"}]} \ No newline at end of file diff --git a/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/b7773d35be754aa50e484d9cac8cfc3696e8dfa937be9783c7f7cd21872816ee b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/b7773d35be754aa50e484d9cac8cfc3696e8dfa937be9783c7f7cd21872816ee new file mode 100644 index 0000000..c80e599 --- /dev/null +++ b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/b7773d35be754aa50e484d9cac8cfc3696e8dfa937be9783c7f7cd21872816ee @@ -0,0 +1 @@ +{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","size":233,"digest":"sha256:f9b849875a33c0a9f8a6b471d32146a96e252e8932c0391de0ef90c567d4d550"},"layers":[{"mediaType":"application/vnd.dsse.envelope.v1+json","size":12108,"digest":"sha256:a2ece3a040c3cc7a511e60ef56bc2bfac79118e207e6297aaeaf9aa8fec5c015","annotations":{"dev.cosignproject.cosign/signature":"","dev.sigstore.cosign/bundle":"{\"SignedEntryTimestamp\":\"MEUCIG2qb5QlmsGWZ89660rL8+l1iziU9pF3disW9ComofltAiEAjL0tGaKBPox181dqOm5ikEnSKbwzCmIKr6LSrLrJTjI=\",\"Payload\":{\"body\":\"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaW50b3RvIiwic3BlYyI6eyJjb250ZW50Ijp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJhMmVjZTNhMDQwYzNjYzdhNTExZTYwZWY1NmJjMmJmYWM3OTExOGUyMDdlNjI5N2FhZWFmOWFhOGZlYzVjMDE1In0sInBheWxvYWRIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiOWY0MjNhMDE5MzU2MDY1MDM2YTRiN2UxYzRmMTE4OGFlNTM5ZTMzZjU3NDk2ZWU4ZDdjZjgyZDc4NGQ4ZDE5NiJ9fSwicHVibGljS2V5IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVVkZha05EUVRWbFowRjNTVUpCWjBsVlkzWk1kMVl5WjNGbmREbFVWRFZRZDNsVFZrbzRia2xXWTJwRmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcEpkMDlFUVRGTlJFMHdUbnBSTUZkb1kwNU5ha2wzVDBSQk1VMUVUVEZPZWxFd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVY0ZG5CQ2NESk1kVVZ1ZFhkbFdEZzNUbnBNY0RBNVMyVnplSFEyYnpGeGJWaHllWFlLY1cwck1sUTBOVXB0TlRKb1VESkthWFprVGxKcFoxSjJUM0o2WVVodWNWRTBTbFJqVkVWNVJuYzNSVE5pWWxCeFUzRlBRMEZ5V1hkblowdDVUVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXUkNaMDVXU0ZFMFJVWm5VVlYzTm5SbkNrd3laVlp4YTI1cVpGSnRVMmROY3pJeGNUWTVVVkV3ZDBoM1dVUldVakJxUWtKbmQwWnZRVlV6T1ZCd2VqRlphMFZhWWpWeFRtcHdTMFpYYVhocE5Ga0tXa1E0ZDJkWlZVZEJNVlZrUlZGRlFpOTNVamROU0cxSFpESm9NR1JJUW5wUGFUaDJXakpzTUdGSVZtbE1iVTUyWWxNNWVtSklUbWhNVjFwNVdWY3hiQXBrTWpsNVlYazVlbUpJVG1oTVYyUndaRWRvTVZscE1XNWFWelZzWTIxR01HSXpTWFpNYldSd1pFZG9NVmxwT1ROaU0wcHlXbTE0ZG1RelRYWmFNbFoxQ2xwWVNtaGtSemw1V0RKT2RtSnVVbWhoVnpWc1kydzVlbUpJVG1oTmVUVTFZbGQ0UVdOdFZtMWplVGx2V2xkR2EyTjVPWFJaVjJ4MVRVUnJSME5wYzBjS1FWRlJRbWMzT0hkQlVVVkZTekpvTUdSSVFucFBhVGgyWkVjNWNscFhOSFZaVjA0d1lWYzVkV041Tlc1aFdGSnZaRmRLTVdNeVZubFpNamwxWkVkV2RRcGtRelZxWWpJd2QwaDNXVXRMZDFsQ1FrRkhSSFo2UVVKQloxRlNaREk1ZVdFeVduTmlNMlJtV2tkc2VtTkhSakJaTW1kM1RtZFpTMHQzV1VKQ1FVZEVDblo2UVVKQmQxRnZUa1JWZDA1RVNUUmFhbEpvVG1wbk1VOUVZM2ROVkZac1RsUkJlVmxxYTNoT2VrNW9UMWRhYWsxRVJUTlpWRVp0VG5wSk1FNVVRbGNLUW1kdmNrSm5SVVZCV1U4dlRVRkZSVUpGWjNWYU1td3dZVWhXYVV3elpIWmpiWFJ0WWtjNU0yTjVPV3hOYlZWMVdUSTVkV1JIUm5CaWJWWjVURzVrZGdwamJYUnRZa2M1TTFneVVuQmpNMEpvWkVkT2IweHRNV2hoVnpSMVdrZFdiVmxZVm5Oa1F6VjZZa2hPYUUxNU5UVmlWM2QzVEVGWlMwdDNXVUpDUVVkRUNuWjZRVUpDVVZGbFl6SjRlbGxUTVcxamJVWjBXbGhrZG1OdGMzWmFXR2hvWWxoQ2MxcFRNWGRaVjA1eVdWZGtiRTFDTUVkRGFYTkhRVkZSUW1jM09IY0tRVkZaUlVRelNteGFiazEyWVVkV2FGcElUWFppVjBad1ltcERRbWxSV1V0TGQxbENRa0ZJVjJWUlNVVkJaMUkzUWtoclFXUjNRakZCUVdobmEzWkJid3BWZGpsdlVtUklVbUY1WlVWdVJWWnVSMHQzVjFCalRUUXdiVE50ZGtOSlIwNXRPWGxCUVVGQ1oyMTNaSFV5UlVGQlFWRkVRVVZaZDFKQlNXZERRMjl5Q21sTk5WaHRTV1p1YUZCVE5rWklUVGh6YmxOVlFUTXpZVlp4VjNSNVFYbDVZMHRpTjBsVWQwTkpSbEl6TmtwVWJrVjVObEJLVFRFM09GVlFVbmxNVGxnS1EwNXpSekJLU0cxbVNrWjBkemhCUnpoUmVGVk5RVzlIUTBOeFIxTk5ORGxDUVUxRVFUSnJRVTFIV1VOTlVVUk1XWFEzVkVoRGFUQmtUVTA0VEhsNldBcG9MMGMxY2xaSWEzcHZNVGt3WkRWc2MydE1MMGhTWVRScmFHcHJiVkJsUTFvd1J6QlpPVU0wVUU1RlMxcGtNRU5OVVVSbllWVm9hRGsyZGxJNFJVaFBDbVJaYkN0MFFVaEllRVY1ZWt3NFp6ZEZjbU4xTnpoRmR6SXlkbXBhV1VjellYRndTelZMZERSeGFYVTBjbGhHV1hOQ05EMEtMUzB0TFMxRlRrUWdRMFZTVkVsR1NVTkJWRVV0TFMwdExRbz0ifX0=\",\"integratedTime\":1659671264,\"logIndex\":3116401,\"logID\":\"c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d\"}}","dev.sigstore.cosign/certificate":"-----BEGIN CERTIFICATE-----\nMIIEEjCCA5egAwIBAgIUcvLwV2gqgt9TT5PwySVJ8nIVcjEwCgYIKoZIzj0EAwMw\nNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl\ncm1lZGlhdGUwHhcNMjIwODA1MDM0NzQ0WhcNMjIwODA1MDM1NzQ0WjAAMFkwEwYH\nKoZIzj0CAQYIKoZIzj0DAQcDQgAExvpBp2LuEnuweX87NzLp09Kesxt6o1qmXryv\nqm+2T45Jm52hP2JivdNRigRvOrzaHnqQ4JTcTEyFw7E3bbPqSqOCArYwggKyMA4G\nA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUw6tg\nL2eVqknjdRmSgMs21q69QQ0wHwYDVR0jBBgwFoAU39Ppz1YkEZb5qNjpKFWixi4Y\nZD8wgYUGA1UdEQEB/wR7MHmGd2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1l\nd29yay9zbHNhLWdpdGh1Yi1nZW5lcmF0b3IvLmdpdGh1Yi93b3JrZmxvd3MvZ2Vu\nZXJhdG9yX2NvbnRhaW5lcl9zbHNhMy55bWxAcmVmcy9oZWFkcy9tYWluMDkGCisG\nAQQBg78wAQEEK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVu\ndC5jb20wHwYKKwYBBAGDvzABAgQRd29ya2Zsb3dfZGlzcGF0Y2gwNgYKKwYBBAGD\nvzABAwQoNDUwNDI4ZjRhNjg1ODcwMTVlNTAyYjkxNzNhOWZjMDE3YTFmNzI0NTBW\nBgorBgEEAYO/MAEEBEguZ2l0aHViL3dvcmtmbG93cy9lMmUuY29udGFpbmVyLndv\ncmtmbG93X2Rpc3BhdGNoLm1haW4uZGVmYXVsdC5zbHNhMy55bWwwLAYKKwYBBAGD\nvzABBQQec2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlMB0GCisGAQQBg78w\nAQYED3JlZnMvaGVhZHMvbWFpbjCBiQYKKwYBBAHWeQIEAgR7BHkAdwB1AAhgkvAo\nUv9oRdHRayeEnEVnGKwWPcM40m3mvCIGNm9yAAABgmwdu2EAAAQDAEYwRAIgCCor\niM5XmIfnhPS6FHM8snSUA33aVqWtyAyycKb7ITwCIFR36JTnEy6PJM178UPRyLNX\nCNsG0JHmfJFtw8AG8QxUMAoGCCqGSM49BAMDA2kAMGYCMQDLYt7THCi0dMM8LyzX\nh/G5rVHkzo190d5lskL/HRa4khjkmPeCZ0G0Y9C4PNEKZd0CMQDgaUhh96vR8EHO\ndYl+tAHHxEyzL8g7Ercu78Ew22vjZYG3aqpK5Kt4qiu4rXFYsB4=\n-----END CERTIFICATE-----\n","dev.sigstore.cosign/chain":"-----BEGIN CERTIFICATE-----\nMIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0C\nAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV7\n7LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS\n0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYB\nBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjp\nKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZI\nzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJR\nnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsP\nmygUY7Ii2zbdCdliiow=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw\nKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y\nMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl\nLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7\nXeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex\nX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j\nYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY\nwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ\nKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM\nWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9\nTNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ\n-----END CERTIFICATE-----"}}]} \ No newline at end of file diff --git a/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/b9f88661235d25835ef747dab426861d51c4e9923b92623d422d7ac58eb123e9 b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/b9f88661235d25835ef747dab426861d51c4e9923b92623d422d7ac58eb123e9 new file mode 100644 index 0000000..3d9245d Binary files /dev/null and b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/b9f88661235d25835ef747dab426861d51c4e9923b92623d422d7ac58eb123e9 differ diff --git a/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/f9b849875a33c0a9f8a6b471d32146a96e252e8932c0391de0ef90c567d4d550 b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/f9b849875a33c0a9f8a6b471d32146a96e252e8932c0391de0ef90c567d4d550 new file mode 100644 index 0000000..6e9707f --- /dev/null +++ b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/blobs/sha256/f9b849875a33c0a9f8a6b471d32146a96e252e8932c0391de0ef90c567d4d550 @@ -0,0 +1 @@ +{"architecture":"","created":"0001-01-01T00:00:00Z","history":[{"created":"0001-01-01T00:00:00Z"}],"os":"","rootfs":{"type":"layers","diff_ids":["sha256:a2ece3a040c3cc7a511e60ef56bc2bfac79118e207e6297aaeaf9aa8fec5c015"]},"config":{}} \ No newline at end of file diff --git a/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/index.json b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/index.json new file mode 100755 index 0000000..5d28a31 --- /dev/null +++ b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/index.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": 2, + "manifests": [ + { + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "size": 737, + "digest": "sha256:2bc8d7da45cdfb2902cef3292a3211351079b15b0f14f0ae9d5f9eea98b8b2d2", + "annotations": { + "kind": "dev.cosignproject.cosign/image" + } + }, + { + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "size": 6898, + "digest": "sha256:b7773d35be754aa50e484d9cac8cfc3696e8dfa937be9783c7f7cd21872816ee", + "annotations": { + "kind": "dev.cosignproject.cosign/atts" + } + } + ] +} \ No newline at end of file diff --git a/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/oci-layout b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/oci-layout new file mode 100755 index 0000000..224a869 --- /dev/null +++ b/cli/slsa-verifier/testdata/generic_container/v1.2.0/container_workflow_dispatch/oci-layout @@ -0,0 +1,3 @@ +{ + "imageLayoutVersion": "1.0.0" +} \ No newline at end of file diff --git a/experimental/rest/service.go b/experimental/rest/service.go index 79da852..5187366 100644 --- a/experimental/rest/service.go +++ b/experimental/rest/service.go @@ -129,7 +129,7 @@ func verifyHandlerV1(r *http.Request) *v1Result { } ctx := context.Background() - p, builderID, err := verifiers.Verify(ctx, []byte(query.DsseEnvelope), + p, builderID, err := verifiers.Verify(ctx, "", []byte(query.DsseEnvelope), query.ArtifactHash, provenanceOpts, builderOpts) if err != nil { return results.withError(err) diff --git a/go.mod b/go.mod index 6492cc8..b347f90 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/Microsoft/go-winio v0.5.2 // indirect github.com/PaesslerAG/gval v1.0.0 // indirect github.com/PaesslerAG/jsonpath v0.1.1 // indirect github.com/ThalesIgnite/crypto11 v1.2.5 // indirect @@ -76,6 +77,8 @@ require ( github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/docker v20.10.17+incompatible // indirect github.com/docker/docker-credential-helpers v0.6.4 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.4.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect diff --git a/go.sum b/go.sum index ef38e60..6a3d166 100644 --- a/go.sum +++ b/go.sum @@ -186,6 +186,8 @@ github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JP github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= @@ -593,11 +595,13 @@ github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56 github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= github.com/docker/go v1.5.1-1 h1:hr4w35acWBPhGBXlzPoHpmZ/ygPjnmFVxGxxGnMyP7k= github.com/docker/go v1.5.1-1/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= diff --git a/register/register.go b/register/register.go index e0c6f80..9208915 100644 --- a/register/register.go +++ b/register/register.go @@ -23,7 +23,7 @@ type SLSAVerifier interface { // VerifyImage verifies a provenance for a supplied OCI image. VerifyImage(ctx context.Context, - provenance []byte, artifactHash string, + artifactImage string, provenanceOpts *options.ProvenanceOpts, builderOpts *options.BuilderOpts, ) ([]byte, string, error) diff --git a/verifiers/container/container.go b/verifiers/container/container.go new file mode 100644 index 0000000..2da9992 --- /dev/null +++ b/verifiers/container/container.go @@ -0,0 +1,15 @@ +package container + +import ( + "strings" + + "github.com/google/go-containerregistry/pkg/crane" +) + +var GetImageDigest = func(image string) (string, error) { + digest, err := crane.Digest(image) + if err != nil { + return "", err + } + return strings.TrimPrefix(digest, "sha256:"), nil +} diff --git a/verifiers/container/cosign.go b/verifiers/container/cosign.go new file mode 100644 index 0000000..2cc6f28 --- /dev/null +++ b/verifiers/container/cosign.go @@ -0,0 +1,18 @@ +package container + +import ( + "context" + + crname "github.com/google/go-containerregistry/pkg/name" + "github.com/sigstore/cosign/pkg/cosign" + "github.com/sigstore/cosign/pkg/oci" +) + +var RunCosignImageVerification = func(ctx context.Context, + image string, co *cosign.CheckOpts) ([]oci.Signature, bool, error) { + signedImgRef, err := crname.ParseReference(image) + if err != nil { + return nil, false, err + } + return cosign.VerifyImageAttestations(ctx, signedImgRef, co) +} diff --git a/verifiers/internal/gcb/verifier.go b/verifiers/internal/gcb/verifier.go index 4377a98..8a353f7 100644 --- a/verifiers/internal/gcb/verifier.go +++ b/verifiers/internal/gcb/verifier.go @@ -40,7 +40,7 @@ func (v *GCBVerifier) VerifyArtifact(ctx context.Context, // VerifyImage verifies provenance for an OCI image. func (v *GCBVerifier) VerifyImage(ctx context.Context, - provenance []byte, artifactHash string, + artifactImage string, provenanceOpts *options.ProvenanceOpts, builderOpts *options.BuilderOpts, ) ([]byte, string, error) { diff --git a/verifiers/internal/gha/builder.go b/verifiers/internal/gha/builder.go index 7168cb0..fe2e977 100644 --- a/verifiers/internal/gha/builder.go +++ b/verifiers/internal/gha/builder.go @@ -18,13 +18,21 @@ var ( certOidcIssuer = "https://token.actions.githubusercontent.com" ) -var defaultTrustedReusableWorkflows = map[string]bool{ +var defaultArtifactTrustedReusableWorkflows = map[string]bool{ trustedBuilderRepository + "/.github/workflows/generator_generic_slsa3.yml": true, trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml": true, } +var defaultContainerTrustedReusableWorkflows = map[string]bool{ + trustedBuilderRepository + "/.github/workflows/generator_container_slsa3.yml": true, +} + // VerifyWorkflowIdentity verifies the signing certificate information -func VerifyWorkflowIdentity(id *WorkflowIdentity, builderOpts *options.BuilderOpts, source string) (string, error) { +// Builder IDs are verified against an expected builder ID provided in the +// builerOpts, or against the set of defaultBuilders provided. +func VerifyWorkflowIdentity(id *WorkflowIdentity, + builderOpts *options.BuilderOpts, source string, + defaultBuilders map[string]bool) (string, error) { // cert URI path is /org/repo/path/to/workflow@ref workflowPath := strings.SplitN(id.JobWobWorkflowRef, "@", 2) if len(workflowPath) < 2 { @@ -33,7 +41,8 @@ func VerifyWorkflowIdentity(id *WorkflowIdentity, builderOpts *options.BuilderOp // Trusted workflow verification by name. reusableWorkflowPath := strings.Trim(workflowPath[0], "/") - builderID, err := verifyTrustedBuilderID(reusableWorkflowPath, builderOpts.ExpectedID) + builderID, err := verifyTrustedBuilderID(reusableWorkflowPath, + builderOpts.ExpectedID, defaultBuilders) if err != nil { return "", err } @@ -61,11 +70,13 @@ func VerifyWorkflowIdentity(id *WorkflowIdentity, builderOpts *options.BuilderOp return builderID, nil } -func verifyTrustedBuilderID(path string, builderID *string) (string, error) { +// Verifies the builder ID at path against an expected builderID. +// If an expected builderID is not provided, uses the defaultBuilders. +func verifyTrustedBuilderID(path string, builderID *string, defaultBuilders map[string]bool) (string, error) { // No builder ID provided by user: use the default trusted workflows. if builderID == nil || *builderID == "" { - if _, ok := defaultTrustedReusableWorkflows[path]; !ok { - return "", fmt.Errorf("%w: %s", serrors.ErrorUntrustedReusableWorkflow, path) + if _, ok := defaultBuilders[path]; !ok { + return "", fmt.Errorf("%w: %s got %t", serrors.ErrorUntrustedReusableWorkflow, path, builderID == nil) } } else { // Verify the builderID. diff --git a/verifiers/internal/gha/builder_test.go b/verifiers/internal/gha/builder_test.go index 3000bd0..1073d30 100644 --- a/verifiers/internal/gha/builder_test.go +++ b/verifiers/internal/gha/builder_test.go @@ -17,6 +17,7 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { workflow *WorkflowIdentity buildOpts *options.BuilderOpts builderID string + defaults map[string]bool source string err error }{ @@ -29,8 +30,9 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Trigger: "workflow_dispatch", Issuer: "https://token.actions.githubusercontent.com", }, - source: "asraa/slsa-on-github-test", - err: serrors.ErrorMalformedURI, + source: "asraa/slsa-on-github-test", + defaults: defaultArtifactTrustedReusableWorkflows, + err: serrors.ErrorMalformedURI, }, { name: "untrusted job workflow ref", @@ -41,8 +43,9 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Trigger: "workflow_dispatch", Issuer: "https://token.actions.githubusercontent.com", }, - source: "asraa/slsa-on-github-test", - err: serrors.ErrorUntrustedReusableWorkflow, + source: "asraa/slsa-on-github-test", + defaults: defaultArtifactTrustedReusableWorkflows, + err: serrors.ErrorUntrustedReusableWorkflow, }, { name: "untrusted job workflow ref for general repos", @@ -53,8 +56,9 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Trigger: "workflow_dispatch", Issuer: "https://bad.issuer.com", }, - source: "asraa/slsa-on-github-test", - err: serrors.ErrorInvalidRef, + source: "asraa/slsa-on-github-test", + defaults: defaultArtifactTrustedReusableWorkflows, + err: serrors.ErrorInvalidRef, }, { name: "valid main ref for trusted builder", @@ -66,6 +70,7 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Issuer: "https://token.actions.githubusercontent.com", }, source: trustedBuilderRepository, + defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", }, { @@ -78,6 +83,7 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Issuer: certOidcIssuer, }, source: e2eTestRepository, + defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", }, { @@ -93,6 +99,7 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml"), }, + defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", }, { @@ -108,7 +115,8 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("some-other-builderID"), }, - err: serrors.ErrorUntrustedReusableWorkflow, + defaults: defaultArtifactTrustedReusableWorkflows, + err: serrors.ErrorUntrustedReusableWorkflow, }, { name: "unexpected source for e2e test", @@ -121,6 +129,7 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { }, source: "malicious/source", err: serrors.ErrorMismatchSource, + defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", }, { @@ -131,8 +140,9 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Trigger: "workflow_dispatch", Issuer: certOidcIssuer, }, - source: "malicious/source", - err: serrors.ErrorMismatchSource, + source: "malicious/source", + defaults: defaultArtifactTrustedReusableWorkflows, + err: serrors.ErrorMismatchSource, }, { name: "unexpected source", @@ -143,8 +153,9 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Trigger: "workflow_dispatch", Issuer: certOidcIssuer, }, - source: "asraa/slsa-on-github-test", - err: serrors.ErrorMismatchSource, + source: "asraa/slsa-on-github-test", + defaults: defaultArtifactTrustedReusableWorkflows, + err: serrors.ErrorMismatchSource, }, { name: "valid workflow identity", @@ -156,6 +167,7 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Issuer: certOidcIssuer, }, source: "asraa/slsa-on-github-test", + defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", }, { @@ -171,6 +183,7 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml"), }, + defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", }, { @@ -186,7 +199,8 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("some-other-builderID"), }, - err: serrors.ErrorUntrustedReusableWorkflow, + defaults: defaultArtifactTrustedReusableWorkflows, + err: serrors.ErrorUntrustedReusableWorkflow, }, { name: "invalid workflow identity with prerelease", @@ -199,6 +213,7 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { }, source: "asraa/slsa-on-github-test", err: serrors.ErrorInvalidRef, + defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", }, { @@ -210,8 +225,9 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Trigger: "workflow_dispatch", Issuer: certOidcIssuer, }, - source: "asraa/slsa-on-github-test", - err: serrors.ErrorInvalidRef, + source: "asraa/slsa-on-github-test", + defaults: defaultArtifactTrustedReusableWorkflows, + err: serrors.ErrorInvalidRef, }, { name: "invalid workflow identity with metadata", @@ -222,8 +238,9 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Trigger: "workflow_dispatch", Issuer: certOidcIssuer, }, - source: "asraa/slsa-on-github-test", - err: serrors.ErrorInvalidRef, + source: "asraa/slsa-on-github-test", + defaults: defaultArtifactTrustedReusableWorkflows, + err: serrors.ErrorInvalidRef, }, { name: "valid workflow identity with fully qualified source", @@ -235,6 +252,22 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { Issuer: certOidcIssuer, }, source: "github.com/asraa/slsa-on-github-test", + defaults: defaultArtifactTrustedReusableWorkflows, + builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", + }, + { + name: "valid workflow identity with fully qualified source - no default", + workflow: &WorkflowIdentity{ + CallerRepository: "asraa/slsa-on-github-test", + CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + Trigger: "workflow_dispatch", + Issuer: certOidcIssuer, + }, + source: "github.com/asraa/slsa-on-github-test", + buildOpts: &options.BuilderOpts{ + ExpectedID: asStringPointer("https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml"), + }, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", }, { @@ -250,6 +283,7 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml"), }, + defaults: defaultArtifactTrustedReusableWorkflows, builderID: "https://github.com/" + trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml", }, { @@ -265,7 +299,21 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { buildOpts: &options.BuilderOpts{ ExpectedID: asStringPointer("some-other-builderID"), }, - err: serrors.ErrorUntrustedReusableWorkflow, + defaults: defaultArtifactTrustedReusableWorkflows, + err: serrors.ErrorUntrustedReusableWorkflow, + }, + { + name: "valid workflow identity with fully qualified source - mismatch defaults", + workflow: &WorkflowIdentity{ + CallerRepository: "asraa/slsa-on-github-test", + CallerHash: "0dfcd24824432c4ce587f79c918eef8fc2c44d7b", + JobWobWorkflowRef: trustedBuilderRepository + "/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.3", + Trigger: "workflow_dispatch", + Issuer: certOidcIssuer, + }, + source: "github.com/asraa/slsa-on-github-test", + defaults: defaultContainerTrustedReusableWorkflows, + err: serrors.ErrorUntrustedReusableWorkflow, }, } for _, tt := range tests { @@ -276,7 +324,8 @@ func Test_VerifyWorkflowIdentity(t *testing.T) { if opts == nil { opts = &options.BuilderOpts{} } - id, err := VerifyWorkflowIdentity(tt.workflow, opts, tt.source) + id, err := VerifyWorkflowIdentity(tt.workflow, opts, tt.source, + tt.defaults) if !errCmp(err, tt.err) { t.Errorf(cmp.Diff(err, tt.err, cmpopts.EquateErrors())) } @@ -300,11 +349,19 @@ func Test_verifyTrustedBuilderID(t *testing.T) { name string id *string path string + defaults map[string]bool expected error }{ { - name: "default trusted", - path: trustedBuilderRepository + "/.github/workflows/generator_generic_slsa3.yml", + name: "default trusted", + path: trustedBuilderRepository + "/.github/workflows/generator_generic_slsa3.yml", + defaults: defaultArtifactTrustedReusableWorkflows, + }, + { + name: "default mismatch against container defaults", + path: trustedBuilderRepository + "/.github/workflows/generator_generic_slsa3.yml", + defaults: defaultContainerTrustedReusableWorkflows, + expected: serrors.ErrorUntrustedReusableWorkflow, }, { name: "valid ID for GitHub builder", @@ -341,7 +398,7 @@ func Test_verifyTrustedBuilderID(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - id, err := verifyTrustedBuilderID(tt.path, tt.id) + id, err := verifyTrustedBuilderID(tt.path, tt.id, tt.defaults) if !errCmp(err, tt.expected) { t.Errorf(cmp.Diff(err, tt.expected, cmpopts.EquateErrors())) } diff --git a/verifiers/internal/gha/verifier.go b/verifiers/internal/gha/verifier.go index b0fcfb9..cf39ed6 100644 --- a/verifiers/internal/gha/verifier.go +++ b/verifiers/internal/gha/verifier.go @@ -2,16 +2,20 @@ package gha import ( "context" + "crypto/x509" "encoding/base64" "fmt" "os" "strings" + "github.com/secure-systems-lab/go-securesystemslib/dsse" + "github.com/sigstore/cosign/cmd/cosign/cli/fulcio" "github.com/sigstore/cosign/cmd/cosign/cli/rekor" + "github.com/sigstore/cosign/pkg/cosign" - serrors "github.com/slsa-framework/slsa-verifier/errors" "github.com/slsa-framework/slsa-verifier/options" "github.com/slsa-framework/slsa-verifier/register" + "github.com/slsa-framework/slsa-verifier/verifiers/container" ) const VerifierName = "GHA" @@ -34,23 +38,12 @@ func (v *GHAVerifier) IsAuthoritativeFor(builderID string) bool { return strings.HasPrefix(builderID, "https://github.com/") } -// VerifyArtifact verifies provenance for an artifact. -func (v *GHAVerifier) VerifyArtifact(ctx context.Context, - provenance []byte, artifactHash string, +func verifyEnvAndCert(env *dsse.Envelope, + cert *x509.Certificate, provenanceOpts *options.ProvenanceOpts, builderOpts *options.BuilderOpts, + defaultBuilders map[string]bool, ) ([]byte, string, error) { - rClient, err := rekor.NewClient(defaultRekorAddr) - if err != nil { - return nil, "", err - } - - /* Verify signature on the intoto attestation. */ - env, cert, err := VerifyProvenanceSignature(ctx, rClient, provenance, artifactHash) - if err != nil { - return nil, "", err - } - /* Verify properties of the signing identity. */ // Get the workflow info given the certificate information. workflowInfo, err := GetWorkflowInfoFromCertificate(cert) @@ -60,7 +53,7 @@ func (v *GHAVerifier) VerifyArtifact(ctx context.Context, // Verify the workflow identity. builderID, err := VerifyWorkflowIdentity(workflowInfo, builderOpts, - provenanceOpts.ExpectedSourceURI) + provenanceOpts.ExpectedSourceURI, defaultBuilders) if err != nil { return nil, "", err } @@ -80,11 +73,76 @@ func (v *GHAVerifier) VerifyArtifact(ctx context.Context, return r, builderID, err } -// VerifyImage verifies provenance for an OCI image. -func (v *GHAVerifier) VerifyImage(ctx context.Context, +// VerifyArtifact verifies provenance for an artifact. +func (v *GHAVerifier) VerifyArtifact(ctx context.Context, provenance []byte, artifactHash string, provenanceOpts *options.ProvenanceOpts, builderOpts *options.BuilderOpts, ) ([]byte, string, error) { - return nil, "todo", serrors.ErrorNotSupported + rClient, err := rekor.NewClient(defaultRekorAddr) + if err != nil { + return nil, "", err + } + + /* Verify signature on the intoto attestation. */ + env, cert, err := VerifyProvenanceSignature(ctx, rClient, provenance, artifactHash) + if err != nil { + return nil, "", err + } + + return verifyEnvAndCert(env, cert, + provenanceOpts, builderOpts, + defaultArtifactTrustedReusableWorkflows) +} + +// VerifyImage verifies provenance for an OCI image. +func (v *GHAVerifier) VerifyImage(ctx context.Context, + artifactImage string, + provenanceOpts *options.ProvenanceOpts, + builderOpts *options.BuilderOpts, +) ([]byte, string, error) { + /* Retrieve any valid signed attestations that chain up to Fulcio root CA. */ + roots, err := fulcio.GetRoots() + if err != nil { + return nil, "", err + } + opts := &cosign.CheckOpts{ + RootCerts: roots, + } + + atts, _, err := container.RunCosignImageVerification(ctx, + artifactImage, opts) + if err != nil { + return nil, "", err + } + + /* Now verify properties of the attestations */ + var verifyErr error + var builderID string + var verifiedProvenance []byte + for _, att := range atts { + pyld, err := att.Payload() + if err != nil { + fmt.Fprintf(os.Stderr, "unexpected error getting payload from OCI registry %s", err) + continue + } + env, err := EnvelopeFromBytes(pyld) + if err != nil { + fmt.Fprintf(os.Stderr, "unexpected error parsing envelope from OCI registry %s", err) + continue + } + cert, err := att.Cert() + if err != nil { + fmt.Fprintf(os.Stderr, "unexpected error getting certificate from OCI registry %s", err) + continue + } + verifiedProvenance, builderID, verifyErr = verifyEnvAndCert(env, + cert, provenanceOpts, builderOpts, + defaultContainerTrustedReusableWorkflows) + if verifyErr == nil { + return verifiedProvenance, builderID, nil + } + } + + return nil, "", fmt.Errorf("no valid attestations found on OCI registry: %w", verifyErr) } diff --git a/verifiers/verifier.go b/verifiers/verifier.go index d3b7872..934318d 100644 --- a/verifiers/verifier.go +++ b/verifiers/verifier.go @@ -11,26 +11,35 @@ import ( "github.com/slsa-framework/slsa-verifier/verifiers/internal/gha" ) -func Verify(ctx context.Context, +func Verify(ctx context.Context, artifactImage string, provenance []byte, artifactHash string, provenanceOpts *options.ProvenanceOpts, builderOpts *options.BuilderOpts, ) ([]byte, string, error) { - // If user provids a builderID, find the right verifier - // based on its ID. + // By default, use the GHA builders + verifier := register.SLSAVerifiers[gha.VerifierName] + + // If user provids a builderID, find the right verifier based on its ID. if builderOpts.ExpectedID != nil && *builderOpts.ExpectedID != "" { + foundBuilder := false for _, v := range register.SLSAVerifiers { if v.IsAuthoritativeFor(*builderOpts.ExpectedID) { - return v.VerifyArtifact(ctx, provenance, artifactHash, - provenanceOpts, builderOpts) + foundBuilder = true + verifier = v + break } } - // No builder found. - return nil, "", fmt.Errorf("%w: %s", serrors.ErrorVerifierNotSupported, *builderOpts.ExpectedID) + if !foundBuilder { + // No builder found. + return nil, "", fmt.Errorf("%w: %s", serrors.ErrorVerifierNotSupported, *builderOpts.ExpectedID) + } } // By default, try the GHA builders. - return register.SLSAVerifiers[gha.VerifierName].VerifyArtifact(ctx, provenance, artifactHash, + if artifactImage != "" { + return verifier.VerifyImage(ctx, artifactImage, provenanceOpts, builderOpts) + } + return verifier.VerifyArtifact(ctx, provenance, artifactHash, provenanceOpts, builderOpts) }