feat: Verify provenance by build type (#632)

Fixes #473

Updates handling of provenance by providing implementations based on
[buildType](https://slsa.dev/provenance/v1#buildType) since this
determines how to interpret parameters and dependencies. This is done
because we need a way to interpret parameters not just based on the
predicateType. The 3 major build types with format differences are:
  - non-BYOB SLSA v0.2
  - BYOB SLSA v0.2
  - BYOB SLSA v1.0

---------

Signed-off-by: Ian Lewis <ianlewis@google.com>
This commit is contained in:
Ian Lewis
2023-06-16 09:54:20 +09:00
committed by GitHub
parent 7aa6533540
commit d2dc8193ae
14 changed files with 725 additions and 670 deletions
+3 -2
View File
@@ -16,6 +16,7 @@ import (
"github.com/secure-systems-lab/go-securesystemslib/dsse"
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/internal/gha/slsaprovenance/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
)
@@ -114,7 +115,7 @@ func extractAttestations(attestations []attestation) (*attestation, *attestation
for i := range attestations {
att := attestations[i]
// Provenance type verification.
if att.PredicateType == slsaprovenance.ProvenanceV02Type {
if att.PredicateType == common.ProvenanceV02Type {
provenanceAttestation = &att
}
// Publish type verification.
@@ -196,7 +197,7 @@ func (n *Npm) verifyPublishAttesttationSignature() error {
func (n *Npm) verifyIntotoHeaders() error {
if err := verifyIntotoTypes(n.verifiedProvenanceAtt,
slsaprovenance.ProvenanceV02Type, intoto.PayloadType, false); err != nil {
common.ProvenanceV02Type, intoto.PayloadType, false); err != nil {
return err
}
if err := verifyIntotoTypes(n.verifiedPublishAtt,
+4 -4
View File
@@ -12,7 +12,7 @@ import (
intoto "github.com/in-toto/in-toto-golang/in_toto"
dsselib "github.com/secure-systems-lab/go-securesystemslib/dsse"
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/internal/gha/slsaprovenance/common"
)
func Test_verifyName(t *testing.T) {
@@ -671,7 +671,7 @@ func Test_verifyIntotoTypes(t *testing.T) {
}{
{
name: "prov correct",
predicateType: slsaprovenance.ProvenanceV02Type,
predicateType: common.ProvenanceV02Type,
payloadType: intoto.PayloadType,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
@@ -682,7 +682,7 @@ func Test_verifyIntotoTypes(t *testing.T) {
},
{
name: "prov mismatch payload type",
predicateType: slsaprovenance.ProvenanceV02Type,
predicateType: common.ProvenanceV02Type,
payloadType: intoto.PayloadType,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
@@ -694,7 +694,7 @@ func Test_verifyIntotoTypes(t *testing.T) {
},
{
name: "prov mismatch predicate type",
predicateType: slsaprovenance.ProvenanceV02Type + "a",
predicateType: common.ProvenanceV02Type + "a",
payloadType: intoto.PayloadType,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
+19 -15
View File
@@ -16,11 +16,9 @@ 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/internal/gha/slsaprovenance/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/iface"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
// Load provenance types.
_ "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v1.0"
)
// SignedAttestation contains a signed DSSE envelope
@@ -34,6 +32,7 @@ type SignedAttestation struct {
RekorEntry *models.LogEntryAnon
}
// EnvelopeFromBytes reads a DSSE envelope from the given payload.
func EnvelopeFromBytes(payload []byte) (env *dsselib.Envelope, err error) {
env = &dsselib.Envelope{}
err = json.Unmarshal(payload, env)
@@ -43,7 +42,7 @@ func EnvelopeFromBytes(payload []byte) (env *dsselib.Envelope, err error) {
// Verify Builder ID in provenance statement.
// This function does an exact comparison, and expects expectedBuilderID to be the full
// `name@refs/tags/<name>`.
func verifyBuilderIDExactMatch(prov slsaprovenance.Provenance, expectedBuilderID string) error {
func verifyBuilderIDExactMatch(prov iface.Provenance, expectedBuilderID string) error {
id, err := prov.BuilderID()
if err != nil {
return err
@@ -62,7 +61,7 @@ func verifyBuilderIDExactMatch(prov slsaprovenance.Provenance, expectedBuilderID
// Verify Builder ID in provenance statement.
// This function verifies the names match. If the expected builder ID contains a version,
// it also verifies the versions match.
func verifyBuilderIDLooseMatch(prov slsaprovenance.Provenance, expectedBuilderID string) error {
func verifyBuilderIDLooseMatch(prov iface.Provenance, expectedBuilderID string) error {
id, err := prov.BuilderID()
if err != nil {
return err
@@ -91,7 +90,7 @@ func asURI(s string) string {
}
// Verify source URI in provenance statement.
func verifySourceURI(prov slsaprovenance.Provenance, expectedSourceURI string, allowNoMaterialRef bool) error {
func verifySourceURI(prov iface.Provenance, expectedSourceURI string, allowNoMaterialRef bool) error {
source := asURI(expectedSourceURI)
// We expect github.com URIs only.
@@ -172,7 +171,7 @@ func sourceFromURI(uri string, allowNoRef bool) (string, error) {
}
// Verify Subject Digest from the provenance statement.
func verifyDigest(prov slsaprovenance.Provenance, expectedHash string) error {
func verifyDigest(prov iface.Provenance, expectedHash string) error {
subjects, err := prov.Subjects()
if err != nil {
return err
@@ -221,6 +220,7 @@ func VerifyProvenanceSignature(ctx context.Context, trustedRoot *TrustedRoot,
provenance, rClient, trustedRoot)
}
// VerifyNpmPackageProvenance verifies provenance for an npm package.
func VerifyNpmPackageProvenance(env *dsselib.Envelope, workflow *WorkflowIdentity,
provenanceOpts *options.ProvenanceOpts, isTrustedBuilder bool,
) error {
@@ -282,7 +282,7 @@ func VerifyNpmPackageProvenance(env *dsselib.Envelope, workflow *WorkflowIdentit
return nil
}
func isValidDelegatorBuilderID(prov slsaprovenance.Provenance) error {
func isValidDelegatorBuilderID(prov iface.Provenance) error {
// Verify the TRW was referenced at a proper tag by the user.
id, err := prov.BuilderID()
if err != nil {
@@ -295,6 +295,7 @@ func isValidDelegatorBuilderID(prov slsaprovenance.Provenance) error {
return utils.IsValidBuilderTag(parts[1], false)
}
// VerifyProvenance verifies the provenance for the given DSSE envelope.
func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceOpts, byob bool,
) error {
prov, err := slsaprovenance.ProvenanceFromEnvelope(env)
@@ -322,7 +323,8 @@ func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceO
return VerifyProvenanceCommonOptions(prov, provenanceOpts, false)
}
func VerifyProvenanceCommonOptions(prov slsaprovenance.Provenance, provenanceOpts *options.ProvenanceOpts,
// VerifyProvenanceCommonOptions verifies the given provenance.
func VerifyProvenanceCommonOptions(prov iface.Provenance, provenanceOpts *options.ProvenanceOpts,
allowNoMaterialRef bool,
) error {
// Verify source.
@@ -366,7 +368,9 @@ func VerifyProvenanceCommonOptions(prov slsaprovenance.Provenance, provenanceOpt
return nil
}
func VerifyWorkflowInputs(prov slsaprovenance.Provenance, inputs map[string]string) error {
// VerifyWorkflowInputs verifies that the workflow inputs in the provenance
// match the expected values.
func VerifyWorkflowInputs(prov iface.Provenance, inputs map[string]string) error {
pyldInputs, err := prov.GetWorkflowInputs()
if err != nil {
return err
@@ -374,7 +378,7 @@ func VerifyWorkflowInputs(prov slsaprovenance.Provenance, inputs map[string]stri
// Verify all inputs.
for k, v := range inputs {
value, err := slsaprovenance.GetAsString(pyldInputs, k)
value, err := common.GetAsString(pyldInputs, k)
if err != nil {
return fmt.Errorf("%w: cannot retrieve value of '%s'", serrors.ErrorMismatchWorkflowInputs, k)
}
@@ -390,7 +394,7 @@ func VerifyWorkflowInputs(prov slsaprovenance.Provenance, inputs map[string]stri
// VerifyBranch verifies that the source branch in the provenance matches the
// expected value.
func VerifyBranch(prov slsaprovenance.Provenance, expectedBranch string) error {
func VerifyBranch(prov iface.Provenance, expectedBranch string) error {
ref, err := prov.GetBranch()
if err != nil {
return err
@@ -410,7 +414,7 @@ func VerifyBranch(prov slsaprovenance.Provenance, expectedBranch string) error {
// VerifyTag verifies that the source tag in the provenance matches the
// expected value.
func VerifyTag(prov slsaprovenance.Provenance, expectedTag string) error {
func VerifyTag(prov iface.Provenance, expectedTag string) error {
ref, err := prov.GetTag()
if err != nil {
return err
@@ -430,7 +434,7 @@ func VerifyTag(prov slsaprovenance.Provenance, expectedTag string) error {
// VerifyVersionedTag verifies that the source tag in the provenance matches the
// expected semver value.
func VerifyVersionedTag(prov slsaprovenance.Provenance, expectedTag string) error {
func VerifyVersionedTag(prov iface.Provenance, expectedTag string) error {
// Retrieve, validate and canonicalize the provenance tag.
// Note: prerelease is validated as part of patch validation
// and must be equal. Build is discarded as per https://semver.org/:
+33 -30
View File
@@ -6,14 +6,12 @@ import (
"strings"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance"
// Load provenance types.
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/iface"
slsav02 "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 verifyProvenanceMatchesCertificate(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
func verifyProvenanceMatchesCertificate(prov iface.Provenance, workflow *WorkflowIdentity) error {
// See the generation at https://github.com/npm/cli/blob/latest/workspaces/libnpmpublish/lib/provenance.js.
// Verify systemParameters.
if err := verifySystemParameters(prov, workflow); err != nil {
@@ -65,7 +63,7 @@ func verifyProvenanceMatchesCertificate(prov slsaprovenance.Provenance, workflow
return nil
}
func verifySubjectDigestName(prov slsaprovenance.Provenance, digestName string) error {
func verifySubjectDigestName(prov iface.Provenance, digestName string) error {
subjects, err := prov.Subjects()
if err != nil {
return err
@@ -84,7 +82,7 @@ func verifySubjectDigestName(prov slsaprovenance.Provenance, digestName string)
return nil
}
func verifyBuildConfig(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
func verifyBuildConfig(prov iface.Provenance, workflow *WorkflowIdentity) error {
triggerPath, err := prov.GetBuildTriggerPath()
if err != nil {
// If the field is not available in the provenance,
@@ -98,7 +96,7 @@ func verifyBuildConfig(prov slsaprovenance.Provenance, workflow *WorkflowIdentit
return equalCertificateValue(workflow.BuildConfigPath, triggerPath, "trigger workflow")
}
func verifyResolvedDependencies(prov slsaprovenance.Provenance) error {
func verifyResolvedDependencies(prov iface.Provenance) error {
n, err := prov.GetNumberResolvedDependencies()
if err != nil {
return err
@@ -110,7 +108,7 @@ func verifyResolvedDependencies(prov slsaprovenance.Provenance) error {
return nil
}
func verifyMetadata(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
func verifyMetadata(prov iface.Provenance, workflow *WorkflowIdentity) error {
if err := verifyCommonMetadata(prov, workflow); err != nil {
return err
}
@@ -125,7 +123,7 @@ func verifyMetadata(prov slsaprovenance.Provenance, workflow *WorkflowIdentity)
return nil
}
func verifyCommonMetadata(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
func verifyCommonMetadata(prov iface.Provenance, workflow *WorkflowIdentity) error {
// Verify build invocation ID.
invocationID, err := prov.GetBuildInvocationID()
if err != nil {
@@ -169,7 +167,7 @@ func verifyCommonMetadata(prov slsaprovenance.Provenance, workflow *WorkflowIden
return nil
}
func verifyV02Metadata(prov slsaprovenance.Provenance) error {
func verifyV02Metadata(prov iface.Provenance) error {
// https://github.com/in-toto/in-toto-golang/blob/master/in_toto/slsa_provenance/v0.2/provenance.go
/*
v0.2:
@@ -181,21 +179,23 @@ func verifyV02Metadata(prov slsaprovenance.Provenance) error {
},
"reproducible": false
*/
prov02, ok := prov.(*slsav02.ProvenanceV02)
prov02, ok := prov.(slsav02.ProvenanceV02)
if !ok {
return nil
}
if prov02.Predicate.Metadata == nil {
predicate := prov02.Predicate()
if predicate.Metadata == nil {
return nil
}
if prov02.Predicate.Metadata.Reproducible {
if predicate.Metadata.Reproducible {
return fmt.Errorf("%w: reproducible: %v",
serrors.ErrorNonVerifiableClaim,
prov02.Predicate.Metadata.Reproducible)
predicate.Metadata.Reproducible)
}
completeness := prov02.Predicate.Metadata.Completeness
completeness := predicate.Metadata.Completeness
if completeness.Parameters || completeness.Materials ||
completeness.Environment {
return fmt.Errorf("%w: completeness: %v",
@@ -205,44 +205,47 @@ func verifyV02Metadata(prov slsaprovenance.Provenance) error {
return nil
}
func verifyV02Parameters(prov slsaprovenance.Provenance) error {
func verifyV02Parameters(prov iface.Provenance) error {
// https://github.com/in-toto/in-toto-golang/blob/master/in_toto/slsa_provenance/v0.2/provenance.go
prov02, ok := prov.(*slsav02.ProvenanceV02)
prov02, ok := prov.(slsav02.ProvenanceV02)
if !ok {
return nil
}
if prov02.Predicate.Invocation.Parameters == nil {
predicate := prov02.Predicate()
if predicate.Invocation.Parameters == nil {
return nil
}
m, ok := prov02.Predicate.Invocation.Parameters.(map[string]any)
m, ok := predicate.Invocation.Parameters.(map[string]any)
if !ok || len(m) > 0 {
return fmt.Errorf("%w: parameters: %v",
serrors.ErrorNonVerifiableClaim, prov02.Predicate.Invocation.Parameters)
serrors.ErrorNonVerifiableClaim, predicate.Invocation.Parameters)
}
return nil
}
func verifyV02BuildConfig(prov slsaprovenance.Provenance) error {
func verifyV02BuildConfig(prov iface.Provenance) error {
// https://github.com/in-toto/in-toto-golang/blob/master/in_toto/slsa_provenance/v0.2/provenance.go
prov02, ok := prov.(*slsav02.ProvenanceV02)
prov02, ok := prov.(slsav02.ProvenanceV02)
if !ok {
return nil
}
predicate := prov02.Predicate()
if prov02.Predicate.BuildConfig == nil {
if predicate.BuildConfig == nil {
return nil
}
m, ok := prov02.Predicate.BuildConfig.(map[string]any)
m, ok := predicate.BuildConfig.(map[string]any)
if !ok || len(m) > 0 {
return fmt.Errorf("%w: buildConfig: %v",
serrors.ErrorNonVerifiableClaim, prov02.Predicate.BuildConfig)
serrors.ErrorNonVerifiableClaim, predicate.BuildConfig)
}
return nil
}
func verifySystemParameters(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
func verifySystemParameters(prov iface.Provenance, workflow *WorkflowIdentity) error {
/*
"environment": {
"GITHUB_EVENT_NAME": "workflow_dispatch",
@@ -338,7 +341,7 @@ func getRunIDs(workflow *WorkflowIdentity) (string, string, error) {
func verifySystemRun(params map[string]any, workflow *WorkflowIdentity) error {
// Verify only if the values are provided in the provenance.
if !slsaprovenance.Exists(params, "GITHUB_RUN_ID") && !slsaprovenance.Exists(params, "GITHUB_RUN_ATTEMPT") {
if !common.Exists(params, "GITHUB_RUN_ID") && !common.Exists(params, "GITHUB_RUN_ATTEMPT") {
return nil
}
// The certificate contains runID as '4757060009/attempts/1'.
@@ -364,11 +367,11 @@ func verifySystemRun(params map[string]any, workflow *WorkflowIdentity) error {
func verifySystemParameter(params map[string]any, name string, certValue *string) error {
// If the provenance does not contain an env variable.
if !slsaprovenance.Exists(params, name) {
if !common.Exists(params, name) {
return nil
}
// Provenance contains the field, we must verify it.
provValue, err := slsaprovenance.GetAsString(params, name)
provValue, err := common.GetAsString(params, name)
if err != nil {
return err
}
@@ -1,7 +1,6 @@
package gha
import (
"fmt"
"testing"
"time"
@@ -9,10 +8,7 @@ import (
intoto "github.com/in-toto/in-toto-golang/in_toto"
intotocommon "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
intotov02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
intotov1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
slsav02 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v0.2"
slsav10 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v1.0"
)
func Test_verifySubjectDigestName(t *testing.T) {
@@ -48,7 +44,7 @@ func Test_verifySubjectDigestName(t *testing.T) {
{
name: "invalid no subjects",
digestName: "sha256",
err: serrors.ErrorInvalidDssePayload,
err: serrors.ErrorNonVerifiableClaim,
},
{
name: "wrong digest",
@@ -66,25 +62,10 @@ func Test_verifySubjectDigestName(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
prov02 := &slsav02.ProvenanceV02{
&intoto.ProvenanceStatement{
StatementHeader: intoto.StatementHeader{
Subject: tt.subject,
},
},
prov := &testProvenance{
subjects: tt.subject,
}
err := verifySubjectDigestName(prov02, tt.digestName)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
prov1 := &slsav10.ProvenanceV1{
StatementHeader: intoto.StatementHeader{
Subject: tt.subject,
},
}
err = verifySubjectDigestName(prov1, tt.digestName)
if !errCmp(err, tt.err) {
if err := verifySubjectDigestName(prov, tt.digestName); !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
})
@@ -125,33 +106,10 @@ func Test_verifyBuildConfig(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
prov02 := &slsav02.ProvenanceV02{
&intoto.ProvenanceStatement{
Predicate: intotov02.ProvenancePredicate{
Invocation: intotov02.ProvenanceInvocation{
ConfigSource: intotov02.ConfigSource{
EntryPoint: tt.path,
},
},
},
},
prov := &testProvenance{
buildTriggerPath: tt.path,
}
err := verifyBuildConfig(prov02, &tt.workflow)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
prov1 := &slsav10.ProvenanceV1{
Predicate: intotov1.ProvenancePredicate{
BuildDefinition: intotov1.ProvenanceBuildDefinition{
InternalParameters: map[string]interface{}{
"GITHUB_WORKFLOW_REF": fmt.Sprintf("some/repo/%s@some-ref", tt.path),
},
},
},
}
err = verifyBuildConfig(prov1, &tt.workflow)
if !errCmp(err, tt.err) {
if err := verifyBuildConfig(prov, &tt.workflow); !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
})
@@ -186,29 +144,10 @@ func Test_verifyResolvedDependencies(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
prov02 := &slsav02.ProvenanceV02{
&intoto.ProvenanceStatement{
Predicate: intotov02.ProvenancePredicate{},
},
prov := &testProvenance{
noResolvedDeps: tt.n,
}
if tt.n > 0 {
prov02.Predicate.Materials = make([]intotocommon.ProvenanceMaterial, tt.n)
}
err := verifyResolvedDependencies(prov02)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
prov1 := &slsav10.ProvenanceV1{
Predicate: intotov1.ProvenancePredicate{
BuildDefinition: intotov1.ProvenanceBuildDefinition{},
},
}
if tt.n > 0 {
prov1.Predicate.BuildDefinition.ResolvedDependencies = make([]intotov1.ResourceDescriptor, tt.n)
}
err = verifyResolvedDependencies(prov1)
if !errCmp(err, tt.err) {
if err := verifyResolvedDependencies(prov); !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
})
@@ -296,45 +235,19 @@ func Test_verifyCommonMetadata(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()
metadata := tt.metadata || tt.invocationID != nil || tt.startTime != nil ||
tt.endTime != nil
prov02 := &slsav02.ProvenanceV02{
&intoto.ProvenanceStatement{
Predicate: intotov02.ProvenancePredicate{},
},
}
if metadata {
prov02.Predicate.Metadata = &intotov02.ProvenanceMetadata{}
}
prov := &testProvenance{}
if tt.invocationID != nil {
prov02.Predicate.Metadata.BuildInvocationID = *tt.invocationID
prov.buildInvocationID = *tt.invocationID
}
if tt.startTime != nil {
prov02.Predicate.Metadata.BuildStartedOn = tt.startTime
prov.buildStartTime = tt.startTime
}
if tt.endTime != nil {
prov02.Predicate.Metadata.BuildFinishedOn = tt.endTime
prov.buildFinishTime = tt.endTime
}
err := verifyCommonMetadata(prov02, &tt.workflow)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
prov1 := &slsav10.ProvenanceV1{}
if tt.invocationID != nil {
prov1.Predicate.RunDetails.BuildMetadata.InvocationID = *tt.invocationID
}
if tt.startTime != nil {
prov1.Predicate.RunDetails.BuildMetadata.StartedOn = tt.startTime
}
if tt.endTime != nil {
prov1.Predicate.RunDetails.BuildMetadata.FinishedOn = tt.endTime
}
err = verifyCommonMetadata(prov1, &tt.workflow)
if !errCmp(err, tt.err) {
if err := verifyCommonMetadata(prov, &tt.workflow); !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
})
@@ -382,15 +295,12 @@ func Test_verifyV02Metadata(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()
metadata := tt.metadata || tt.reproducible || tt.parameters ||
tt.environment || tt.materials
prov02 := &slsav02.ProvenanceV02{
&intoto.ProvenanceStatement{},
}
prov02 := &testProvenanceV02{}
if metadata {
prov02.Predicate.Metadata = &intotov02.ProvenanceMetadata{
prov02.predicate.Metadata = &intotov02.ProvenanceMetadata{
Completeness: intotov02.ProvenanceComplete{
Parameters: tt.parameters,
Materials: tt.materials,
@@ -399,8 +309,7 @@ func Test_verifyV02Metadata(t *testing.T) {
Reproducible: tt.reproducible,
}
}
err := verifyV02Metadata(prov02)
if !errCmp(err, tt.err) {
if err := verifyV02Metadata(prov02); !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
})
@@ -441,11 +350,9 @@ func Test_verifyV02Parameters(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
prov02 := &slsav02.ProvenanceV02{
&intoto.ProvenanceStatement{},
}
prov02 := &testProvenanceV02{}
if tt.present || len(tt.value) > 0 {
prov02.Predicate.Invocation.Parameters = tt.value
prov02.predicate.Invocation.Parameters = tt.value
}
err := verifyV02Parameters(prov02)
if !errCmp(err, tt.err) {
@@ -489,11 +396,9 @@ func Test_verifyV02BuildConfig(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
prov02 := &slsav02.ProvenanceV02{
&intoto.ProvenanceStatement{},
}
prov02 := &testProvenanceV02{}
if tt.present || len(tt.value) > 0 {
prov02.Predicate.BuildConfig = tt.value
prov02.predicate.BuildConfig = tt.value
}
err := verifyV02BuildConfig(prov02)
if !errCmp(err, tt.err) {
@@ -616,52 +521,43 @@ func Test_verifyMetadata(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
metadata := tt.metadata || tt.invocationID != nil || tt.startTime != nil ||
tt.endTime != nil || tt.reproducible || tt.parameters ||
tt.environment || tt.materials
prov02 := &slsav02.ProvenanceV02{
&intoto.ProvenanceStatement{},
}
if metadata {
prov02.Predicate.Metadata = &intotov02.ProvenanceMetadata{
Completeness: intotov02.ProvenanceComplete{
Parameters: tt.parameters,
Materials: tt.materials,
Environment: tt.environment,
},
Reproducible: tt.reproducible,
}
if tt.invocationID != nil {
prov02.Predicate.Metadata.BuildInvocationID = *tt.invocationID
}
if tt.startTime != nil {
prov02.Predicate.Metadata.BuildStartedOn = tt.startTime
}
if tt.endTime != nil {
prov02.Predicate.Metadata.BuildFinishedOn = tt.endTime
}
}
errV02 := verifyMetadata(prov02, &tt.workflow)
if !errCmp(errV02, tt.errV02) {
t.Errorf(cmp.Diff(errV02, tt.errV02))
}
prov1 := &slsav10.ProvenanceV1{}
prov02 := &testProvenanceV02{}
if tt.invocationID != nil {
prov1.Predicate.RunDetails.BuildMetadata.InvocationID = *tt.invocationID
prov02.buildInvocationID = *tt.invocationID
}
if tt.startTime != nil {
prov1.Predicate.RunDetails.BuildMetadata.StartedOn = tt.startTime
prov02.buildStartTime = tt.startTime
}
if tt.endTime != nil {
prov1.Predicate.RunDetails.BuildMetadata.FinishedOn = tt.endTime
prov02.buildFinishTime = tt.endTime
}
errV01 := verifyMetadata(prov1, &tt.workflow)
if !errCmp(errV01, tt.errV01) {
t.Errorf(cmp.Diff(errV01, tt.errV01))
prov02.predicate.Metadata = &intotov02.ProvenanceMetadata{
Completeness: intotov02.ProvenanceComplete{
Parameters: tt.parameters,
Materials: tt.materials,
Environment: tt.environment,
},
Reproducible: tt.reproducible,
}
if err := verifyMetadata(prov02, &tt.workflow); !errCmp(err, tt.errV02) {
t.Errorf(cmp.Diff(err, tt.errV02))
}
prov1 := &testProvenanceV1{}
if tt.invocationID != nil {
prov1.buildInvocationID = *tt.invocationID
}
if tt.startTime != nil {
prov1.buildStartTime = tt.startTime
}
if tt.endTime != nil {
prov1.buildFinishTime = tt.endTime
}
if err := verifyMetadata(prov1, &tt.workflow); !errCmp(err, tt.errV01) {
t.Errorf(cmp.Diff(err, tt.errV01))
}
})
}
@@ -1038,30 +934,12 @@ func Test_verifySystemParameters(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()
prov02 := &slsav02.ProvenanceV02{
&intoto.ProvenanceStatement{
Predicate: intotov02.ProvenancePredicate{
Invocation: intotov02.ProvenanceInvocation{
Environment: tt.environment,
},
},
},
prov := &testProvenance{
systemParameters: tt.environment,
}
err := verifySystemParameters(prov02, &tt.workflow)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
prov1 := &slsav10.ProvenanceV1{
Predicate: intotov1.ProvenancePredicate{
BuildDefinition: intotov1.ProvenanceBuildDefinition{
InternalParameters: tt.environment,
},
},
}
err = verifySystemParameters(prov1, &tt.workflow)
if !errCmp(err, tt.err) {
if err := verifySystemParameters(prov, &tt.workflow); !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
})
@@ -1127,6 +1005,11 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
},
{
name: "unknown field",
subject: []intoto.Subject{
{
Digest: intotocommon.DigestSet{"sha512": "abcd"},
},
},
environment: map[string]interface{}{
"SOMETHING": "workflow_dispatch",
},
@@ -1201,48 +1084,15 @@ func Test_verifyProvenanceMatchesCertificate(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()
prov02 := &slsav02.ProvenanceV02{
&intoto.ProvenanceStatement{
StatementHeader: intoto.StatementHeader{
Subject: tt.subject,
},
Predicate: intotov02.ProvenancePredicate{
Invocation: intotov02.ProvenanceInvocation{
Environment: tt.environment,
ConfigSource: intotov02.ConfigSource{
EntryPoint: tt.workflowTriggerPath,
},
},
},
},
prov := &testProvenance{
subjects: tt.subject,
noResolvedDeps: tt.numberResolvedDependencies,
buildTriggerPath: tt.workflowTriggerPath,
systemParameters: tt.environment,
}
if tt.numberResolvedDependencies > 0 {
prov02.Predicate.Materials = make([]intotocommon.ProvenanceMaterial, tt.numberResolvedDependencies)
}
err := verifyProvenanceMatchesCertificate(prov02, &tt.certificateIdentity)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
prov1 := &slsav10.ProvenanceV1{
StatementHeader: intoto.StatementHeader{
Subject: tt.subject,
},
Predicate: intotov1.ProvenancePredicate{
BuildDefinition: intotov1.ProvenanceBuildDefinition{
InternalParameters: tt.environment,
// TODO(#566): verify fields for v1.0 provenance.
},
},
}
if tt.numberResolvedDependencies > 0 {
prov1.Predicate.BuildDefinition.ResolvedDependencies = make([]intotov1.ResourceDescriptor, tt.numberResolvedDependencies)
}
err = verifyProvenanceMatchesCertificate(prov1, &tt.certificateIdentity)
if !errCmp(err, tt.err) {
if err := verifyProvenanceMatchesCertificate(prov, &tt.certificateIdentity); !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
})
+23 -6
View File
@@ -8,9 +8,12 @@ import (
"github.com/google/go-cmp/cmp"
intoto "github.com/in-toto/in-toto-golang/in_toto"
"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"
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/internal/gha/slsaprovenance/iface"
)
type testProvenance struct {
@@ -47,7 +50,21 @@ func (p *testProvenance) GetWorkflowInputs() (map[string]interface{}, error) {
return p.workflowInputs, nil
}
func provenanceFromBytes(payload []byte) (slsaprovenance.Provenance, error) {
type testProvenanceV02 struct {
testProvenance
predicate slsa02.ProvenancePredicate
}
func (p *testProvenanceV02) Predicate() slsa02.ProvenancePredicate { return p.predicate }
type testProvenanceV1 struct {
testProvenance
predicate slsa1.ProvenancePredicate
}
func (p *testProvenanceV1) Predicate() slsa1.ProvenancePredicate { return p.predicate }
func provenanceFromBytes(payload []byte) (iface.Provenance, error) {
env, err := EnvelopeFromBytes(payload)
if err != nil {
return nil, err
@@ -95,7 +112,7 @@ func Test_VerifyDigest(t *testing.T) {
t.Parallel()
tests := []struct {
name string
prov slsaprovenance.Provenance
prov iface.Provenance
artifactHash string
expected error
}{
@@ -507,7 +524,7 @@ func Test_VerifyBranch(t *testing.T) {
t.Parallel()
tests := []struct {
name string
prov slsaprovenance.Provenance
prov iface.Provenance
branch string
expected error
}{
@@ -574,7 +591,7 @@ func Test_VerifyWorkflowInputs(t *testing.T) {
t.Parallel()
tests := []struct {
name string
prov slsaprovenance.Provenance
prov iface.Provenance
inputs map[string]string
expected error
}{
@@ -696,7 +713,7 @@ func Test_VerifyTag(t *testing.T) {
t.Parallel()
tests := []struct {
name string
prov slsaprovenance.Provenance
prov iface.Provenance
tag string
expected error
}{
@@ -763,7 +780,7 @@ func Test_VerifyVersionedTag(t *testing.T) {
t.Parallel()
tests := []struct {
name string
prov slsaprovenance.Provenance
prov iface.Provenance
tag string
expected error
}{
@@ -1,18 +1,22 @@
package slsaprovenance
package common
import (
"fmt"
"strings"
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
)
const (
// ProvenanceV02Type is the SLSA v0.2 predicate type.
ProvenanceV02Type = "https://slsa.dev/provenance/v0.2"
)
// GetWorkflowInputs gets the workflow inputs from the GitHub environment map
// and converts the keys to the necessary casing depending on predicate type.
func GetWorkflowInputs(environment map[string]any, predicateType string) (map[string]any, error) {
func GetWorkflowInputs(environment map[string]any, upperEnv bool) (map[string]any, error) {
// Verify it's a workflow_dispatch trigger.
eventKey, err := convertKey("github_event_name", predicateType)
eventKey, err := convertKey("github_event_name", upperEnv)
if err != nil {
return nil, fmt.Errorf("%w: %s",
serrors.ErrorMismatchWorkflowInputs, err)
@@ -26,7 +30,7 @@ func GetWorkflowInputs(environment map[string]any, predicateType string) (map[st
serrors.ErrorMismatchWorkflowInputs, triggerName)
}
payload, err := GetEventPayload(environment, predicateType)
payload, err := GetEventPayload(environment, upperEnv)
if err != nil {
return nil, err
}
@@ -45,8 +49,8 @@ func GetWorkflowInputs(environment map[string]any, predicateType string) (map[st
// GetEventPayload retrieves the GitHub event payload from the environment map
// that contains the GitHub context payload.
func GetEventPayload(environment map[string]any, predicateType string) (map[string]any, error) {
eventPayloadKey, err := convertKey("github_event_payload", predicateType)
func GetEventPayload(environment map[string]any, upperEnv bool) (map[string]any, error) {
eventPayloadKey, err := convertKey("github_event_payload", upperEnv)
if err != nil {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err)
}
@@ -63,15 +67,11 @@ func GetEventPayload(environment map[string]any, predicateType string) (map[stri
return payload, nil
}
func convertKey(key, predicateType string) (string, error) {
switch predicateType {
case slsa1.PredicateSLSAProvenance:
func convertKey(key string, upperEnv bool) (string, error) {
if upperEnv {
return strings.ToUpper(key), nil
case ProvenanceV02Type:
return key, nil
default:
return "", fmt.Errorf("unrecognized predicate type %s", predicateType)
}
return key, nil
}
func getAsAny(environment map[string]any, field string) (any, error) {
@@ -83,8 +83,8 @@ func getAsAny(environment map[string]any, field string) (any, error) {
return value, nil
}
func getBranchForTag(environment map[string]any, predicateType string) (string, error) {
baseRefKey, err := convertKey("github_base_ref", predicateType)
func getBranchForTag(environment map[string]any, upperEnv bool) (string, error) {
baseRefKey, err := convertKey("github_base_ref", upperEnv)
if err != nil {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err)
}
@@ -100,7 +100,7 @@ func getBranchForTag(environment map[string]any, predicateType string) (string,
}
// Look at the event payload instead.
environmentKey, err := convertKey("github_event_name", predicateType)
environmentKey, err := convertKey("github_event_name", upperEnv)
if err != nil {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err)
}
@@ -109,7 +109,7 @@ func getBranchForTag(environment map[string]any, predicateType string) (string,
return "", err
}
payload, err := GetEventPayload(environment, predicateType)
payload, err := GetEventPayload(environment, upperEnv)
if err != nil {
return "", err
}
@@ -154,8 +154,9 @@ func getBranchForTag(environment map[string]any, predicateType string) (string,
}
}
func GetTag(environment map[string]any, predicateType string) (string, error) {
refTypeKey, err := convertKey("github_ref_type", predicateType)
// GetTag returns the triggering event's tag based on the given environment.
func GetTag(environment map[string]any, upperEnv bool) (string, error) {
refTypeKey, err := convertKey("github_ref_type", upperEnv)
if err != nil {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err)
}
@@ -169,7 +170,7 @@ func GetTag(environment map[string]any, predicateType string) (string, error) {
case "branch":
return "", nil
case "tag":
refKey, err := convertKey("github_ref", predicateType)
refKey, err := convertKey("github_ref", upperEnv)
if err != nil {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err)
}
@@ -180,8 +181,9 @@ func GetTag(environment map[string]any, predicateType string) (string, error) {
}
}
func GetBranch(environment map[string]any, predicateType string) (string, error) {
refTypeKey, err := convertKey("github_ref_type", predicateType)
// GetBranch returns the triggering event's branch based on the given environment.
func GetBranch(environment map[string]any, upperEnv bool) (string, error) {
refTypeKey, err := convertKey("github_ref_type", upperEnv)
if err != nil {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err)
}
@@ -193,24 +195,26 @@ func GetBranch(environment map[string]any, predicateType string) (string, error)
switch refType {
case "branch":
refKey, err := convertKey("github_ref", predicateType)
refKey, err := convertKey("github_ref", upperEnv)
if err != nil {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err)
}
return GetAsString(environment, refKey)
case "tag":
return getBranchForTag(environment, predicateType)
return getBranchForTag(environment, upperEnv)
default:
return "", fmt.Errorf("%w: %s %s", serrors.ErrorInvalidDssePayload,
"unknown ref type", refType)
}
}
// Exists returns true if the given key exists in the environment.
func Exists(environment map[string]any, field string) bool {
_, ok := environment[field]
return ok
}
// GetAsString returns the value in the given environment as a string.
func GetAsString(environment map[string]any, field string) (string, error) {
value, ok := environment[field]
if !ok {
@@ -0,0 +1,50 @@
package iface
import (
"time"
intoto "github.com/in-toto/in-toto-golang/in_toto"
)
// Provenance represents provenance for a predicate type and build type.
type Provenance interface {
// BuilderID returns the builder id in the predicate.
BuilderID() (string, error)
// SourceURI is the full URI (including tag) of the source material.
SourceURI() (string, error)
// TriggerURI is the full URI (including tag) of the configuration / trigger.
TriggerURI() (string, error)
// Subject is the list of intoto subjects in the provenance.
Subjects() ([]intoto.Subject, error)
// GetBranch retrieves the branch name of the source from the provenance.
GetBranch() (string, error)
// GetTag retrieves the tag of the source from the provenance.
GetTag() (string, error)
// Get workflow trigger path.
GetBuildTriggerPath() (string, error)
// Get system pararmeters.
GetSystemParameters() (map[string]any, error)
// Get build invocation ID.
GetBuildInvocationID() (string, error)
// Get build start time.
GetBuildStartTime() (*time.Time, error)
// Get build finish time.
GetBuildFinishTime() (*time.Time, error)
// Get number of resolved dependencies.
GetNumberResolvedDependencies() (int, error)
// GetWorkflowInputs retrieves the inputs from the provenance. Only succeeds for event
// relevant event types (workflow_inputs).
GetWorkflowInputs() (map[string]interface{}, error)
}
@@ -1,69 +1,32 @@
package slsaprovenance
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"sync"
"time"
intoto "github.com/in-toto/in-toto-golang/in_toto"
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
dsselib "github.com/secure-systems-lab/go-securesystemslib/dsse"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/iface"
slsav02 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v0.2"
slsav1 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v1.0"
)
const (
ProvenanceV02Type = "https://slsa.dev/provenance/v0.2"
)
// provenanceConstructor creates a new Provenance instance for the given payload as a json Decoder.
type provenanceConstructor func(payload []byte) (iface.Provenance, error)
type Provenance interface {
// BuilderID returns the builder id in the predicate.
BuilderID() (string, error)
// SourceURI is the full URI (including tag) of the source material.
SourceURI() (string, error)
// TriggerURI is the full URI (including tag) of the configuration / trigger.
TriggerURI() (string, error)
// Subject is the list of intoto subjects in the provenance.
Subjects() ([]intoto.Subject, error)
// GetBranch retrieves the branch name of the source from the provenance.
GetBranch() (string, error)
// GetTag retrieves the tag of the source from the provenance.
GetTag() (string, error)
// Get workflow trigger path.
GetBuildTriggerPath() (string, error)
// Get system pararmeters.
GetSystemParameters() (map[string]any, error)
// Get build invocation ID.
GetBuildInvocationID() (string, error)
// Get build start time.
GetBuildStartTime() (*time.Time, error)
// Get build finish time.
GetBuildFinishTime() (*time.Time, error)
// Get number of resolved dependencies.
GetNumberResolvedDependencies() (int, error)
// GetWorkflowInputs retrieves the inputs from the provenance. Only succeeds for event
// relevant event types (workflow_inputs).
GetWorkflowInputs() (map[string]interface{}, error)
// predicateTypeMap stores the different provenance version types. It is a map of
// predicate type -> ProvenanceConstructor.
var predicateTypeMap = map[string]provenanceConstructor{
common.ProvenanceV02Type: slsav02.New,
slsa1.PredicateSLSAProvenance: slsav1.New,
}
// ProvenanceMap stores the different provenance version types.
var ProvenanceMap sync.Map
// Provenance interface that each type may implement.
func ProvenanceFromEnvelope(env *dsselib.Envelope) (Provenance, error) {
// ProvenanceFromEnvelope returns a Provenance instance for the given DSSE Envelope.
func ProvenanceFromEnvelope(env *dsselib.Envelope) (iface.Provenance, error) {
if env.PayloadType != "application/vnd.in-toto+json" {
return nil, fmt.Errorf("%w: expected payload type 'application/vnd.in-toto+json', got '%s'",
serrors.ErrorInvalidDssePayload, env.PayloadType)
@@ -82,19 +45,14 @@ func ProvenanceFromEnvelope(env *dsselib.Envelope) (Provenance, error) {
}
// Load the appropriate structure and unmarshal.
ptype, ok := ProvenanceMap.Load(pred.PredicateType)
newProv, ok := predicateTypeMap[pred.PredicateType]
if !ok {
return nil, fmt.Errorf("%w: unexpected predicate type '%s'", serrors.ErrorInvalidDssePayload, pred.PredicateType)
}
prov := ptype.(func() Provenance)()
// Strict unmarshal.
// NOTE: this supports extensions because they are
// only used as part of interface{}-defined fields.
dec := json.NewDecoder(bytes.NewReader(pyld))
dec.DisallowUnknownFields()
if err := dec.Decode(prov); err != nil {
prov, err := newProv(pyld)
if err != nil {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err.Error())
}
return prov, nil
}
@@ -0,0 +1,135 @@
package v02
import (
"fmt"
"time"
intoto "github.com/in-toto/in-toto-golang/in_toto"
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/common"
)
// provenanceV02 implements basic logic for SLSA v0.2 provenance.
type provenanceV02 struct {
// upperEnv specifies if environment fields are in uppercase.
upperEnv bool
prov *intotoAttestation
}
// Predicate implements provenanceV02.Predicate.
func (p *provenanceV02) Predicate() slsa02.ProvenancePredicate {
return p.prov.Predicate
}
// BuilderID implements Provenance.BuilderID.
func (p *provenanceV02) BuilderID() (string, error) {
return p.prov.Predicate.Builder.ID, nil
}
// SourceURI implements Provenance.SourceURI.
func (p *provenanceV02) SourceURI() (string, error) {
if len(p.prov.Predicate.Materials) == 0 {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "no material")
}
uri := p.prov.Predicate.Materials[0].URI
if uri == "" {
return "", fmt.Errorf("%w: empty uri", serrors.ErrorMalformedURI)
}
return uri, nil
}
// TriggerURI implements Provenance.TriggerURI.
func (p *provenanceV02) TriggerURI() (string, error) {
uri := p.prov.Predicate.Invocation.ConfigSource.URI
if uri == "" {
return "", fmt.Errorf("%w: empty uri", serrors.ErrorMalformedURI)
}
return uri, nil
}
// Subjects implements Provenance.Subjects.
func (p *provenanceV02) Subjects() ([]intoto.Subject, error) {
subj := p.prov.Subject
if len(subj) == 0 {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "no subjects")
}
return subj, nil
}
// GetBranch implements Provenance.GetBranch.
func (p *provenanceV02) GetBranch() (string, error) {
// GetBranch gets the branch from the invocation parameters.
environment, ok := p.prov.Predicate.Invocation.Environment.(map[string]interface{})
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type")
}
return common.GetBranch(environment, p.upperEnv)
}
// GetTag implements Provenance.GetTag.
func (p *provenanceV02) GetTag() (string, error) {
environment, ok := p.prov.Predicate.Invocation.Environment.(map[string]interface{})
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type")
}
return common.GetTag(environment, p.upperEnv)
}
// GetWorkflowInputs implements Provenance.GetWorkflowInputs.
func (p *provenanceV02) GetWorkflowInputs() (map[string]interface{}, error) {
// Verify it's a workflow_dispatch trigger.
environment, ok := p.prov.Predicate.Invocation.Environment.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type")
}
return common.GetWorkflowInputs(environment, p.upperEnv)
}
// GetBuildTriggerPath implements Provenance.GetBuildTriggerPath.
func (p *provenanceV02) GetBuildTriggerPath() (string, error) {
return p.prov.Predicate.Invocation.ConfigSource.EntryPoint, nil
}
// GetBuildInvocationID implements Provenance.GetBuildInvocationID.
func (p *provenanceV02) GetBuildInvocationID() (string, error) {
if p.prov.Predicate.Metadata == nil {
return "", nil
}
return p.prov.Predicate.Metadata.BuildInvocationID, nil
}
// GetBuildStartTime implements Provenance.GetBuildStartTime.
func (p *provenanceV02) GetBuildStartTime() (*time.Time, error) {
if p.prov.Predicate.Metadata == nil {
return nil, nil
}
return p.prov.Predicate.Metadata.BuildStartedOn, nil
}
// GetBuildFinishTime implements Provenance.GetBuildFinishTime.
func (p *provenanceV02) GetBuildFinishTime() (*time.Time, error) {
if p.prov.Predicate.Metadata == nil {
return nil, nil
}
return p.prov.Predicate.Metadata.BuildFinishedOn, nil
}
// GetNumberResolvedDependencies implements Provenance.GetNumberResolvedDependencies.
func (p *provenanceV02) GetNumberResolvedDependencies() (int, error) {
return len(p.prov.Predicate.Materials), nil
}
// GetSystemParameters implements Provenance.GetSystemParameters.
func (p *provenanceV02) GetSystemParameters() (map[string]any, error) {
environment, ok := p.prov.Predicate.Invocation.Environment.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type")
}
return environment, nil
}
@@ -1,126 +1,73 @@
package v02
import (
"bytes"
"encoding/json"
"fmt"
"time"
intoto "github.com/in-toto/in-toto-golang/in_toto"
slsa02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
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/internal/gha/slsaprovenance/iface"
)
// TODO(https://github.com/slsa-framework/slsa-verifier/issues/473): Use a static mapping.
//
//nolint:gochecknoinits
func init() {
slsaprovenance.ProvenanceMap.Store(
slsaprovenance.ProvenanceV02Type,
New)
var (
goBuilderBuildType = "https://github.com/slsa-framework/slsa-github-generator/go@v1"
genericGeneratorBuildType = "https://github.com/slsa-framework/slsa-github-generator/generic@v1"
containerGeneratorBuildType = "https://github.com/slsa-framework/slsa-github-generator/container@v1"
npmCLIBuildType = "https://github.com/npm/cli/gha@v1"
// Legacy build types.
legacyGoBuilderBuildType = "https://github.com/slsa-framework/slsa-github-generator-go@v1"
legacyBuilderBuildType = "https://github.com/slsa-framework/slsa-github-generator@v1"
// byobBuildType is the base build type for BYOB delegated builders.
byobDelegatorBuildType = "https://github.com/slsa-framework/slsa-github-generator/delegator-generic@v0"
)
// intotoAttestation is a SLSA v0.2 in-toto attestation statement.
type intotoAttestation struct {
intoto.StatementHeader
Predicate slsa02.ProvenancePredicate `json:"predicate"`
}
type ProvenanceV02 struct {
*intoto.ProvenanceStatement
// ProvenanceV02 represents v0.2 provenance.
type ProvenanceV02 interface {
Predicate() slsa02.ProvenancePredicate
}
// This returns a new, empty instance of the v0.2 provenance.
func New() slsaprovenance.Provenance {
return &ProvenanceV02{}
}
// New returns a new Provenance for the given json payload.
func New(payload []byte) (iface.Provenance, error) {
// Strict unmarshal.
// NOTE: this supports extensions because they are
// only used as part of interface{}-defined fields.
dec := json.NewDecoder(bytes.NewReader(payload))
dec.DisallowUnknownFields()
func (prov *ProvenanceV02) BuilderID() (string, error) {
return prov.Predicate.Builder.ID, nil
}
func (prov *ProvenanceV02) SourceURI() (string, error) {
if len(prov.Predicate.Materials) == 0 {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "no material")
}
uri := prov.Predicate.Materials[0].URI
if uri == "" {
return "", fmt.Errorf("%w: empty uri", serrors.ErrorMalformedURI)
a := &intotoAttestation{}
if err := dec.Decode(a); err != nil {
return nil, err
}
return uri, nil
}
func (prov *ProvenanceV02) TriggerURI() (string, error) {
uri := prov.Predicate.Invocation.ConfigSource.URI
if uri == "" {
return "", fmt.Errorf("%w: empty uri", serrors.ErrorMalformedURI)
switch {
case a.Predicate.BuildType == byobDelegatorBuildType:
return &provenanceV02{
upperEnv: true,
prov: a,
}, nil
case a.Predicate.BuildType == goBuilderBuildType ||
a.Predicate.BuildType == genericGeneratorBuildType ||
a.Predicate.BuildType == containerGeneratorBuildType ||
a.Predicate.BuildType == npmCLIBuildType ||
a.Predicate.BuildType == legacyBuilderBuildType ||
a.Predicate.BuildType == legacyGoBuilderBuildType:
return &provenanceV02{
upperEnv: false,
prov: a,
}, nil
default:
return nil, fmt.Errorf("%w: unknown buildType: %q", serrors.ErrorInvalidDssePayload, a.Predicate.BuildType)
}
return uri, nil
}
func (prov *ProvenanceV02) 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 *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")
}
return slsaprovenance.GetBranch(environment, prov.PredicateType)
}
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")
}
return slsaprovenance.GetTag(environment, prov.PredicateType)
}
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")
}
return slsaprovenance.GetWorkflowInputs(environment, prov.PredicateType)
}
func (prov *ProvenanceV02) GetBuildTriggerPath() (string, error) {
return prov.Predicate.Invocation.ConfigSource.EntryPoint, nil
}
func (prov *ProvenanceV02) GetBuildInvocationID() (string, error) {
if prov.Predicate.Metadata == nil {
return "", nil
}
return prov.Predicate.Metadata.BuildInvocationID, nil
}
func (prov *ProvenanceV02) GetBuildStartTime() (*time.Time, error) {
if prov.Predicate.Metadata == nil {
return nil, nil
}
return prov.Predicate.Metadata.BuildStartedOn, nil
}
func (prov *ProvenanceV02) GetBuildFinishTime() (*time.Time, error) {
if prov.Predicate.Metadata == nil {
return nil, nil
}
return prov.Predicate.Metadata.BuildFinishedOn, nil
}
func (prov *ProvenanceV02) GetNumberResolvedDependencies() (int, error) {
return len(prov.Predicate.Materials), nil
}
func (prov *ProvenanceV02) GetSystemParameters() (map[string]any, error) {
environment, ok := prov.Predicate.Invocation.Environment.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type")
}
return environment, nil
}
@@ -0,0 +1,240 @@
package v1
import (
"fmt"
"strings"
"time"
intoto "github.com/in-toto/in-toto-golang/in_toto"
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/common"
)
// byobBuildType is the base build type for BYOB delegated builders.
var byobBuildType = "https://github.com/slsa-framework/slsa-github-generator/delegator-generic@v0"
// BYOBProvenance is SLSA v1.0 provenance for the slsa-github-generator BYOB build type.
type BYOBProvenance struct {
prov *intotoAttestation
}
// Predicate implements ProvenanceV02.Predicate.
func (p *BYOBProvenance) Predicate() slsa1.ProvenancePredicate {
return p.prov.Predicate
}
// BuilderID implements Provenance.BuilderID.
func (p *BYOBProvenance) BuilderID() (string, error) {
return p.prov.Predicate.RunDetails.Builder.ID, nil
}
// SourceURI implements Provenance.SourceURI.
func (p *BYOBProvenance) SourceURI() (string, error) {
// Use resolvedDependencies.
if len(p.prov.Predicate.BuildDefinition.ResolvedDependencies) == 0 {
return "", fmt.Errorf("%w: empty resovedDependencies", serrors.ErrorInvalidDssePayload)
}
// For now, we use the first resolvedDependency relying on a GHA builder-verifier contract.
uri := p.prov.Predicate.BuildDefinition.ResolvedDependencies[0].URI
if uri == "" {
return "", fmt.Errorf("%w: empty uri", serrors.ErrorMalformedURI)
}
return uri, nil
}
// TODO(#613): Support for generators.
//
//nolint:unused
func getValidateKey(m map[string]interface{}, key string) (string, error) {
v, ok := m[key]
if !ok {
return "", fmt.Errorf("%w: no %v found", serrors.ErrorInvalidFormat, key)
}
vv, ok := v.(string)
if !ok {
return "", fmt.Errorf("%w: not a string %v", serrors.ErrorInvalidFormat, v)
}
if vv == "" {
return "", fmt.Errorf("%w: empty %v", serrors.ErrorInvalidFormat, key)
}
return vv, nil
}
// TODO(#613): Support for generators.
//
//nolint:unused
func (p *BYOBProvenance) generatorTriggerInfo() (string, string, string, error) {
// See https://github.com/slsa-framework/github-actions-buildtypes/blob/main/workflow/v1/example.json#L16-L19.
extParams, ok := p.prov.Predicate.BuildDefinition.ExternalParameters.(map[string]interface{})
if !ok {
return "", "", "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "external parameters type")
}
workflow, ok := extParams["workflow"]
if !ok {
return "", "", "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "external parameters workflow")
}
workflowMap, ok := workflow.(map[string]interface{})
if !ok {
return "", "", "", fmt.Errorf("%w: %s, type %T", serrors.ErrorInvalidDssePayload, "not a map of interface{}", workflow)
}
ref, err := getValidateKey(workflowMap, "ref")
if err != nil {
return "", "", "", fmt.Errorf("%w: %v", serrors.ErrorMalformedURI, err)
}
repository, err := getValidateKey(workflowMap, "repository")
if err != nil {
return "", "", "", fmt.Errorf("%w: %v", serrors.ErrorMalformedURI, err)
}
path, err := getValidateKey(workflowMap, "path")
if err != nil {
return "", "", "", err
}
return repository, ref, path, nil
}
func (p *BYOBProvenance) builderTriggerInfo() (string, string, string, error) {
sysParams, ok := p.prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
if !ok {
return "", "", "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "internal parameters type")
}
if _, exists := sysParams["GITHUB_WORKFLOW_REF"]; !exists {
return "", "", "", fmt.Errorf("%w: GITHUB_WORKFLOW_REF", serrors.ErrorNotPresent)
}
workflowRef, err := common.GetAsString(sysParams, "GITHUB_WORKFLOW_REF")
if err != nil {
return "", "", "", err
}
parts := strings.Split(workflowRef, "@")
if len(parts) != 2 {
return "", "", "", fmt.Errorf("%w: ref: %s", serrors.ErrorInvalidFormat, workflowRef)
}
repoAndPath := parts[0]
ref := parts[1]
parts = strings.Split(repoAndPath, "/")
if len(parts) < 2 {
return "", "", "", fmt.Errorf("%w: rep and path: %s", serrors.ErrorInvalidFormat, repoAndPath)
}
repo := strings.Join(parts[:2], "/")
path := strings.Join(parts[2:], "/")
return fmt.Sprintf("git+https://github.com/%s", repo), ref, path, nil
}
func (p *BYOBProvenance) triggerInfo() (string, string, string, error) {
// TODO(#613): Support for generators.
return p.builderTriggerInfo()
}
// TriggerURI implements Provenance.TriggerURI.
func (p *BYOBProvenance) TriggerURI() (string, error) {
repository, ref, _, err := p.triggerInfo()
if err != nil {
return "", err
}
if repository == "" || ref == "" {
return "", fmt.Errorf("%w: repository or ref is empty", serrors.ErrorMalformedURI)
}
return fmt.Sprintf("%s@%s", repository, ref), nil
}
// Subjects implements Provenance.Subjects.
func (p *BYOBProvenance) Subjects() ([]intoto.Subject, error) {
subj := p.prov.Subject
if len(subj) == 0 {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "no subjects")
}
return subj, nil
}
// GetBranch implements Provenance.GetBranch.
func (p *BYOBProvenance) GetBranch() (string, error) {
// TODO(https://github.com/slsa-framework/slsa-verifier/issues/472): Add GetBranch() support.
sysParams, ok := p.prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "internal parameters type")
}
return common.GetBranch(sysParams, true)
}
// GetTag implements Provenance.GetTag.
func (p *BYOBProvenance) GetTag() (string, error) {
// Get the value from the internalParameters if there is no source URI.
sysParams, ok := p.prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type")
}
return common.GetTag(sysParams, true)
}
// GetWorkflowInputs implements Provenance.GetWorkflowInputs.
func (p *BYOBProvenance) GetWorkflowInputs() (map[string]interface{}, error) {
sysParams, ok := p.prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type")
}
return common.GetWorkflowInputs(sysParams, true)
}
// GetBuildTriggerPath implements Provenance.GetBuildTriggerPath.
func (p *BYOBProvenance) GetBuildTriggerPath() (string, error) {
// TODO(https://github.com/slsa-framework/slsa-verifier/issues/566):
// verify the ref and repo as well.
sysParams, ok := p.prov.Predicate.BuildDefinition.ExternalParameters.(map[string]interface{})
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type")
}
w, ok := sysParams["workflow"]
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "workflow parameters type")
}
wMap, ok := w.(map[string]string)
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "workflow not a map")
}
v, ok := wMap["path"]
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "no path entry on workflow")
}
return v, nil
}
// GetBuildInvocationID implements Provenance.GetBuildInvocationID.
func (p *BYOBProvenance) GetBuildInvocationID() (string, error) {
return p.prov.Predicate.RunDetails.BuildMetadata.InvocationID, nil
}
// GetBuildStartTime implements Provenance.GetBuildStartTime.
func (p *BYOBProvenance) GetBuildStartTime() (*time.Time, error) {
return p.prov.Predicate.RunDetails.BuildMetadata.StartedOn, nil
}
// GetBuildFinishTime implements Provenance.GetBuildFinishTime.
func (p *BYOBProvenance) GetBuildFinishTime() (*time.Time, error) {
return p.prov.Predicate.RunDetails.BuildMetadata.FinishedOn, nil
}
// GetNumberResolvedDependencies implements Provenance.GetNumberResolvedDependencies.
func (p *BYOBProvenance) GetNumberResolvedDependencies() (int, error) {
return len(p.prov.Predicate.BuildDefinition.ResolvedDependencies), nil
}
// GetSystemParameters implements Provenance.GetSystemParameters.
func (p *BYOBProvenance) GetSystemParameters() (map[string]any, error) {
sysParams, ok := p.prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type")
}
return sysParams, nil
}
@@ -0,0 +1,11 @@
package v1
// containerBasedBuildType is the build type for the container-based builder and is based on BYOB.
var containerBasedBuildType = "https://slsa.dev/container-based-build/v0.1?draft"
// ContainerBasedProvenance is provenance generated by the container-based builder.
type ContainerBasedProvenance struct {
// NOTE: The Container-based builder is not based on BYOB framework but the
// provenanece is identical and can be treated the same (for now).
*BYOBProvenance
}
@@ -1,217 +1,52 @@
package v1
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"time"
intoto "github.com/in-toto/in-toto-golang/in_toto"
slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1"
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/internal/gha/slsaprovenance/iface"
)
// TODO(https://github.com/slsa-framework/slsa-verifier/issues/473): Use a static mapping.
//
//nolint:gochecknoinits
func init() {
slsaprovenance.ProvenanceMap.Store(
slsa1.PredicateSLSAProvenance,
New)
}
type ProvenanceV1 struct {
type intotoAttestation struct {
intoto.StatementHeader
Predicate slsa1.ProvenancePredicate `json:"predicate"`
predicateType string
Predicate slsa1.ProvenancePredicate `json:"predicate"`
}
// This returns a new, empty instance of the v0.2 provenance.
func New() slsaprovenance.Provenance {
return &ProvenanceV1{
predicateType: slsa1.PredicateSLSAProvenance,
}
// ProvenanceV1 represents v1.0 provenance.
type ProvenanceV1 interface {
Predicate() slsa1.ProvenancePredicate
}
func (prov *ProvenanceV1) BuilderID() (string, error) {
return prov.Predicate.RunDetails.Builder.ID, nil
}
func (prov *ProvenanceV1) SourceURI() (string, error) {
// Use resolvedDependencies.
if len(prov.Predicate.BuildDefinition.ResolvedDependencies) == 0 {
return "", fmt.Errorf("%w: empty resovedDependencies", serrors.ErrorInvalidDssePayload)
}
// For now, we use the first resolvedDependency relying on a GHA builder-verifier contract.
uri := prov.Predicate.BuildDefinition.ResolvedDependencies[0].URI
if uri == "" {
return "", fmt.Errorf("%w: empty uri", serrors.ErrorMalformedURI)
}
return uri, nil
}
// TODO(#613): Support for generators.
//
//nolint:unused
func getValidateKey(m map[string]interface{}, key string) (string, error) {
v, ok := m[key]
if !ok {
return "", fmt.Errorf("%w: no %v found", serrors.ErrorInvalidFormat, key)
}
vv, ok := v.(string)
if !ok {
return "", fmt.Errorf("%w: not a string %v", serrors.ErrorInvalidFormat, v)
}
if vv == "" {
return "", fmt.Errorf("%w: empty %v", serrors.ErrorInvalidFormat, key)
}
return vv, nil
}
// TODO(#613): Support for generators.
//
//nolint:unused
func (prov *ProvenanceV1) generatorTriggerInfo() (string, string, string, error) {
// See https://github.com/slsa-framework/github-actions-buildtypes/blob/main/workflow/v1/example.json#L16-L19.
extParams, ok := prov.Predicate.BuildDefinition.ExternalParameters.(map[string]interface{})
if !ok {
return "", "", "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "external parameters type")
}
workflow, ok := extParams["workflow"]
if !ok {
return "", "", "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "external parameters workflow")
}
workflowMap, ok := workflow.(map[string]interface{})
if !ok {
return "", "", "", fmt.Errorf("%w: %s, type %T", serrors.ErrorInvalidDssePayload, "not a map of interface{}", workflow)
}
ref, err := getValidateKey(workflowMap, "ref")
if err != nil {
return "", "", "", fmt.Errorf("%w: %v", serrors.ErrorMalformedURI, err)
}
repository, err := getValidateKey(workflowMap, "repository")
if err != nil {
return "", "", "", fmt.Errorf("%w: %v", serrors.ErrorMalformedURI, err)
// New returns a new Provenance object based on the payload.
func New(payload []byte) (iface.Provenance, error) {
// Strict unmarshal.
// NOTE: this supports extensions because they are
// only used as part of interface{}-defined fields.
dec := json.NewDecoder(bytes.NewReader(payload))
dec.DisallowUnknownFields()
a := &intotoAttestation{}
if err := dec.Decode(a); err != nil {
return nil, err
}
switch a.Predicate.BuildDefinition.BuildType {
case byobBuildType:
return &BYOBProvenance{
prov: a,
}, nil
case containerBasedBuildType:
return &ContainerBasedProvenance{
BYOBProvenance: &BYOBProvenance{
prov: a,
},
}, nil
default:
return nil, fmt.Errorf("%w: unknown buildType: %q", serrors.ErrorInvalidDssePayload, a.Predicate.BuildDefinition.BuildType)
}
path, err := getValidateKey(workflowMap, "path")
if err != nil {
return "", "", "", err
}
return repository, ref, path, nil
}
func (prov *ProvenanceV1) builderTriggerInfo() (string, string, string, error) {
sysParams, ok := prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
if !ok {
return "", "", "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "internal parameters type")
}
if _, exists := sysParams["GITHUB_WORKFLOW_REF"]; !exists {
return "", "", "", fmt.Errorf("%w: GITHUB_WORKFLOW_REF", serrors.ErrorNotPresent)
}
workflowRef, err := slsaprovenance.GetAsString(sysParams, "GITHUB_WORKFLOW_REF")
if err != nil {
return "", "", "", err
}
parts := strings.Split(workflowRef, "@")
if len(parts) != 2 {
return "", "", "", fmt.Errorf("%w: ref: %s", serrors.ErrorInvalidFormat, workflowRef)
}
repoAndPath := parts[0]
ref := parts[1]
parts = strings.Split(repoAndPath, "/")
if len(parts) < 2 {
return "", "", "", fmt.Errorf("%w: rep and path: %s", serrors.ErrorInvalidFormat, repoAndPath)
}
repo := strings.Join(parts[:2], "/")
path := strings.Join(parts[2:], "/")
return fmt.Sprintf("git+https://github.com/%s", repo), ref, path, nil
}
func (prov *ProvenanceV1) triggerInfo() (string, string, string, error) {
// TODO(#613): Support for generators.
return prov.builderTriggerInfo()
}
func (prov *ProvenanceV1) TriggerURI() (string, error) {
repository, ref, _, err := prov.triggerInfo()
if err != nil {
return "", err
}
if repository == "" || ref == "" {
return "", fmt.Errorf("%w: repository or ref is empty", serrors.ErrorMalformedURI)
}
return fmt.Sprintf("%s@%s", repository, ref), nil
}
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.
sysParams, ok := prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "internal parameters type")
}
return slsaprovenance.GetBranch(sysParams, prov.predicateType)
}
func (prov *ProvenanceV1) GetTag() (string, error) {
sysParams, ok := prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type")
}
return slsaprovenance.GetTag(sysParams, prov.predicateType)
}
func (prov *ProvenanceV1) GetWorkflowInputs() (map[string]interface{}, error) {
sysParams, ok := prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type")
}
return slsaprovenance.GetWorkflowInputs(sysParams, prov.predicateType)
}
func (prov *ProvenanceV1) GetBuildTriggerPath() (string, error) {
_, _, path, err := prov.triggerInfo()
if err != nil {
return "", err
}
return path, nil
}
func (prov *ProvenanceV1) GetBuildInvocationID() (string, error) {
return prov.Predicate.RunDetails.BuildMetadata.InvocationID, nil
}
func (prov *ProvenanceV1) GetBuildStartTime() (*time.Time, error) {
return prov.Predicate.RunDetails.BuildMetadata.StartedOn, nil
}
func (prov *ProvenanceV1) GetBuildFinishTime() (*time.Time, error) {
return prov.Predicate.RunDetails.BuildMetadata.FinishedOn, nil
}
func (prov *ProvenanceV1) GetNumberResolvedDependencies() (int, error) {
return len(prov.Predicate.BuildDefinition.ResolvedDependencies), nil
}
func (prov *ProvenanceV1) GetSystemParameters() (map[string]any, error) {
sysParams, ok := prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type")
}
return sysParams, nil
}