Skip to content

Commit fba178e

Browse files
authored
feat: Use env variable to retrieve trigger workflow (#615)
* update Signed-off-by: laurentsimon <[email protected]> * update Signed-off-by: laurentsimon <[email protected]> * update Signed-off-by: laurentsimon <[email protected]> * update Signed-off-by: laurentsimon <[email protected]> * update Signed-off-by: laurentsimon <[email protected]> * update Signed-off-by: laurentsimon <[email protected]> --------- Signed-off-by: laurentsimon <[email protected]>
1 parent ba32c70 commit fba178e

File tree

6 files changed

+114
-58
lines changed

6 files changed

+114
-58
lines changed

errors/errors.go

+1
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ var (
4040
ErrorRekorPubKey = errors.New("error retrieving Rekor public keys")
4141
ErrorInvalidPackageName = errors.New("invalid package name")
4242
ErrorInvalidSubject = errors.New("invalid subject")
43+
ErrorNotPresent = errors.New("not present")
4344
)

verifiers/internal/gha/builder.go

+1
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ func GetWorkflowInfoFromCertificate(cert *x509.Certificate) (*WorkflowIdentity,
454454
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidFormat, cert.URIs[0].Path)
455455
}
456456
// Remove the starting '/'.
457+
// NOTE: The Path has the following structure: repo/name/path/to/workflow.yml@ref.
457458
subjectWorkflowRef := cert.URIs[0].Path[1:]
458459

459460
var pSubjectSha1, pSourceID, pSourceRef, pSourceOwnerID, pBuildConfigPath, pRunID *string

verifiers/internal/gha/provenance_forgeable.go

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package gha
22

