From 239c4489cea389bf8becd72703306af63a15bc05 Mon Sep 17 00:00:00 2001 From: asraa Date: Thu, 9 Feb 2023 11:21:15 -0600 Subject: [PATCH] feat: add slsa v1?draft provenance experimental support (#470) * feat: add slsa v1?draft provenance support Signed-off-by: Asra Ali Signed-off-by: Asra Ali --- cli/slsa-verifier/main.go | 1 - cli/slsa-verifier/verify.go | 17 +- cli/slsa-verifier/verify/options.go | 7 + cli/slsa-verifier/verify/verify_artifact.go | 19 +- go.mod | 29 +-- go.sum | 41 ++++ options/options.go | 3 + verifiers/internal/gha/provenance.go | 183 +----------------- verifiers/internal/gha/provenance_test.go | 99 +++++++++- .../gha/slsaprovenance/slsaprovenance.go | 14 +- .../gha/slsaprovenance/v0.2/provenance.go | 167 +++++++++++++--- .../gha/slsaprovenance/v1.0/provenance.go | 77 ++++++++ .../dsse-no-subject-hash-v1.intoto.jsonl | 10 + .../testdata/dsse-no-subject-v1.intoto.jsonl | 10 + .../testdata/dsse-not-slsa-v1.intoto.jsonl | 10 + .../dsse-valid-multi-subjects-v1.intoto.jsonl | 10 + .../gha/testdata/dsse-valid-v1.intoto.jsonl | 10 + verifiers/internal/gha/verifier.go | 12 +- verifiers/utils/provenance.go | 21 ++ 19 files changed, 506 insertions(+), 234 deletions(-) create mode 100644 verifiers/internal/gha/slsaprovenance/v1.0/provenance.go create mode 100644 verifiers/internal/gha/testdata/dsse-no-subject-hash-v1.intoto.jsonl create mode 100644 verifiers/internal/gha/testdata/dsse-no-subject-v1.intoto.jsonl create mode 100644 verifiers/internal/gha/testdata/dsse-not-slsa-v1.intoto.jsonl create mode 100644 verifiers/internal/gha/testdata/dsse-valid-multi-subjects-v1.intoto.jsonl create mode 100644 verifiers/internal/gha/testdata/dsse-valid-v1.intoto.jsonl create mode 100644 verifiers/utils/provenance.go diff --git a/cli/slsa-verifier/main.go b/cli/slsa-verifier/main.go index 37e7496..bf066f4 100644 --- a/cli/slsa-verifier/main.go +++ b/cli/slsa-verifier/main.go @@ -16,7 +16,6 @@ func check(err error) { } } -//nolint:deadcode func ExperimentalEnabled() bool { return os.Getenv("SLSA_VERIFIER_EXPERIMENTAL") == "1" } diff --git a/cli/slsa-verifier/verify.go b/cli/slsa-verifier/verify.go index a4c753a..ac987b7 100644 --- a/cli/slsa-verifier/verify.go +++ b/cli/slsa-verifier/verify.go @@ -43,6 +43,7 @@ func verifyArtifactCmd() *cobra.Command { Run: func(cmd *cobra.Command, args []string) { v := verify.VerifyArtifactCommand{ ProvenancePath: o.ProvenancePath, + BundlePath: o.BundlePath, SourceURI: o.SourceURI, PrintProvenance: o.PrintProvenance, BuildWorkflowInputs: o.BuildWorkflowInputs.AsMap(), @@ -60,6 +61,21 @@ func verifyArtifactCmd() *cobra.Command { v.BuilderID = &o.BuilderID } + // In experimental mode, we allow either provenance or bundle path, but exactly + // one must be set. We already check to ensure that they are mutually exclusive. + if ExperimentalEnabled() { + if !(cmd.Flags().Changed("provenance-path") || + cmd.Flags().Changed("bundle-path")) { + fmt.Fprintf(os.Stderr, "%s\n%s", cmd.UsageString(), + "exactly one of --provenance-path or --bundle-path must be supplied") + os.Exit(1) + } + } else if !cmd.Flags().Changed("provenance-path") { + // --provenance-path must be set. + fmt.Fprintf(os.Stderr, "%s\n%s\n", cmd.UsageString(), "--provenance-path must be supplied") + os.Exit(1) + } + if _, err := v.Exec(cmd.Context(), args); err != nil { fmt.Fprintf(os.Stderr, "%s: %v\n", FAILURE, err) os.Exit(1) @@ -70,7 +86,6 @@ func verifyArtifactCmd() *cobra.Command { } o.AddFlags(cmd) - cmd.MarkFlagRequired("provenance-path") return cmd } diff --git a/cli/slsa-verifier/verify/options.go b/cli/slsa-verifier/verify/options.go index f860cb1..ea3016b 100644 --- a/cli/slsa-verifier/verify/options.go +++ b/cli/slsa-verifier/verify/options.go @@ -39,6 +39,7 @@ type VerifyOptions struct { BuilderID string /* Other */ ProvenancePath string + BundlePath string PrintProvenance bool } @@ -67,11 +68,17 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().StringVar(&o.ProvenancePath, "provenance-path", "", "path to a provenance file") + cmd.Flags().StringVar(&o.BundlePath, "bundle-path", "", + "path to a Sigstore provenance bundle file containing offline information.") + cmd.Flags().BoolVar(&o.PrintProvenance, "print-provenance", false, "[optional] print the verified provenance to stdout") cmd.MarkFlagRequired("source-uri") cmd.MarkFlagsMutuallyExclusive("source-versioned-tag", "source-tag") + + // Enforce exactly one of --provenance-path and --bundle-path. + cmd.MarkFlagsMutuallyExclusive("provenance-path", "bundle-path") } type workflowInputs struct { diff --git a/cli/slsa-verifier/verify/verify_artifact.go b/cli/slsa-verifier/verify/verify_artifact.go index 0a3e854..01090b7 100644 --- a/cli/slsa-verifier/verify/verify_artifact.go +++ b/cli/slsa-verifier/verify/verify_artifact.go @@ -30,6 +30,7 @@ import ( // Note: nil branch, tag, version-tag and builder-id means we ignore them during verification. type VerifyArtifactCommand struct { ProvenancePath string + BundlePath string BuilderID *string SourceURI string SourceBranch *string @@ -62,10 +63,20 @@ func (c *VerifyArtifactCommand) Exec(ctx context.Context, artifacts []string) (* ExpectedID: c.BuilderID, } - provenance, err := os.ReadFile(c.ProvenancePath) - if err != nil { - fmt.Fprintf(os.Stderr, "Verifying artifact %s: FAILED: %v\n\n", artifact, err) - return nil, err + var provenance []byte + if c.ProvenancePath != "" { + provenance, err = os.ReadFile(c.ProvenancePath) + if err != nil { + fmt.Fprintf(os.Stderr, "Verifying artifact %s: FAILED: %v\n\n", artifact, err) + return nil, err + } + } else { + bundle, err := os.ReadFile(c.BundlePath) + if err != nil { + fmt.Fprintf(os.Stderr, "Verifying artifact %s: FAILED: %v\n\n", artifact, err) + return nil, err + } + provenanceOpts.ProvenanceBundle = bundle } verifiedProvenance, outBuilderID, err := verifiers.VerifyArtifact(ctx, provenance, artifactHash, provenanceOpts, builderOpts) diff --git a/go.mod b/go.mod index fd0a826..61f6cdd 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/go-openapi/runtime v0.25.0 github.com/google/go-cmp v0.5.9 github.com/google/trillian v1.5.1-0.20220819043421-0a389c4bb8d9 // indirect - github.com/in-toto/in-toto-golang v0.6.0 + github.com/in-toto/in-toto-golang v0.6.1-0.20230207212643-96dcb8c596fb github.com/secure-systems-lab/go-securesystemslib v0.4.0 github.com/sigstore/rekor v1.0.1 github.com/sigstore/sigstore v1.5.1 @@ -23,21 +23,24 @@ require ( github.com/slsa-framework/slsa-github-generator v1.4.0 github.com/spf13/cobra v1.6.1 github.com/transparency-dev/merkle v0.0.1 - golang.org/x/mod v0.7.0 + golang.org/x/mod v0.8.0 sigs.k8s.io/release-utils v0.7.3 ) require ( filippo.io/edwards25519 v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect github.com/digitorus/pkcs7 v0.0.0-20221212123742-001c36b64ec3 // indirect github.com/digitorus/timestamp v0.0.0-20221019182153-ef3b63b79b31 // indirect github.com/sigstore/timestamp-authority v0.2.1 // indirect + github.com/spiffe/go-spiffe/v2 v2.1.2 // indirect + github.com/zeebo/errs v1.3.0 // indirect go.step.sm/crypto v0.23.1 // indirect ) require ( bitbucket.org/creachadair/shell v0.0.7 // indirect - cloud.google.com/go/compute v1.14.0 // indirect + cloud.google.com/go/compute v1.15.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect github.com/Azure/azure-sdk-for-go v67.3.0+incompatible // indirect @@ -82,12 +85,12 @@ require ( github.com/bgentry/speakeasy v0.1.0 // indirect github.com/blang/semver v3.5.1+incompatible // indirect github.com/cenkalti/backoff/v4 v4.1.3 // indirect - github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21 // indirect github.com/clbanning/mxj/v2 v2.5.6 // indirect - github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect - github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect + github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect + github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b // indirect github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect github.com/coreos/go-oidc/v3 v3.5.0 // indirect @@ -102,8 +105,8 @@ require ( github.com/docker/docker v20.10.21+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 // indirect - github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect + github.com/envoyproxy/go-control-plane v0.10.3 // indirect + github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fullstorydev/grpcurl v1.8.7 // indirect github.com/ghodss/yaml v1.0.0 // indirect @@ -144,7 +147,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jedisct1/go-minisign v0.0.0-20211028175153-1c139d1cc84b // indirect github.com/jhump/protoreflect v1.14.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -234,14 +237,14 @@ require ( golang.org/x/net v0.5.0 // indirect golang.org/x/oauth2 v0.4.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.4.0 // indirect + golang.org/x/sys v0.5.0 // indirect golang.org/x/term v0.4.0 // indirect golang.org/x/text v0.6.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.5.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa // indirect - google.golang.org/grpc v1.52.3 // indirect + google.golang.org/genproto v0.0.0-20230202175211-008b39050e57 // indirect + google.golang.org/grpc v1.53.0 // indirect google.golang.org/protobuf v1.28.1 gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 3bf4ce6..8de5226 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,8 @@ cloud.google.com/go/compute v1.13.0 h1:AYrLkB8NPdDRslNp4Jxmzrhdr03fUAIDbiGFjLWow cloud.google.com/go/compute v1.13.0/go.mod h1:5aPTS0cUNMIc1CE546K+Th6weJUNQErARyZtRXDJ8GE= cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= +cloud.google.com/go/compute v1.15.1 h1:7UGq3QknM33pw5xATlpzeoomNxsacIVvTqTTvbfajmE= +cloud.google.com/go/compute v1.15.1/go.mod h1:bjjoF/NtFUrkD/urWfdHaKuOPDR5nWIs63rR+SXhcpA= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.2 h1:aWKAjYaBaOSrpKl57+jnS/3fJRQnxL7TvR/u1VVbt6k= cloud.google.com/go/compute/metadata v0.2.2/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= @@ -156,6 +158,8 @@ github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0 github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= +github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= @@ -366,6 +370,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054 h1:uH66TXeswKn5PW5zdZ39xEwfS9an067BirqA+P4QaLI= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= @@ -373,6 +379,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21 h1:XlpL9EHrPOBJMLDDOf35/G4t5rGAFNNAZQ3cDcWavtc= github.com/chrismellard/docker-credential-acr-env v0.0.0-20220119192733-fe33c00cee21/go.mod h1:Zlre/PVxuSI9y6/UV4NwGixQ48RHQDSPiUkofr6rbMU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -387,6 +395,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= +github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -394,6 +404,9 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 h1:KwaoQzs/WeUxxJqiJsZ4euOly1Az/IgZXXSxlD/UBNk= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b h1:ACGZRIr7HsgBKHsueQ1yM4WaVaXh21ynwqsF8M8tXhA= +github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5 h1:xD/lrqdvwsc+O2bjSSi3YqY73Ke3LAiSCx49aCesA0E= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= @@ -492,10 +505,15 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1 h1:xvqufLtNVwAhN8NMyWklVgxnWohi+wtMGQMhtxexlm0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/go-control-plane v0.10.3 h1:xdCVXxEe0Y3FQith+0cj2irwZudqGYvecuLB1HtdexY= +github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.3.0-java/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2 h1:JiO+kJTpmYGjEodY7O1Zk8oZcNz1+f30UtwtXoFUPzE= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= +github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= +github.com/envoyproxy/protoc-gen-validate v0.9.1 h1:PS7VIOgmSVhWUEeZwTe7z7zouA22Cr590PzXKbZHOVY= +github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= @@ -899,9 +917,15 @@ github.com/in-toto/in-toto-golang v0.5.0 h1:hb8bgwr0M2hGdDsLjkJ3ZqJ8JFLL/tgYdAxF github.com/in-toto/in-toto-golang v0.5.0/go.mod h1:/Rq0IZHLV7Ku5gielPT4wPHJfH1GdHMCq8+WPxw8/BE= github.com/in-toto/in-toto-golang v0.6.0 h1:1s7cyzb5zGyzKPLgFsi4sC0o3EA24HLKlne8BrnOrSc= github.com/in-toto/in-toto-golang v0.6.0/go.mod h1:NaFLcsxtvZgbwQpyHwK8MlDXN9b+NuOMXqeIqxzfBoA= +github.com/in-toto/in-toto-golang v0.6.1-0.20230207163334-3493691c6d9c h1:d50sx5hkklYL5PkZrxtg4TAM1yfjykNw5p0AgDW2cq8= +github.com/in-toto/in-toto-golang v0.6.1-0.20230207163334-3493691c6d9c/go.mod h1:AoOsIJkpD8RHrZIAQxXqhGPm7M38IzT4JAdgPpf4OZc= +github.com/in-toto/in-toto-golang v0.6.1-0.20230207212643-96dcb8c596fb h1:lUoNEu/QPf8P52025Pk46e5godp1uRUzSJ3CQknuFdE= +github.com/in-toto/in-toto-golang v0.6.1-0.20230207212643-96dcb8c596fb/go.mod h1:AoOsIJkpD8RHrZIAQxXqhGPm7M38IzT4JAdgPpf4OZc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= @@ -990,6 +1014,7 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linkedin/goavro v2.1.0+incompatible/go.mod h1:bBCwI2eGYpUI/4820s67MElg9tdeLbINjLjiM2xZFYM= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= +github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -1235,6 +1260,7 @@ github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XF github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -1349,6 +1375,8 @@ github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/spiffe/go-spiffe/v2 v2.1.2 h1:nfNwopOP7q0qsWU6AUASqmbtYViwHA6vuHyAtqFJtNc= +github.com/spiffe/go-spiffe/v2 v2.1.2/go.mod h1:cbQmFrxsOpbm5tWURAYip9ZK0dOSFeoFG3/5Ub9Hvy0= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -1447,6 +1475,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zalando/go-keyring v0.1.0/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0= +github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= +github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= @@ -1548,6 +1578,7 @@ go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48 go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.16.0 h1:WHzDWdXUvbc5bG2ObdrGfaNpQz7ft7QN9HHmJlbiB1E= go.opentelemetry.io/proto/otlp v0.16.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.step.sm/crypto v0.23.1 h1:Yr9vlzjGqIKVi88KcpZtEcNTcpDkt1nVR7tumW4h+CU= @@ -1665,6 +1696,8 @@ golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0 h1:LapD9S96VoQRhi/GrNTqeBJFrUjs5UHCAtTlgwA5oZA= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1911,6 +1944,8 @@ golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -2031,6 +2066,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2190,6 +2226,7 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= @@ -2210,6 +2247,8 @@ google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqd google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa h1:qQPhfbPO23fwm/9lQr91L1u62Zo6cm+zI+slZT+uf+o= google.golang.org/genproto v0.0.0-20230125152338-dcaf20b6aeaa/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/genproto v0.0.0-20230202175211-008b39050e57 h1:vArvWooPH749rNHpBGgVl+U9B9dATjiEhJzcWGlovNs= +google.golang.org/genproto v0.0.0-20230202175211-008b39050e57/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -2258,6 +2297,8 @@ google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ= google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/options/options.go b/options/options.go index f5cd591..2b78f83 100644 --- a/options/options.go +++ b/options/options.go @@ -23,6 +23,9 @@ type ProvenanceOpts struct { // ExpectedWorkflowInputs is a map of key=value inputs. ExpectedWorkflowInputs map[string]string + + // Bundle containing information to verify the provenance offline. + ProvenanceBundle []byte } // BuildOpts are the options for checking the builder. diff --git a/verifiers/internal/gha/provenance.go b/verifiers/internal/gha/provenance.go index 8233fc7..7fc335b 100644 --- a/verifiers/internal/gha/provenance.go +++ b/verifiers/internal/gha/provenance.go @@ -18,9 +18,11 @@ import ( serrors "github.com/slsa-framework/slsa-verifier/v2/errors" "github.com/slsa-framework/slsa-verifier/v2/options" "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance" + "github.com/slsa-framework/slsa-verifier/v2/verifiers/utils" // Load provenance types. _ "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v0.2" + _ "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v1.0" ) // SignedAttestation contains a signed DSSE envelope @@ -240,25 +242,14 @@ func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceO } func VerifyWorkflowInputs(prov slsaprovenance.Provenance, inputs map[string]string) error { - // Verify it's a workflow_dispatch trigger. - triggerName, err := prov.GetStringFromEnvironment("github_event_name") - if err != nil { - return err - } - if triggerName != "workflow_dispatch" { - return fmt.Errorf("%w: expected 'workflow_dispatch' trigger, got %s", - serrors.ErrorMismatchWorkflowInputs, triggerName) - } - - // Assume no nested level. - pyldInputs, err := prov.GetInputs() + pyldInputs, err := prov.GetWorkflowInputs() if err != nil { return err } // Verify all inputs. for k, v := range inputs { - value, err := getAsString(pyldInputs, k) + value, err := utils.GetAsString(pyldInputs, k) if err != nil { return fmt.Errorf("%w: cannot retrieve value of '%s'", serrors.ErrorMismatchWorkflowInputs, k) } @@ -273,7 +264,7 @@ func VerifyWorkflowInputs(prov slsaprovenance.Provenance, inputs map[string]stri } func VerifyBranch(prov slsaprovenance.Provenance, expectedBranch string) error { - branch, err := getBranch(prov) + branch, err := prov.GetBranch() if err != nil { return err } @@ -287,7 +278,7 @@ func VerifyBranch(prov slsaprovenance.Provenance, expectedBranch string) error { } func VerifyTag(prov slsaprovenance.Provenance, expectedTag string) error { - tag, err := getTag(prov) + tag, err := prov.GetTag() if err != nil { return err } @@ -310,7 +301,7 @@ func VerifyVersionedTag(prov slsaprovenance.Provenance, expectedTag string) erro // Note: prerelease is validated as part of patch validation // and must be equal. Build is discarded as per https://semver.org/: // "Build metadata MUST be ignored when determining version precedence", - tag, err := getTag(prov) + tag, err := prov.GetTag() if err != nil { return err } @@ -379,166 +370,6 @@ func extractFromVersion(v string, i int) (string, error) { return parts[i], nil } -func getAsAny(payload map[string]any, field string) (any, error) { - value, ok := payload[field] - if !ok { - return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, - fmt.Sprintf("payload type for %s", field)) - } - return value, nil -} - -func getAsString(pyld map[string]interface{}, field string) (string, error) { - value, ok := pyld[field] - if !ok { - return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, - fmt.Sprintf("environment type for %s", field)) - } - - i, ok := value.(string) - if !ok { - return "", fmt.Errorf("%w: %s '%s'", serrors.ErrorInvalidDssePayload, "environment type string", field) - } - return i, nil -} - -func getEventPayload(prov slsaprovenance.Provenance) (map[string]interface{}, error) { - eventPayload, err := prov.GetAnyFromEnvironment("github_event_payload") - if err != nil { - return nil, err - } - - payload, ok := eventPayload.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type payload") - } - - return payload, nil -} - -func getBaseRef(prov slsaprovenance.Provenance) (string, error) { - baseRef, err := prov.GetStringFromEnvironment("github_base_ref") - if err != nil { - return "", err - } - - // This `base_ref` seems to always be "". - if baseRef != "" { - return baseRef, nil - } - - // Look at the event payload instead. - // We don't do that for all triggers because the payload - // is event-specific; and only the `push` event seems to have a `base_ref`. - eventName, err := prov.GetStringFromEnvironment("github_event_name") - if err != nil { - return "", err - } - - if eventName != "push" { - return "", nil - } - - payload, err := getEventPayload(prov) - if err != nil { - return "", err - } - - value, err := getAsAny(payload, "base_ref") - if err != nil { - return "", err - } - - // The `base_ref` field may be nil if the build was from - // a specific commit rather than a branch. - v, ok := value.(string) - if !ok { - return "", nil - } - return v, nil -} - -func getTargetCommittish(prov slsaprovenance.Provenance) (string, error) { - eventName, err := prov.GetStringFromEnvironment("github_event_name") - if err != nil { - return "", err - } - - if eventName != "release" { - return "", nil - } - - payload, err := getEventPayload(prov) - if err != nil { - return "", err - } - - // For a release event, we look for release.target_commitish. - releasePayload, ok := payload["release"] - if !ok { - return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "release absent from payload") - } - - release, ok := releasePayload.(map[string]interface{}) - if !ok { - return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type releasePayload") - } - - branch, err := getAsString(release, "target_commitish") - if err != nil { - return "", fmt.Errorf("%w: %s", err, "target_commitish not present") - } - - return "refs/heads/" + branch, nil -} - -func getBranchForTag(prov slsaprovenance.Provenance) (string, error) { - // First try the base_ref. - branch, err := getBaseRef(prov) - if branch != "" || err != nil { - return branch, err - } - - // Second try the target comittish. - return getTargetCommittish(prov) -} - -// Get tag from the provenance invocation parameters. -func getTag(prov slsaprovenance.Provenance) (string, error) { - refType, err := prov.GetStringFromEnvironment("github_ref_type") - if err != nil { - return "", err - } - - switch refType { - case "branch": - return "", nil - case "tag": - return prov.GetStringFromEnvironment("github_ref") - default: - return "", fmt.Errorf("%w: %s %s", serrors.ErrorInvalidDssePayload, - "unknown ref type", refType) - } -} - -// Get branch from the provenance invocation parameters. -func getBranch(prov slsaprovenance.Provenance) (string, error) { - refType, err := prov.GetStringFromEnvironment("github_ref_type") - if err != nil { - return "", err - } - - switch refType { - case "branch": - return prov.GetStringFromEnvironment("github_ref") - case "tag": - return getBranchForTag(prov) - default: - return "", fmt.Errorf("%w: %s %s", serrors.ErrorInvalidDssePayload, - "unknown ref type", refType) - } -} - // hasCertInEnvelope checks if a valid x509 certificate is present in the // envelope. func hasCertInEnvelope(provenance []byte) bool { diff --git a/verifiers/internal/gha/provenance_test.go b/verifiers/internal/gha/provenance_test.go index e785529..ac411f3 100644 --- a/verifiers/internal/gha/provenance_test.go +++ b/verifiers/internal/gha/provenance_test.go @@ -9,10 +9,12 @@ import ( intoto "github.com/in-toto/in-toto-golang/in_toto" slsacommon "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common" slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" + slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1.0" serrors "github.com/slsa-framework/slsa-verifier/v2/errors" "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance" v02 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v0.2" + "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v1.0" ) func provenanceFromBytes(payload []byte) (slsaprovenance.Provenance, error) { @@ -79,6 +81,58 @@ func Test_VerifySha256Subject(t *testing.T) { artifactHash: "04e7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", expected: serrors.ErrorMismatchHash, }, + { + name: "slsa 1.0 invalid dsse: not SLSA predicate", + path: "./testdata/dsse-not-slsa-v1.intoto.jsonl", + artifactHash: "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", + expected: serrors.ErrorInvalidDssePayload, + }, + + { + name: "invalid dsse: nil subject", + path: "./testdata/dsse-no-subject-v1.intoto.jsonl", + artifactHash: "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", + expected: serrors.ErrorInvalidDssePayload, + }, + + { + name: "invalid dsse: no sha256 subject digest", + path: "./testdata/dsse-no-subject-hash-v1.intoto.jsonl", + artifactHash: "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", + expected: serrors.ErrorInvalidDssePayload, + }, + { + name: "mismatched artifact hash with env", + path: "./testdata/dsse-valid-v1.intoto.jsonl", + artifactHash: "1ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", + expected: serrors.ErrorMismatchHash, + }, + + { + name: "valid entry", + path: "./testdata/dsse-valid-v1.intoto.jsonl", + artifactHash: "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", + expected: nil, + }, + + { + name: "valid entry multiple subjects last entry", + path: "./testdata/dsse-valid-multi-subjects-v1.intoto.jsonl", + artifactHash: "03e7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", + expected: nil, + }, + { + name: "valid multiple subjects second entry", + path: "./testdata/dsse-valid-multi-subjects-v1.intoto.jsonl", + artifactHash: "02e7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", + expected: nil, + }, + { + name: "multiple subjects invalid hash", + path: "./testdata/dsse-valid-multi-subjects-v1.intoto.jsonl", + artifactHash: "04e7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e", + expected: serrors.ErrorMismatchHash, + }, } for _, tt := range tests { tt := tt // Re-initializing variable so it is not changed while executing the closure below @@ -109,6 +163,8 @@ func Test_verifySourceURI(t *testing.T) { prov *intoto.ProvenanceStatement sourceURI string expected error + // v1 provenance does not include materials + skipv1 bool }{ { name: "source has no @", @@ -137,6 +193,7 @@ func Test_verifySourceURI(t *testing.T) { }, sourceURI: "git+https://github.com/some/repo", expected: serrors.ErrorInvalidDssePayload, + skipv1: true, }, { name: "empty configSource", @@ -284,6 +341,7 @@ func Test_verifySourceURI(t *testing.T) { }, }, sourceURI: "git+https://github.com/some/repo", + skipv1: true, expected: serrors.ErrorInvalidDssePayload, }, { @@ -349,11 +407,32 @@ func Test_verifySourceURI(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - prov := &v02.ProvenanceV02{ + prov02 := &v02.ProvenanceV02{ ProvenanceStatement: tt.prov, } - err := verifySourceURI(prov, tt.sourceURI) + err := verifySourceURI(prov02, tt.sourceURI) + if !errCmp(err, tt.expected) { + t.Errorf(cmp.Diff(err, tt.expected)) + } + + if tt.skipv1 { + return + } + + // Update to v1 SLSA provenance. + prov1 := &v1.ProvenanceV1{ + Predicate: slsa1.ProvenancePredicate{ + BuildDefinition: slsa1.ProvenanceBuildDefinition{ + ExternalParameters: map[string]interface{}{ + "source": slsa1.ArtifactReference{ + URI: tt.prov.Predicate.Invocation.ConfigSource.URI, + }, + }, + }, + }, + } + err = verifySourceURI(prov1, tt.sourceURI) if !errCmp(err, tt.expected) { t.Errorf(cmp.Diff(err, tt.expected)) } @@ -448,6 +527,22 @@ func Test_verifyBuilderIDExactMatch(t *testing.T) { if !errCmp(err, tt.expected) { t.Errorf(cmp.Diff(err, tt.expected)) } + + // Update to v1 SLSA provenance. + prov1 := &v1.ProvenanceV1{ + Predicate: slsa1.ProvenancePredicate{ + RunDetails: slsa1.ProvenanaceRunDetails{ + Builder: slsa1.Builder{ + ID: tt.prov.Predicate.Builder.ID, + }, + }, + }, + } + + err = verifyBuilderIDExactMatch(prov1, tt.id) + if !errCmp(err, tt.expected) { + t.Errorf(cmp.Diff(err, tt.expected)) + } }) } } diff --git a/verifiers/internal/gha/slsaprovenance/slsaprovenance.go b/verifiers/internal/gha/slsaprovenance/slsaprovenance.go index 953a13e..5466ee2 100644 --- a/verifiers/internal/gha/slsaprovenance/slsaprovenance.go +++ b/verifiers/internal/gha/slsaprovenance/slsaprovenance.go @@ -24,17 +24,15 @@ type Provenance interface { // Subject is the list of intoto subjects in the provenance. Subjects() ([]intoto.Subject, error) - // GetStringFromEnvironment retrieves a string parameter from the environment - // attested to in the provenance. - GetStringFromEnvironment(name string) (string, error) + // GetBranch retrieves the branch name of the source from the provenance. + GetBranch() (string, error) - // GetAnyFromEnvironment retrieves an object parameter from the environment - // attested to in the provenance. - GetAnyFromEnvironment(name string) (interface{}, error) + // GetTag retrieves the tag of the source from the provenance. + GetTag() (string, error) - // GetInputs retrieves the inputs from the provenance. Only succeeds for event + // GetWorkflowInputs retrieves the inputs from the provenance. Only succeeds for event // relevant event types (workflow_inputs). - GetInputs() (map[string]interface{}, error) + GetWorkflowInputs() (map[string]interface{}, error) } // ProvenanceMap stores the different provenance version types. diff --git a/verifiers/internal/gha/slsaprovenance/v0.2/provenance.go b/verifiers/internal/gha/slsaprovenance/v0.2/provenance.go index ced6e32..223d52d 100644 --- a/verifiers/internal/gha/slsaprovenance/v0.2/provenance.go +++ b/verifiers/internal/gha/slsaprovenance/v0.2/provenance.go @@ -6,9 +6,10 @@ import ( intoto "github.com/in-toto/in-toto-golang/in_toto" serrors "github.com/slsa-framework/slsa-verifier/v2/errors" "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance" + "github.com/slsa-framework/slsa-verifier/v2/verifiers/utils" ) -// TODO(asraa): Use a static mapping. +// TODO(https://github.com/slsa-framework/slsa-verifier/issues/473): Use a static mapping. // //nolint:gochecknoinits func init() { @@ -49,49 +50,74 @@ func (prov *ProvenanceV02) Subjects() ([]intoto.Subject, error) { return subj, nil } -func (prov *ProvenanceV02) GetStringFromEnvironment(name string) (string, error) { +func (prov *ProvenanceV02) GetBranch() (string, error) { + // GetBranch gets the branch from the invocation parameters. environment, ok := prov.Predicate.Invocation.Environment.(map[string]interface{}) if !ok { return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type") } - val, ok := environment[name] - if !ok { - return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, - fmt.Sprintf("environment type for %s", name)) + + refType, err := utils.GetAsString(environment, "github_ref_type") + if err != nil { + return "", err } - i, ok := val.(string) - if !ok { - return "", fmt.Errorf("%w: %s '%s'", serrors.ErrorInvalidDssePayload, "environment type string", name) + + switch refType { + case "branch": + return utils.GetAsString(environment, "github_ref") + case "tag": + return getBranchForTag(prov) + default: + return "", fmt.Errorf("%w: %s %s", serrors.ErrorInvalidDssePayload, + "unknown ref type", refType) } - return i, nil } -func (prov *ProvenanceV02) GetAnyFromEnvironment(name string) (interface{}, error) { +func (prov *ProvenanceV02) GetTag() (string, error) { environment, ok := prov.Predicate.Invocation.Environment.(map[string]interface{}) if !ok { return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type") } - val, ok := environment[name] - if !ok { - return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, - fmt.Sprintf("environment type for %s", name)) + + refType, err := utils.GetAsString(environment, "github_ref_type") + if err != nil { + return "", err + } + + switch refType { + case "branch": + return "", nil + case "tag": + return utils.GetAsString(environment, "github_ref") + default: + return "", fmt.Errorf("%w: %s %s", serrors.ErrorInvalidDssePayload, + "unknown ref type", refType) } - return val, nil } -func (prov *ProvenanceV02) GetInputs() (map[string]interface{}, error) { - eventPayload, err := prov.GetAnyFromEnvironment("github_event_payload") +func (prov *ProvenanceV02) GetWorkflowInputs() (map[string]interface{}, error) { + // Verify it's a workflow_dispatch trigger. + environment, ok := prov.Predicate.Invocation.Environment.(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type") + } + + triggerName, err := utils.GetAsString(environment, "github_event_name") + if err != nil { + return nil, err + } + if triggerName != "workflow_dispatch" { + return nil, fmt.Errorf("%w: expected 'workflow_dispatch' trigger, got %s", + serrors.ErrorMismatchWorkflowInputs, triggerName) + } + + payload, err := getEventPayload(environment) if err != nil { return nil, err } - payload, ok := eventPayload.(map[string]interface{}) - if !ok { - return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type payload") - } - - payloadInputs, ok := payload["inputs"] - if !ok { + payloadInputs, err := getAsAny(payload, "inputs") + if err != nil { return nil, fmt.Errorf("%w: error retrieving 'inputs': %v", serrors.ErrorInvalidDssePayload, err) } @@ -101,3 +127,94 @@ func (prov *ProvenanceV02) GetInputs() (map[string]interface{}, error) { } return pyldInputs, nil } + +func getBranchForTag(prov *ProvenanceV02) (string, error) { + // First try the base_ref. + environment, ok := prov.Predicate.Invocation.Environment.(map[string]interface{}) + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type") + } + + baseRef, err := utils.GetAsString(environment, "github_base_ref") + if err != nil { + return "", err + } + + // This `base_ref` seems to always be "". + if baseRef != "" { + return baseRef, nil + } + + // Look at the event payload instead. + eventName, err := utils.GetAsString(environment, "github_event_name") + if err != nil { + return "", err + } + + payload, err := getEventPayload(environment) + if err != nil { + return "", err + } + + // We don't do that for all triggers because the payload + // is event-specific. Only `push` events seem to have a `base_ref`, and + // `release` events specify a branch in `target_commitish`. + switch eventName { + case "push": + value, err := getAsAny(payload, "base_ref") + if err != nil { + return "", err + } + + // The `base_ref` field may be nil if the build was from + // a specific commit rather than a branch. + v, ok := value.(string) + if !ok { + return "", nil + } + return v, nil + case "release": + // For a release event, we look for release.target_commitish. + releasePayload, ok := payload["release"] + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "release absent from payload") + } + + release, ok := releasePayload.(map[string]interface{}) + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type releasePayload") + } + + branch, err := utils.GetAsString(release, "target_commitish") + if err != nil { + return "", fmt.Errorf("%w: %s", err, "target_commitish not present") + } + + return "refs/heads/" + branch, nil + default: + return "", nil + } +} + +func getAsAny(environment map[string]any, field string) (any, error) { + value, ok := environment[field] + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, + fmt.Sprintf("environment type for %s", field)) + } + return value, nil +} + +func getEventPayload(environment map[string]any) (map[string]any, error) { + eventPayload, ok := environment["github_event_payload"] + if !ok { + return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type event payload") + } + + payload, ok := eventPayload.(map[string]any) + if !ok { + return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type payload") + } + + return payload, nil +} diff --git a/verifiers/internal/gha/slsaprovenance/v1.0/provenance.go b/verifiers/internal/gha/slsaprovenance/v1.0/provenance.go new file mode 100644 index 0000000..60e3231 --- /dev/null +++ b/verifiers/internal/gha/slsaprovenance/v1.0/provenance.go @@ -0,0 +1,77 @@ +package v1 + +import ( + "errors" + "fmt" + + intoto "github.com/in-toto/in-toto-golang/in_toto" + slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1.0" + serrors "github.com/slsa-framework/slsa-verifier/v2/errors" + "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance" +) + +// TODO(https://github.com/slsa-framework/slsa-verifier/issues/473): Use a static mapping. +// +//nolint:gochecknoinits +func init() { + slsaprovenance.ProvenanceMap.Store( + "https://slsa.dev/provenance/v1.0?draft", + New) +} + +type ProvenanceV1 struct { + intoto.StatementHeader + Predicate slsa1.ProvenancePredicate `json:"predicate"` +} + +// This returns a new, empty instance of the v0.2 provenance. +func New() slsaprovenance.Provenance { + return &ProvenanceV1{} +} + +func (prov *ProvenanceV1) BuilderID() (string, error) { + return prov.Predicate.RunDetails.Builder.ID, nil +} + +func (prov *ProvenanceV1) SourceURI() (string, error) { + extParams, ok := prov.Predicate.BuildDefinition.ExternalParameters.(map[string]interface{}) + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "external parameters type") + } + source, ok := extParams["source"] + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "external parameters source") + } + sourceRef, ok := source.(slsa1.ArtifactReference) + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "external parameters source type") + } + return sourceRef.URI, nil +} + +func (prov *ProvenanceV1) ConfigURI() (string, error) { + // The source and config are the same for GHA provenance. + return prov.SourceURI() +} + +func (prov *ProvenanceV1) Subjects() ([]intoto.Subject, error) { + subj := prov.Subject + if len(subj) == 0 { + return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "no subjects") + } + return subj, nil +} + +func (prov *ProvenanceV1) GetBranch() (string, error) { + // TODO(https://github.com/slsa-framework/slsa-verifier/issues/472): Add GetBranch() support. + return "", errors.New("unimplemented") +} + +func (prov *ProvenanceV1) GetTag() (string, error) { + // TODO(https://github.com/slsa-framework/slsa-verifier/issues/472): Add GetTag() support. + return "", errors.New("unimplemented") +} + +func (prov *ProvenanceV1) GetWorkflowInputs() (map[string]interface{}, error) { + return nil, errors.New("unimplemented") +} diff --git a/verifiers/internal/gha/testdata/dsse-no-subject-hash-v1.intoto.jsonl b/verifiers/internal/gha/testdata/dsse-no-subject-hash-v1.intoto.jsonl new file mode 100644 index 0000000..4c04f07 --- /dev/null +++ b/verifiers/internal/gha/testdata/dsse-no-subject-hash-v1.intoto.jsonl @@ -0,0 +1,10 @@ +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGExIjogIjQ1MDYyOTBlMmU4ZmViMWYzNGIyN2EwNDRmN2NjODYzYzgzMGVmNmIiCiAgICAgIH0sCiAgICAgICJuYW1lIjogImJpbmFyeS1saW51eC1hbWQ2NCIKICAgIH0KICBdLAogICJwcmVkaWNhdGVUeXBlIjogImh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MS4wP2RyYWZ0IiwKICAicHJlZGljYXRlIjogewogICAgImJ1aWxkRGVmaW5pdGlvbiI6IHsKICAgICAgImJ1aWxkTm90U0xTQSI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1YkFjdGlvbnNXb3JrZmxvd0B2MSIsCiAgICAgICJleHRlcm5hbFBhcmFtYXRlcnMiOiB7CiAgICAgICAgInNvdXJjZSI6IHsKICAgICAgICAgICJ1cmkiOiAiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UiLAogICAgICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAgICAgInNoYTEiOiAiNGU2YzVmNmQwYjRhMTI2ZmEyMzczZDdlNTdiN2EwYWYwNTEwODc5MSIKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAicnVuRGV0YWlscyI6IHsKICAgICAgImJ1aWxkZXIiOiB7CiAgICAgICAgImlkIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViSG9zdGVkQWN0aW9uc0B2MSIKICAgICAgfSwKICAgICAgIm1ldGFkYXRhIjogewogICAgICAgICJpbnZvY2F0aW9uSWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9hY3Rpb25zL3J1bnMvNDEzNTQ2Mzc0MS9hdHRlbXB0cy8xIgogICAgICB9CiAgICB9CiAgfQp9Cg==", + "signatures": [ + { + "keyid": "", + "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" + } + ] +} diff --git a/verifiers/internal/gha/testdata/dsse-no-subject-v1.intoto.jsonl b/verifiers/internal/gha/testdata/dsse-no-subject-v1.intoto.jsonl new file mode 100644 index 0000000..0ba57b8 --- /dev/null +++ b/verifiers/internal/gha/testdata/dsse-no-subject-v1.intoto.jsonl @@ -0,0 +1,10 @@ +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogW10sCiAgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxLjA/ZHJhZnQiLAogICJwcmVkaWNhdGUiOiB7CiAgICAiYnVpbGREZWZpbml0aW9uIjogewogICAgICAiYnVpbGROb3RTTFNBIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViQWN0aW9uc1dvcmtmbG93QHYxIiwKICAgICAgImV4dGVybmFsUGFyYW1hdGVycyI6IHsKICAgICAgICAic291cmNlIjogewogICAgICAgICAgInVyaSI6ICJnaXQraHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZSIsCiAgICAgICAgICAiZGlnZXN0IjogewogICAgICAgICAgICAic2hhMSI6ICI0ZTZjNWY2ZDBiNGExMjZmYTIzNzNkN2U1N2I3YTBhZjA1MTA4NzkxIgogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJydW5EZXRhaWxzIjogewogICAgICAiYnVpbGRlciI6IHsKICAgICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL0F0dGVzdGF0aW9ucy9HaXRIdWJIb3N0ZWRBY3Rpb25zQHYxIgogICAgICB9LAogICAgICAibWV0YWRhdGEiOiB7CiAgICAgICAgImludm9jYXRpb25JZCI6ICJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2FjdGlvbnMvcnVucy80MTM1NDYzNzQxL2F0dGVtcHRzLzEiCiAgICAgIH0KICAgIH0KICB9Cn0K", + "signatures": [ + { + "keyid": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGUiOiB7CiAgICAiYnVpbGRUeXBlIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViQWN0aW9uc1dvcmtmbG93QHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL0F0dGVzdGF0aW9ucy9HaXRIdWJIb3N0ZWRBY3Rpb25zQHYxIgogICAgfSwKICAgICJpbnZvY2F0aW9uIjogewogICAgICAiY29uZmlnU291cmNlIjogewogICAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgICAiU0hBMSI6ICI0NTA2MjkwZTJlOGZlYjFmMzRiMjdhMDQ0ZjdjYzg2M2M4MzBlZjZiIgogICAgICAgIH0sCiAgICAgICAgImVudHJ5UG9pbnQiOiAiVGVzdCBTTFNBIiwKICAgICAgICAidXJpIjogImdpdCthc3JhYS9zbHNhLW9uLWdpdGh1Yi10ZXN0LmdpdCIKICAgICAgfSwKICAgICAgImVudmlyb25tZW50IjogewogICAgICAgICJhcmNoIjogImFtZDY0IiwKICAgICAgICAiZW52IjogewogICAgICAgICAgIkdJVEhVQl9FVkVOVF9OQU1FIjogIndvcmtmbG93X2Rpc3BhdGNoIiwKICAgICAgICAgICJHSVRIVUJfUlVOX0lEIjogIjE4OTM3OTkyMjAiLAogICAgICAgICAgIkdJVEhVQl9SVU5fTlVNQkVSIjogIjc2IgogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJtYXRlcmlhbHMiOiBbCiAgICAgIHsKICAgICAgICAiZGlnZXN0IjogewogICAgICAgICAgIlNIQTEiOiAiNDUwNjI5MGUyZThmZWIxZjM0YjI3YTA0NGY3Y2M4NjNjODMwZWY2YiIKICAgICAgICB9LAogICAgICAgICJ1cmkiOiAiZ2l0K2FzcmFhL3Nsc2Etb24tZ2l0aHViLXRlc3QuZ2l0IgogICAgICB9CiAgICBdCiAgfSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCn0K", + "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" + } + ] +} diff --git a/verifiers/internal/gha/testdata/dsse-not-slsa-v1.intoto.jsonl b/verifiers/internal/gha/testdata/dsse-not-slsa-v1.intoto.jsonl new file mode 100644 index 0000000..4c04f07 --- /dev/null +++ b/verifiers/internal/gha/testdata/dsse-not-slsa-v1.intoto.jsonl @@ -0,0 +1,10 @@ +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGExIjogIjQ1MDYyOTBlMmU4ZmViMWYzNGIyN2EwNDRmN2NjODYzYzgzMGVmNmIiCiAgICAgIH0sCiAgICAgICJuYW1lIjogImJpbmFyeS1saW51eC1hbWQ2NCIKICAgIH0KICBdLAogICJwcmVkaWNhdGVUeXBlIjogImh0dHBzOi8vc2xzYS5kZXYvcHJvdmVuYW5jZS92MS4wP2RyYWZ0IiwKICAicHJlZGljYXRlIjogewogICAgImJ1aWxkRGVmaW5pdGlvbiI6IHsKICAgICAgImJ1aWxkTm90U0xTQSI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1YkFjdGlvbnNXb3JrZmxvd0B2MSIsCiAgICAgICJleHRlcm5hbFBhcmFtYXRlcnMiOiB7CiAgICAgICAgInNvdXJjZSI6IHsKICAgICAgICAgICJ1cmkiOiAiZ2l0K2h0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UiLAogICAgICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAgICAgInNoYTEiOiAiNGU2YzVmNmQwYjRhMTI2ZmEyMzczZDdlNTdiN2EwYWYwNTEwODc5MSIKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgIH0KICAgIH0sCiAgICAicnVuRGV0YWlscyI6IHsKICAgICAgImJ1aWxkZXIiOiB7CiAgICAgICAgImlkIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViSG9zdGVkQWN0aW9uc0B2MSIKICAgICAgfSwKICAgICAgIm1ldGFkYXRhIjogewogICAgICAgICJpbnZvY2F0aW9uSWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZS9hY3Rpb25zL3J1bnMvNDEzNTQ2Mzc0MS9hdHRlbXB0cy8xIgogICAgICB9CiAgICB9CiAgfQp9Cg==", + "signatures": [ + { + "keyid": "", + "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" + } + ] +} diff --git a/verifiers/internal/gha/testdata/dsse-valid-multi-subjects-v1.intoto.jsonl b/verifiers/internal/gha/testdata/dsse-valid-multi-subjects-v1.intoto.jsonl new file mode 100644 index 0000000..ab4e535 --- /dev/null +++ b/verifiers/internal/gha/testdata/dsse-valid-multi-subjects-v1.intoto.jsonl @@ -0,0 +1,10 @@ +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiMDFlN2U0ZmE3MTY4NjUzODQ0MDAxMmVlMzZhMjYzNGRiYWExOWRmMmRkMTZhNDY2ZjUyNDExZmIzNDhiYmM0ZSIKICAgICAgfSwKICAgICAgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0LTEiCiAgICB9LAogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiMDJlN2U0ZmE3MTY4NjUzODQ0MDAxMmVlMzZhMjYzNGRiYWExOWRmMmRkMTZhNDY2ZjUyNDExZmIzNDhiYmM0ZSIKICAgICAgfSwKICAgICAgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0LTIiCiAgICB9LAogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiMDNlN2U0ZmE3MTY4NjUzODQ0MDAxMmVlMzZhMjYzNGRiYWExOWRmMmRkMTZhNDY2ZjUyNDExZmIzNDhiYmM0ZSIKICAgICAgfSwKICAgICAgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0LTMiCiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEuMD9kcmFmdCIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZERlZmluaXRpb24iOiB7CiAgICAgICJidWlsZE5vdFNMU0EiOiAiaHR0cHM6Ly9naXRodWIuY29tL0F0dGVzdGF0aW9ucy9HaXRIdWJBY3Rpb25zV29ya2Zsb3dAdjEiLAogICAgICAiZXh0ZXJuYWxQYXJhbWF0ZXJzIjogewogICAgICAgICJzb3VyY2UiOiB7CiAgICAgICAgICAidXJpIjogImdpdCtodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlIiwKICAgICAgICAgICJkaWdlc3QiOiB7CiAgICAgICAgICAgICJzaGExIjogIjRlNmM1ZjZkMGI0YTEyNmZhMjM3M2Q3ZTU3YjdhMGFmMDUxMDg3OTEiCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CiAgICB9LAogICAgInJ1bkRldGFpbHMiOiB7CiAgICAgICJidWlsZGVyIjogewogICAgICAgICJpZCI6ICJodHRwczovL2dpdGh1Yi5jb20vQXR0ZXN0YXRpb25zL0dpdEh1Ykhvc3RlZEFjdGlvbnNAdjEiCiAgICAgIH0sCiAgICAgICJtZXRhZGF0YSI6IHsKICAgICAgICAiaW52b2NhdGlvbklkIjogImh0dHBzOi8vZ2l0aHViLmNvbS9zbHNhLWZyYW1ld29yay9leGFtcGxlLXBhY2thZ2UvYWN0aW9ucy9ydW5zLzQxMzU0NjM3NDEvYXR0ZW1wdHMvMSIKICAgICAgfQogICAgfQogIH0KfQo=", + "signatures": [ + { + "keyid": "", + "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" + } + ] +} diff --git a/verifiers/internal/gha/testdata/dsse-valid-v1.intoto.jsonl b/verifiers/internal/gha/testdata/dsse-valid-v1.intoto.jsonl new file mode 100644 index 0000000..d339e61 --- /dev/null +++ b/verifiers/internal/gha/testdata/dsse-valid-v1.intoto.jsonl @@ -0,0 +1,10 @@ +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAiZGlnZXN0IjogewogICAgICAgICJzaGEyNTYiOiAiMGFlN2U0ZmE3MTY4NjUzODQ0MDAxMmVlMzZhMjYzNGRiYWExOWRmMmRkMTZhNDY2ZjUyNDExZmIzNDhiYmM0ZSIKICAgICAgfSwKICAgICAgIm5hbWUiOiAiYmluYXJ5LWxpbnV4LWFtZDY0IgogICAgfQogIF0sCiAgInByZWRpY2F0ZVR5cGUiOiAiaHR0cHM6Ly9zbHNhLmRldi9wcm92ZW5hbmNlL3YxLjA/ZHJhZnQiLAogICJwcmVkaWNhdGUiOiB7CiAgICAiYnVpbGREZWZpbml0aW9uIjogewogICAgICAiYnVpbGROb3RTTFNBIjogImh0dHBzOi8vZ2l0aHViLmNvbS9BdHRlc3RhdGlvbnMvR2l0SHViQWN0aW9uc1dvcmtmbG93QHYxIiwKICAgICAgImV4dGVybmFsUGFyYW1hdGVycyI6IHsKICAgICAgICAic291cmNlIjogewogICAgICAgICAgInVyaSI6ICJnaXQraHR0cHM6Ly9naXRodWIuY29tL3Nsc2EtZnJhbWV3b3JrL2V4YW1wbGUtcGFja2FnZSIsCiAgICAgICAgICAiZGlnZXN0IjogewogICAgICAgICAgICAic2hhMSI6ICI0ZTZjNWY2ZDBiNGExMjZmYTIzNzNkN2U1N2I3YTBhZjA1MTA4NzkxIgogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfSwKICAgICJydW5EZXRhaWxzIjogewogICAgICAiYnVpbGRlciI6IHsKICAgICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL0F0dGVzdGF0aW9ucy9HaXRIdWJIb3N0ZWRBY3Rpb25zQHYxIgogICAgICB9LAogICAgICAibWV0YWRhdGEiOiB7CiAgICAgICAgImludm9jYXRpb25JZCI6ICJodHRwczovL2dpdGh1Yi5jb20vc2xzYS1mcmFtZXdvcmsvZXhhbXBsZS1wYWNrYWdlL2FjdGlvbnMvcnVucy80MTM1NDYzNzQxL2F0dGVtcHRzLzEiCiAgICAgIH0KICAgIH0KICB9Cn0K", + "signatures": [ + { + "keyid": "", + "sig": "MEUCIGIitQ1z1kUQEEaYdGLUtremEsfBzJyGm+Wp2t3PtzSSAiEAiibeJkqt6tTWcxbHNQqUKmtcteyH49NO8U7KiWtu+yc=" + } + ] +} diff --git a/verifiers/internal/gha/verifier.go b/verifiers/internal/gha/verifier.go index 4c05d73..6b43b6d 100644 --- a/verifiers/internal/gha/verifier.go +++ b/verifiers/internal/gha/verifier.go @@ -96,11 +96,15 @@ func (v *GHAVerifier) VerifyArtifact(ctx context.Context, return nil, nil, err } + var signedAtt *SignedAttestation /* Verify signature on the intoto attestation. */ - // TODO(https://github.com/slsa-framework/slsa-github-generator/issues/716): - // We will also need to support bundles when those are complete. - signedAtt, err := VerifyProvenanceSignature(ctx, trustedRoot, rClient, - provenance, artifactHash) + if provenanceOpts.ProvenanceBundle != nil { + signedAtt, err = VerifyProvenanceBundle(ctx, provenanceOpts.ProvenanceBundle, + trustedRoot) + } else { + signedAtt, err = VerifyProvenanceSignature(ctx, trustedRoot, rClient, + provenance, artifactHash) + } if err != nil { return nil, nil, err } diff --git a/verifiers/utils/provenance.go b/verifiers/utils/provenance.go new file mode 100644 index 0000000..6cbeb62 --- /dev/null +++ b/verifiers/utils/provenance.go @@ -0,0 +1,21 @@ +package utils + +import ( + "fmt" + + serrors "github.com/slsa-framework/slsa-verifier/v2/errors" +) + +func GetAsString(environment map[string]any, field string) (string, error) { + value, ok := environment[field] + if !ok { + return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, + fmt.Sprintf("environment type for %s", field)) + } + + i, ok := value.(string) + if !ok { + return "", fmt.Errorf("%w: %s '%s'", serrors.ErrorInvalidDssePayload, "environment type string", field) + } + return i, nil +}