Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Use env variable to retrieve trigger workflow #615

Merged
merged 6 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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