33
import (
4+
"errors"
45
"fmt"
56
"strings"
67

@@ -86,8 +87,14 @@ func verifySubjectDigestName(prov slsaprovenance.Provenance, digestName string)
8687
func verifyBuildConfig(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
8788
triggerPath, err := prov.GetBuildTriggerPath()
8889
if err != nil {
90+
// If the field is not available in the provenance,
91+
// we can safely skip the verification against the certificate.
92+
if errors.Is(err, serrors.ErrorNotPresent) {
93+
return nil
94+
}
8995
return err
9096
}
97+
9198
return equalCertificateValue(workflow.BuildConfigPath, triggerPath, "trigger workflow")
9299
}
93100

@@ -268,6 +275,7 @@ func verifySystemParameters(prov slsaprovenance.Provenance, workflow *WorkflowId
268275
"GITHUB_WORKFLOW_REF": true,
269276
"GITHUB_WORKFLOW_SHA": true,
270277
}
278+
271279
for k := range sysParams {
272280
if !supportedNames[k] {
273281
return fmt.Errorf("%w: unknown '%s' parameter", serrors.ErrorMismatchCertificate, k)
@@ -374,6 +382,7 @@ func equalCertificateValue(expected *string, actual, logName string) error {
374382
return fmt.Errorf("%w: empty certificate value to verify '%s'",
375383
serrors.ErrorMismatchCertificate, logName)
376384
}
385+
377386
if actual != *expected {
378387
return fmt.Errorf("%w: %s: '%s' != '%s'", serrors.ErrorMismatchCertificate,
379388
logName, actual, *expected)

verifiers/internal/gha/provenance_forgeable_test.go

+42-24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package gha
22

33
import (
4+
"fmt"
45
"testing"
56
"time"
67

@@ -143,10 +144,8 @@ func Test_verifyBuildConfig(t *testing.T) {
143144
prov1 := &slsav10.ProvenanceV1{
144145
Predicate: intotov1.ProvenancePredicate{
145146
BuildDefinition: intotov1.ProvenanceBuildDefinition{
146-
ExternalParameters: map[string]interface{}{
147-
"workflow": map[string]string{
148-
"path": tt.path,
149-
},
147+
InternalParameters: map[string]interface{}{
148+
"GITHUB_WORKFLOW_REF": fmt.Sprintf("some/repo/%s@some-ref", tt.path),
150149
},
151150
},
152151
},
@@ -1074,7 +1073,7 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
10741073
expectedWorkflow := WorkflowIdentity{
10751074
BuildTrigger: "workflow_dispatch",
10761075
BuildConfigPath: asStringPointer("release/workflow/path"),
1077-
SubjectWorkflowRef: "path/to/trusted-builder@subject-ref",
1076+
SubjectWorkflowRef: "repo/name/release/workflow/path@subject-ref",
10781077
SubjectSha1: asStringPointer("subject-sha"),
10791078
SourceRepository: "repo/name",
10801079
SourceRef: asStringPointer("source-ref"),
@@ -1089,7 +1088,7 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
10891088
numberResolvedDependencies int
10901089
workflowTriggerPath string
10911090
environment map[string]interface{}
1092-
workflow WorkflowIdentity
1091+
certificateIdentity WorkflowIdentity
10931092
err error
10941093
}{
10951094
{
@@ -1110,18 +1109,29 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
11101109
"GITHUB_RUN_ATTEMPT": "run-attempt",
11111110
"GITHUB_RUN_ID": "run-id",
11121111
"GITHUB_SHA": "source-sha",
1113-
"GITHUB_WORKFLOW_REF": "path/to/trusted-builder@subject-ref",
1112+
"GITHUB_WORKFLOW_REF": "repo/name/release/workflow/path@subject-ref",
11141113
"GITHUB_WORKFLOW_SHA": "subject-sha",
11151114
},
1116-
workflow: expectedWorkflow,
1115+
certificateIdentity: expectedWorkflow,
1116+
},
1117+
{
1118+
name: "correct provenance no env",
1119+
subject: []intoto.Subject{
1120+
{
1121+
Digest: intotocommon.DigestSet{"sha512": "abcd"},
1122+
},
1123+
},
1124+
numberResolvedDependencies: 1,
1125+
workflowTriggerPath: "release/workflow/path",
1126+
certificateIdentity: expectedWorkflow,
11171127
},
11181128
{
11191129
name: "unknown field",
11201130
environment: map[string]interface{}{
11211131
"SOMETHING": "workflow_dispatch",
11221132
},
1123-
workflow: expectedWorkflow,
1124-
err: serrors.ErrorMismatchCertificate,
1133+
certificateIdentity: expectedWorkflow,
1134+
err: serrors.ErrorMismatchCertificate,
11251135
},
11261136
{
11271137
name: "too many resolved dependencies",
@@ -1132,7 +1142,7 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
11321142
},
11331143
numberResolvedDependencies: 2,
11341144
workflowTriggerPath: "release/workflow/path",
1135-
workflow: expectedWorkflow,
1145+
certificateIdentity: expectedWorkflow,
11361146
err: serrors.ErrorNonVerifiableClaim,
11371147
},
11381148
{
@@ -1144,7 +1154,7 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
11441154
},
11451155
numberResolvedDependencies: 1,
11461156
workflowTriggerPath: "release/workflow/path",
1147-
workflow: expectedWorkflow,
1157+
certificateIdentity: expectedWorkflow,
11481158
err: serrors.ErrorNonVerifiableClaim,
11491159
},
11501160
{
@@ -1156,8 +1166,20 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
11561166
},
11571167
numberResolvedDependencies: 1,
11581168
workflowTriggerPath: "release/workflow/path2",
1159-
workflow: expectedWorkflow,
1160-
err: serrors.ErrorMismatchCertificate,
1169+
environment: map[string]interface{}{
1170+
"GITHUB_EVENT_NAME": "workflow_dispatch",
1171+
"GITHUB_REF": "source-ref",
1172+
"GITHUB_REPOSITORY": "repo/name",
1173+
"GITHUB_REPOSITORY_ID": "source-id",
1174+
"GITHUB_REPOSITORY_OWNER_ID": "source-owner-id",
1175+
"GITHUB_RUN_ATTEMPT": "run-attempt",
1176+
"GITHUB_RUN_ID": "run-id",
1177+
"GITHUB_SHA": "source-sha",
1178+
"GITHUB_WORKFLOW_REF": "repo/name/release/workflow/path2@subject-ref",
1179+
"GITHUB_WORKFLOW_SHA": "subject-sha",
1180+
},
1181+
certificateIdentity: expectedWorkflow,
1182+
err: serrors.ErrorMismatchCertificate,
11611183
},
11621184
{
11631185
name: "invalid trigger name",
@@ -1171,8 +1193,8 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
11711193
environment: map[string]interface{}{
11721194
"GITHUB_EVENT_NAME": "workflow_dispatch2",
11731195
},
1174-
workflow: expectedWorkflow,
1175-
err: serrors.ErrorMismatchCertificate,
1196+
certificateIdentity: expectedWorkflow,
1197+
err: serrors.ErrorMismatchCertificate,
11761198
},
11771199
}
11781200
for _, tt := range tests {
@@ -1199,7 +1221,7 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
11991221
prov02.Predicate.Materials = make([]intotocommon.ProvenanceMaterial, tt.numberResolvedDependencies)
12001222
}
12011223

1202-
err := verifyProvenanceMatchesCertificate(prov02, &tt.workflow)
1224+
err := verifyProvenanceMatchesCertificate(prov02, &tt.certificateIdentity)
12031225
if !errCmp(err, tt.err) {
12041226
t.Errorf(cmp.Diff(err, tt.err))
12051227
}
@@ -1211,19 +1233,15 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
12111233
Predicate: intotov1.ProvenancePredicate{
12121234
BuildDefinition: intotov1.ProvenanceBuildDefinition{
12131235
InternalParameters: tt.environment,
1214-
ExternalParameters: map[string]interface{}{
1215-
// TODO(#566): verify fields for v1.0 provenance.
1216-
"workflow": map[string]string{
1217-
"path": tt.workflowTriggerPath,
1218-
},
1219-
},
1236+
// TODO(#566): verify fields for v1.0 provenance.
12201237
},
12211238
},
12221239
}
1240+
12231241
if tt.numberResolvedDependencies > 0 {
12241242
prov1.Predicate.BuildDefinition.ResolvedDependencies = make([]intotov1.ResourceDescriptor, tt.numberResolvedDependencies)
12251243
}
1226-
err = verifyProvenanceMatchesCertificate(prov1, &tt.workflow)
1244+
err = verifyProvenanceMatchesCertificate(prov1, &tt.certificateIdentity)
12271245
if !errCmp(err, tt.err) {
12281246
t.Errorf(cmp.Diff(err, tt.err))
12291247
}

verifiers/internal/gha/provenance_test.go

+11-12
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,6 @@ func Test_verifySourceURI(t *testing.T) {
187187
expectedSourceURI string
188188
allowNoMaterialRef bool
189189
err error
190-
// v1 provenance does not include materials
191-
skipv1 bool
192190
}{
193191
{
194192
name: "source has no @",
@@ -339,10 +337,6 @@ func Test_verifySourceURI(t *testing.T) {
339337
t.Errorf(cmp.Diff(err, tt.err))
340338
}
341339

342-
if tt.skipv1 {
343-
return
344-
}
345-
346340
// Update to v1 SLSA provenance.
347341
var ref, repository string
348342
a := strings.Split(tt.provTriggerURI, "@")
@@ -356,13 +350,18 @@ func Test_verifySourceURI(t *testing.T) {
356350
prov1 := &v1.ProvenanceV1{
357351
Predicate: slsa1.ProvenancePredicate{
358352
BuildDefinition: slsa1.ProvenanceBuildDefinition{
359-
ExternalParameters: map[string]interface{}{
360-
"workflow": map[string]interface{}{
361-
"ref": ref,
362-
"repository": repository,
363-
"path": "some/path",
364-
},
353+
InternalParameters: map[string]interface{}{
354+
"GITHUB_WORKFLOW_REF": fmt.Sprintf("%s/some/path@%s",
355+
strings.TrimPrefix(strings.TrimPrefix(repository, "git+"), "https://github.com/"), ref),
365356
},
357+
/// TODO(#613): Support generators.
358+
// ExternalParameters: map[string]interface{}{
359+
// "workflow": map[string]interface{}{
360+
// "ref": ref,
361+
// "repository": repository,
362+
// "path": "some/path",
363+
// },
364+
// },
366365
ResolvedDependencies: []slsa1.ResourceDescriptor{
367366
{
368367
URI: tt.provMaterialsURI,

verifiers/internal/gha/slsaprovenance/v1.0/provenance.go

+50-22
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package v1
22

33
import (
44
"fmt"
5+
"strings"
56
"time"
67

78
intoto "github.com/in-toto/in-toto-golang/in_toto"
@@ -48,6 +49,9 @@ func (prov *ProvenanceV1) SourceURI() (string, error) {
4849
return uri, nil
4950
}
5051

52+
// TODO(#613): Support for generators.
53+
//
54+
//nolint:unused
5155
func getValidateKey(m map[string]interface{}, key string) (string, error) {
5256
v, ok := m[key]
5357
if !ok {
@@ -63,7 +67,10 @@ func getValidateKey(m map[string]interface{}, key string) (string, error) {
6367
return vv, nil
6468
}
6569

66-
func (prov *ProvenanceV1) triggerInfo() (string, string, string, error) {
70+
// TODO(#613): Support for generators.
71+
//
72+
//nolint:unused
73+
func (prov *ProvenanceV1) generatorTriggerInfo() (string, string, string, error) {
6774
// See https://github.com/slsa-framework/github-actions-buildtypes/blob/main/workflow/v1/example.json#L16-L19.
6875
extParams, ok := prov.Predicate.BuildDefinition.ExternalParameters.(map[string]interface{})
6976
if !ok {
@@ -92,6 +99,43 @@ func (prov *ProvenanceV1) triggerInfo() (string, string, string, error) {
9299
return repository, ref, path, nil
93100
}
94101

102+
func (prov *ProvenanceV1) builderTriggerInfo() (string, string, string, error) {
103+
sysParams, ok := prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
104+
if !ok {
105+
return "", "", "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "internal parameters type")
106+
}
107+
108+
if _, exists := sysParams["GITHUB_WORKFLOW_REF"]; !exists {
109+
return "", "", "", fmt.Errorf("%w: GITHUB_WORKFLOW_REF", serrors.ErrorNotPresent)
110+
}
111+
112+
workflowRef, err := slsaprovenance.GetAsString(sysParams, "GITHUB_WORKFLOW_REF")
113+
if err != nil {
114+
return "", "", "", err
115+
}
116+
117+
parts := strings.Split(workflowRef, "@")
118+
if len(parts) != 2 {
119+
return "", "", "", fmt.Errorf("%w: ref: %s", serrors.ErrorInvalidFormat, workflowRef)
120+
}
121+
repoAndPath := parts[0]
122+
ref := parts[1]
123+
124+
parts = strings.Split(repoAndPath, "/")
125+
if len(parts) < 2 {
126+
return "", "", "", fmt.Errorf("%w: rep and path: %s", serrors.ErrorInvalidFormat, repoAndPath)
127+
}
128+
129+
repo := strings.Join(parts[:2], "/")
130+
path := strings.Join(parts[2:], "/")
131+
return fmt.Sprintf("git+https://github.com/%s", repo), ref, path, nil
132+
}
133+
134+
func (prov *ProvenanceV1) triggerInfo() (string, string, string, error) {
135+
// TODO(#613): Support for generators.
136+
return prov.builderTriggerInfo()
137+
}
138+
95139
func (prov *ProvenanceV1) TriggerURI() (string, error) {
96140
repository, ref, _, err := prov.triggerInfo()
97141
if err != nil {
@@ -115,7 +159,7 @@ func (prov *ProvenanceV1) GetBranch() (string, error) {
115159
// TODO(https://github.com/slsa-framework/slsa-verifier/issues/472): Add GetBranch() support.
116160
sysParams, ok := prov.Predicate.BuildDefinition.InternalParameters.(map[string]interface{})
117161
if !ok {
118-
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type")
162+
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "internal parameters type")
119163
}
120164

121165
return slsaprovenance.GetBranch(sysParams, prov.predicateType)
@@ -137,29 +181,13 @@ func (prov *ProvenanceV1) GetWorkflowInputs() (map[string]interface{}, error) {
137181
return slsaprovenance.GetWorkflowInputs(sysParams, prov.predicateType)
138182
}
139183

140-
// TODO(https://github.com/slsa-framework/slsa-verifier/issues/566):
141-
// verify the ref and repo as well.
142184
func (prov *ProvenanceV1) GetBuildTriggerPath() (string, error) {
143-
sysParams, ok := prov.Predicate.BuildDefinition.ExternalParameters.(map[string]interface{})
144-
if !ok {
145-
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "system parameters type")
146-
}
147-
148-
w, ok := sysParams["workflow"]
149-
if !ok {
150-
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "workflow parameters type")
151-
}
152-
153-
wMap, ok := w.(map[string]string)
154-
if !ok {
155-
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "workflow not a map")
185+
_, _, path, err := prov.triggerInfo()
186+
if err != nil {
187+
return "", err
156188
}
157189

158-
v, ok := wMap["path"]
159-
if !ok {
160-
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "no path entry on workflow")
161-
}
162-
return v, nil
190+
return path, nil
163191
}
164192

165193
func (prov *ProvenanceV1) GetBuildInvocationID() (string, error) {

0 commit comments

Comments
 (0)