diff --git a/verifiers/internal/gha/provenance.go b/verifiers/internal/gha/provenance.go index 3b542ee7f..bc9e8381a 100644 --- a/verifiers/internal/gha/provenance.go +++ b/verifiers/internal/gha/provenance.go @@ -277,6 +277,19 @@ func VerifyNpmPackageProvenance(env *dsselib.Envelope, workflow *WorkflowIdentit return nil } +func isValidDelegatorBuilderID(prov slsaprovenance.Provenance) error { + // Verify the TRW was referenced at a proper tag by the user. + id, err := prov.BuilderID() + if err != nil { + return err + } + parts := strings.Split(id, "@") + if len(parts) != 2 { + return fmt.Errorf("%w: %s", serrors.ErrorInvalidBuilderID, id) + } + return utils.IsValidBuilderTag(parts[1], false) +} + func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceOpts, byob bool, ) error { prov, err := slsaprovenance.ProvenanceFromEnvelope(env) @@ -286,19 +299,9 @@ func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceO // Verify Builder ID. if byob { - // Verify the TRW was referenced at a proper tag by the user. - id, err := prov.BuilderID() - if err != nil { - return err - } - parts := strings.Split(id, "@") - if len(parts) != 2 { - return fmt.Errorf("%w: %s", serrors.ErrorInvalidBuilderID, id) - } - if err := utils.IsValidBuilderTag(parts[1], false); err != nil { + if err := isValidDelegatorBuilderID(prov); err != nil { return err } - // Note: `provenanceOpts.ExpectedBuilderID` is provided by the user. if err := verifyBuilderIDLooseMatch(prov, provenanceOpts.ExpectedBuilderID); err != nil { return err diff --git a/verifiers/internal/gha/provenance_test.go b/verifiers/internal/gha/provenance_test.go index a850775db..aabde4171 100644 --- a/verifiers/internal/gha/provenance_test.go +++ b/verifiers/internal/gha/provenance_test.go @@ -382,6 +382,101 @@ func Test_verifySourceURI(t *testing.T) { } } +func Test_isValidDelegatorBuilderID(t *testing.T) { + t.Parallel() + tests := []struct { + name string + prov *intoto.ProvenanceStatement + err error + }{ + { + name: "no @", + prov: &intoto.ProvenanceStatement{ + Predicate: slsa02.ProvenancePredicate{ + Builder: slsacommon.ProvenanceBuilder{ + ID: "some/builderID", + }, + }, + }, + err: serrors.ErrorInvalidBuilderID, + }, + { + name: "invalid ref", + prov: &intoto.ProvenanceStatement{ + Predicate: slsa02.ProvenancePredicate{ + Builder: slsacommon.ProvenanceBuilder{ + ID: "some/builderID@v1.2.3", + }, + }, + }, + err: serrors.ErrorInvalidRef, + }, + { + name: "invalid ref not tag", + prov: &intoto.ProvenanceStatement{ + Predicate: slsa02.ProvenancePredicate{ + Builder: slsacommon.ProvenanceBuilder{ + ID: "some/builderID@refs/head/v1.2.3", + }, + }, + }, + err: serrors.ErrorInvalidRef, + }, + { + name: "invalid ref not full semver", + prov: &intoto.ProvenanceStatement{ + Predicate: slsa02.ProvenancePredicate{ + Builder: slsacommon.ProvenanceBuilder{ + ID: "some/builderID@refs/heads/v1.2", + }, + }, + }, + err: serrors.ErrorInvalidRef, + }, + { + name: "valid builder", + prov: &intoto.ProvenanceStatement{ + Predicate: slsa02.ProvenancePredicate{ + Builder: slsacommon.ProvenanceBuilder{ + ID: "some/builderID@refs/tags/v1.2.3", + }, + }, + }, + }, + } + for _, tt := range tests { + tt := tt // Re-initializing variable so it is not changed while executing the closure below + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + prov := &v02.ProvenanceV02{ + ProvenanceStatement: tt.prov, + } + + err := isValidDelegatorBuilderID(prov) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + + // Update to v1 SLSA provenance. + prov1 := &v1.ProvenanceV1{ + Predicate: slsa1.ProvenancePredicate{ + RunDetails: slsa1.ProvenanceRunDetails{ + Builder: slsa1.Builder{ + ID: tt.prov.Predicate.Builder.ID, + }, + }, + }, + } + + err = isValidDelegatorBuilderID(prov1) + if !errCmp(err, tt.err) { + t.Errorf(cmp.Diff(err, tt.err)) + } + }) + } +} + func Test_verifyBuilderIDExactMatch(t *testing.T) { t.Parallel() tests := []struct {