feat: support oci image verification (#147)

* feat: support oci image verification

Signed-off-by: Asra Ali <asraa@google.com>

* add testing folder

Signed-off-by: Asra Ali <asraa@google.com>

* update name and make fix

Signed-off-by: Asra Ali <asraa@google.com>

* add tests

Signed-off-by: Asra Ali <asraa@google.com>

* Add initial testing

Signed-off-by: Asra Ali <asraa@google.com>

* updated comments

Signed-off-by: Asra Ali <asraa@google.com>

* update

Signed-off-by: Asra Ali <asraa@google.com>

* fix digest calculation

Signed-off-by: Asra Ali <asraa@google.com>

Signed-off-by: Asra Ali <asraa@google.com>
Co-authored-by: laurentsimon <64505099+laurentsimon@users.noreply.github.com>
This commit is contained in:
asraa
2022-08-17 15:59:01 -05:00
committed by GitHub
co-authored by laurentsimon
parent ccb0786c88
commit 7b4b9cde06
22 changed files with 549 additions and 149 deletions
+48 -20
View File
@@ -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)
}
+220 -72
View File
@@ -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))
}
}
})
@@ -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
}
]
}
@@ -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"]}}
@@ -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":{}}
@@ -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"
}
}
]
}
@@ -0,0 +1,3 @@
{
"imageLayoutVersion": "1.0.0"
}
+1 -1
View File
@@ -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)
+3
View File
@@ -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
+4
View File
@@ -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=
+1 -1
View File
@@ -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)
+15
View File
@@ -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
}
+18
View File
@@ -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)
}
+1 -1
View File
@@ -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) {
+17 -6
View File
@@ -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.
+78 -21
View File
@@ -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()))
}
+77 -19
View File
@@ -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)
}
+17 -8
View File
@@ -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)
}