Skip to content

Commit

Permalink
feat: Use env variable to retrieve trigger workflow (#615)
Browse files Browse the repository at this point in the history
* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

* update

Signed-off-by: laurentsimon <laurentsimon@google.com>

---------

Signed-off-by: laurentsimon <laurentsimon@google.com>
  • Loading branch information
laurentsimon committed May 25, 2023
1 parent ba32c70 commit fba178e
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 58 deletions.
1 change: 1 addition & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ var (
ErrorRekorPubKey = errors.New("error retrieving Rekor public keys")
ErrorInvalidPackageName = errors.New("invalid package name")
ErrorInvalidSubject = errors.New("invalid subject")
ErrorNotPresent = errors.New("not present")
)
1 change: 1 addition & 0 deletions verifiers/internal/gha/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ func GetWorkflowInfoFromCertificate(cert *x509.Certificate) (*WorkflowIdentity,
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidFormat, cert.URIs[0].Path)
}
// Remove the starting '/'.
// NOTE: The Path has the following structure: repo/name/path/to/workflow.yml@ref.
subjectWorkflowRef := cert.URIs[0].Path[1:]

var pSubjectSha1, pSourceID, pSourceRef, pSourceOwnerID, pBuildConfigPath, pRunID *string
Expand Down
9 changes: 9 additions & 0 deletions verifiers/internal/gha/provenance_forgeable.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gha

import (
"errors"
"fmt"
"strings"

Expand Down Expand Up @@ -86,8 +87,14 @@ func verifySubjectDigestName(prov slsaprovenance.Provenance, digestName string)
func verifyBuildConfig(prov slsaprovenance.Provenance, workflow *WorkflowIdentity) error {
triggerPath, err := prov.GetBuildTriggerPath()
if err != nil {
// If the field is not available in the provenance,
// we can safely skip the verification against the certificate.
if errors.Is(err, serrors.ErrorNotPresent) {
return nil
}
return err
}

return equalCertificateValue(workflow.BuildConfigPath, triggerPath, "trigger workflow")
}

Expand Down Expand Up @@ -268,6 +275,7 @@ func verifySystemParameters(prov slsaprovenance.Provenance, workflow *WorkflowId
"GITHUB_WORKFLOW_REF": true,
"GITHUB_WORKFLOW_SHA": true,
}

for k := range sysParams {
if !supportedNames[k] {
return fmt.Errorf("%w: unknown '%s' parameter", serrors.ErrorMismatchCertificate, k)
Expand Down Expand Up @@ -374,6 +382,7 @@ func equalCertificateValue(expected *string, actual, logName string) error {
return fmt.Errorf("%w: empty certificate value to verify '%s'",
serrors.ErrorMismatchCertificate, logName)
}

if actual != *expected {
return fmt.Errorf("%w: %s: '%s' != '%s'", serrors.ErrorMismatchCertificate,
logName, actual, *expected)
Expand Down
66 changes: 42 additions & 24 deletions verifiers/internal/gha/provenance_forgeable_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gha

import (
"fmt"
"testing"
"time"

Expand Down Expand Up @@ -143,10 +144,8 @@ func Test_verifyBuildConfig(t *testing.T) {
prov1 := &slsav10.ProvenanceV1{
Predicate: intotov1.ProvenancePredicate{
BuildDefinition: intotov1.ProvenanceBuildDefinition{
ExternalParameters: map[string]interface{}{
"workflow": map[string]string{
"path": tt.path,
},
InternalParameters: map[string]interface{}{
"GITHUB_WORKFLOW_REF": fmt.Sprintf("some/repo/%s@some-ref", tt.path),
},
},
},
Expand Down Expand Up @@ -1074,7 +1073,7 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
expectedWorkflow := WorkflowIdentity{
BuildTrigger: "workflow_dispatch",
BuildConfigPath: asStringPointer("release/workflow/path"),
SubjectWorkflowRef: "path/to/trusted-builder@subject-ref",
SubjectWorkflowRef: "repo/name/release/workflow/path@subject-ref",
SubjectSha1: asStringPointer("subject-sha"),
SourceRepository: "repo/name",
SourceRef: asStringPointer("source-ref"),
Expand All @@ -1089,7 +1088,7 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
numberResolvedDependencies int
workflowTriggerPath string
environment map[string]interface{}
workflow WorkflowIdentity
certificateIdentity WorkflowIdentity
err error
}{
{
Expand All @@ -1110,18 +1109,29 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
"GITHUB_RUN_ATTEMPT": "run-attempt",
"GITHUB_RUN_ID": "run-id",
"GITHUB_SHA": "source-sha",
"GITHUB_WORKFLOW_REF": "path/to/trusted-builder@subject-ref",
"GITHUB_WORKFLOW_REF": "repo/name/release/workflow/path@subject-ref",
"GITHUB_WORKFLOW_SHA": "subject-sha",
},
workflow: expectedWorkflow,
certificateIdentity: expectedWorkflow,
},
{
name: "correct provenance no env",
subject: []intoto.Subject{
{
Digest: intotocommon.DigestSet{"sha512": "abcd"},
},
},
numberResolvedDependencies: 1,
workflowTriggerPath: "release/workflow/path",
certificateIdentity: expectedWorkflow,
},
{
name: "unknown field",
environment: map[string]interface{}{
"SOMETHING": "workflow_dispatch",
},
workflow: expectedWorkflow,
err: serrors.ErrorMismatchCertificate,
certificateIdentity: expectedWorkflow,
err: serrors.ErrorMismatchCertificate,
},
{
name: "too many resolved dependencies",
Expand All @@ -1132,7 +1142,7 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
},
numberResolvedDependencies: 2,
workflowTriggerPath: "release/workflow/path",
workflow: expectedWorkflow,
certificateIdentity: expectedWorkflow,
err: serrors.ErrorNonVerifiableClaim,
},
{
Expand All @@ -1144,7 +1154,7 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
},
numberResolvedDependencies: 1,
workflowTriggerPath: "release/workflow/path",
workflow: expectedWorkflow,
certificateIdentity: expectedWorkflow,
err: serrors.ErrorNonVerifiableClaim,
},
{
Expand All @@ -1156,8 +1166,20 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
},
numberResolvedDependencies: 1,
workflowTriggerPath: "release/workflow/path2",
workflow: expectedWorkflow,
err: serrors.ErrorMismatchCertificate,
environment: map[string]interface{}{
"GITHUB_EVENT_NAME": "workflow_dispatch",
"GITHUB_REF": "source-ref",
"GITHUB_REPOSITORY": "repo/name",
"GITHUB_REPOSITORY_ID": "source-id",
"GITHUB_REPOSITORY_OWNER_ID": "source-owner-id",
"GITHUB_RUN_ATTEMPT": "run-attempt",
"GITHUB_RUN_ID": "run-id",
"GITHUB_SHA": "source-sha",
"GITHUB_WORKFLOW_REF": "repo/name/release/workflow/path2@subject-ref",
"GITHUB_WORKFLOW_SHA": "subject-sha",
},
certificateIdentity: expectedWorkflow,
err: serrors.ErrorMismatchCertificate,
},
{
name: "invalid trigger name",
Expand All @@ -1171,8 +1193,8 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
environment: map[string]interface{}{
"GITHUB_EVENT_NAME": "workflow_dispatch2",
},
workflow: expectedWorkflow,
err: serrors.ErrorMismatchCertificate,
certificateIdentity: expectedWorkflow,
err: serrors.ErrorMismatchCertificate,
},
}
for _, tt := range tests {
Expand All @@ -1199,7 +1221,7 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
prov02.Predicate.Materials = make([]intotocommon.ProvenanceMaterial, tt.numberResolvedDependencies)
}

err := verifyProvenanceMatchesCertificate(prov02, &tt.workflow)
err := verifyProvenanceMatchesCertificate(prov02, &tt.certificateIdentity)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
Expand All @@ -1211,19 +1233,15 @@ func Test_verifyProvenanceMatchesCertificate(t *testing.T) {
Predicate: intotov1.ProvenancePredicate{
BuildDefinition: intotov1.ProvenanceBuildDefinition{
InternalParameters: tt.environment,
ExternalParameters: map[string]interface{}{
// TODO(#566): verify fields for v1.0 provenance.
"workflow": map[string]string{
"path": tt.workflowTriggerPath,
},
},
// 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.workflow)
err = verifyProvenanceMatchesCertificate(prov1, &tt.certificateIdentity)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}
Expand Down
23 changes: 11 additions & 12 deletions verifiers/internal/gha/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,6 @@ func Test_verifySourceURI(t *testing.T) {
expectedSourceURI string
allowNoMaterialRef bool
err error
// v1 provenance does not include materials
skipv1 bool
}{
{
name: "source has no @",
Expand Down Expand Up @@ -339,10 +337,6 @@ func Test_verifySourceURI(t *testing.T) {
t.Errorf(cmp.Diff(err, tt.err))
}

if tt.skipv1 {
return
}

// Update to v1 SLSA provenance.
var ref, repository string
a := strings.Split(tt.provTriggerURI, "@")
Expand All @@ -356,13 +350,18 @@ func Test_verifySourceURI(t *testing.T) {
prov1 := &v1.ProvenanceV1{
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
ExternalParameters: map[string]interface{}{
"workflow": map[string]interface{}{
"ref": ref,
"repository": repository,
"path": "some/path",
},
InternalParameters: map[string]interface{}{
"GITHUB_WORKFLOW_REF": fmt.Sprintf("%s/some/path@%s",
strings.TrimPrefix(strings.TrimPrefix(repository, "git+"), "https://github.com/"), ref),
},
/// TODO(#613): Support generators.
// ExternalParameters: map[string]interface{}{
// "workflow": map[string]interface{}{
// "ref": ref,
// "repository": repository,
// "path": "some/path",
// },
// },
ResolvedDependencies: []slsa1.ResourceDescriptor{
{
URI: tt.provMaterialsURI,
Expand Down
72 changes: 50 additions & 22 deletions verifiers/internal/gha/slsaprovenance/v1.0/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package v1

import (
"fmt"
"strings"
"time"

intoto "github.com/in-toto/in-toto-golang/in_toto"
Expand Down Expand Up @@ -48,6 +49,9 @@ func (prov *ProvenanceV1) SourceURI() (string, error) {
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 {
Expand All @@ -63,7 +67,10 @@ func getValidateKey(m map[string]interface{}, key string) (string, error) {
return vv, nil
}

func (prov *ProvenanceV1) triggerInfo() (string, string, string, error) {
// 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 {
Expand Down Expand Up @@ -92,6 +99,43 @@ func (prov *ProvenanceV1) triggerInfo() (string, string, string, error) {
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 {
Expand All @@ -115,7 +159,7 @@ 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, "system parameters type")
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "internal parameters type")
}

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

// TODO(https://github.com/slsa-framework/slsa-verifier/issues/566):
// verify the ref and repo as well.
func (prov *ProvenanceV1) GetBuildTriggerPath() (string, error) {
sysParams, ok := 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")
_, _, path, err := prov.triggerInfo()
if err != nil {
return "", err
}

v, ok := wMap["path"]
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "no path entry on workflow")
}
return v, nil
return path, nil
}

func (prov *ProvenanceV1) GetBuildInvocationID() (string, error) {
Expand Down

0 comments on commit fba178e

Please sign in to comment.