feat: Verification for when sha1 is specified in BYOB TRW (#641)

Fixes #600

---------

Signed-off-by: Ian Lewis <ianlewis@google.com>
Signed-off-by: laurentsimon <64505099+laurentsimon@users.noreply.github.com>
Co-authored-by: laurentsimon <64505099+laurentsimon@users.noreply.github.com>
This commit is contained in:
Ian Lewis
2023-07-25 11:29:15 +09:00
committed by GitHub
parent 66ae6bcdf6
commit e7fc7a4621
21 changed files with 1564 additions and 402 deletions

View File

@@ -9,6 +9,7 @@ var (
ErrorMismatchPackageName = errors.New("package name does not match provenance")
ErrorMismatchBuilderID = errors.New("builderID does not match provenance")
ErrorInvalidBuilderID = errors.New("builderID is invalid")
ErrorInvalidBuildType = errors.New("buildType is invalid")
ErrorMismatchSource = errors.New("source used to generate the binary does not match provenance")
ErrorMismatchWorkflowInputs = errors.New("workflow input does not match")
ErrorMalformedURI = errors.New("URI is malformed")

View File

@@ -8,9 +8,10 @@ import (
"strings"
fulcio "github.com/sigstore/fulcio/pkg/certificate"
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/common"
ghacommon "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
)
@@ -27,18 +28,18 @@ var (
)
var defaultArtifactTrustedReusableWorkflows = map[string]bool{
common.GenericGeneratorBuilderID: true,
common.GoBuilderID: true,
common.ContainerBasedBuilderID: true,
ghacommon.GenericGeneratorBuilderID: true,
ghacommon.GoBuilderID: true,
ghacommon.ContainerBasedBuilderID: true,
}
var defaultContainerTrustedReusableWorkflows = map[string]bool{
common.ContainerGeneratorBuilderID: true,
ghacommon.ContainerGeneratorBuilderID: true,
}
var defaultBYOBReusableWorkflows = map[string]bool{
common.GenericDelegatorBuilderID: true,
common.GenericLowPermsDelegatorBuilderID: true,
ghacommon.GenericDelegatorBuilderID: true,
ghacommon.GenericLowPermsDelegatorBuilderID: true,
}
var JReleaserRepository = httpsGithubCom + jReleaserActionRepository

View File

@@ -15,6 +15,7 @@ import (
intoto "github.com/in-toto/in-toto-golang/in_toto"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
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/utils"
@@ -68,6 +69,7 @@ func (b *BundleBytes) UnmarshalJSON(data []byte) error {
type Npm struct {
ctx context.Context
root *TrustedRoot
verifiedBuilderID *utils.TrustedBuilderID
verifiedProvenanceAtt *SignedAttestation
verifiedPublishAtt *SignedAttestation
provenanceAttestation *attestation
@@ -93,8 +95,9 @@ func NpmNew(ctx context.Context, root *TrustedRoot, attestationBytes []byte) (*N
return nil, err
}
return &Npm{
ctx: ctx,
root: root,
ctx: ctx,
root: root,
provenanceAttestation: prov,
publishAttestation: pub,
}, nil
@@ -251,7 +254,7 @@ func (n *Npm) verifyPackageName(name *string) error {
}
// Verify subject name in provenance.
if err := verifyProvenanceSubjectName(n.verifiedProvenanceAtt, *name); err != nil {
if err := verifyProvenanceSubjectName(n.verifiedBuilderID, n.verifiedProvenanceAtt, *name); err != nil {
return err
}
@@ -274,7 +277,7 @@ func (n *Npm) verifyPackageVersion(version *string) error {
}
// Verify subject version in provenance.
if err := verifyProvenanceSubjectVersion(n.verifiedProvenanceAtt, *version); err != nil {
if err := verifyProvenanceSubjectVersion(n.verifiedBuilderID, n.verifiedProvenanceAtt, *version); err != nil {
return err
}
@@ -291,6 +294,25 @@ func (n *Npm) verifyPackageVersion(version *string) error {
return nil
}
func (n *Npm) verifyBuilderID(
provenanceOpts *options.ProvenanceOpts,
builderOpts *options.BuilderOpts,
defaultBuilders map[string]bool,
) (*utils.TrustedBuilderID, error) {
// Verify certificate information.
builder, err := verifyNpmEnvAndCert(
n.ProvenanceEnvelope(),
n.ProvenanceLeafCertificate(),
provenanceOpts, builderOpts,
defaultBuilders,
)
if err != nil {
return nil, err
}
n.verifiedBuilderID = builder
return builder, err
}
func verifyPublishPredicateVersion(att *SignedAttestation, expectedVersion string) error {
_, version, err := getPublishPredicateData(att)
if err != nil {
@@ -336,8 +358,8 @@ func getPublishPredicateData(att *SignedAttestation) (string, string, error) {
return statement.Predicate.Name, statement.Predicate.Version, nil
}
func verifyProvenanceSubjectVersion(att *SignedAttestation, expectedVersion string) error {
subject, err := getSubject(att)
func verifyProvenanceSubjectVersion(b *utils.TrustedBuilderID, att *SignedAttestation, expectedVersion string) error {
subject, err := getSubject(b, att)
if err != nil {
return err
}
@@ -378,15 +400,15 @@ func verifyPublishSubjectName(att *SignedAttestation, expectedName string) error
return verifyName(name, expectedName)
}
func verifyProvenanceSubjectName(att *SignedAttestation, expectedName string) error {
prov, err := slsaprovenance.ProvenanceFromEnvelope(att.Envelope)
func verifyProvenanceSubjectName(b *utils.TrustedBuilderID, att *SignedAttestation, expectedName string) error {
prov, err := slsaprovenance.ProvenanceFromEnvelope(b.Name(), att.Envelope)
if err != nil {
return nil
return fmt.Errorf("reading provenance: %w", err)
}
subjects, err := prov.Subjects()
if err != nil {
return fmt.Errorf("%w", serrors.ErrorInvalidDssePayload)
return fmt.Errorf("%w: %w", serrors.ErrorInvalidDssePayload, err)
}
if len(subjects) != 1 {
return fmt.Errorf("%w: expected 1 subject, got %v", serrors.ErrorInvalidDssePayload, len(subjects))
@@ -443,8 +465,8 @@ func getPackageNameAndVersion(name string) (string, string, error) {
return pkgname, pkgtag, nil
}
func getSubject(att *SignedAttestation) (string, error) {
prov, err := slsaprovenance.ProvenanceFromEnvelope(att.Envelope)
func getSubject(b *utils.TrustedBuilderID, att *SignedAttestation) (string, error) {
prov, err := slsaprovenance.ProvenanceFromEnvelope(b.Name(), att.Envelope)
if err != nil {
return "", err
}

View File

@@ -2,17 +2,20 @@ package gha
import (
"context"
"encoding/base64"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
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/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
)
func Test_verifyName(t *testing.T) {
@@ -81,7 +84,15 @@ func Test_verifyPublishSubjectVersion(t *testing.T) {
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGVUeXBlIjogImh0dHBzOi8vZ2l0aHViLmNvbS9ucG0vYXR0ZXN0YXRpb24vdHJlZS9tYWluL3NwZWNzL3B1Ymxpc2gvdjAuMSIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJuYW1lIjogIkBsYXVyZW50c2ltb24vcHJvdmVuYW5jZS1ucG0tdGVzdCIsCiAgICAidmVyc2lvbiI6ICIxLjAuMCIsCiAgICAicmVnaXN0cnkiOiAiaHR0cHM6Ly9yZWdpc3RyeS5ucG1qcy5vcmciCiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://github.com/npm/attestation/tree/main/specs/publish/v0.1",
"predicate": {
"name": "@laurentsimon/provenance-npm-test",
"version": "1.0.0",
"registry": "https://registry.npmjs.org"
}
}`)),
},
},
version: "1.0.0",
@@ -91,7 +102,15 @@ func Test_verifyPublishSubjectVersion(t *testing.T) {
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGVUeXBlIjogImh0dHBzOi8vZ2l0aHViLmNvbS9ucG0vYXR0ZXN0YXRpb24vdHJlZS9tYWluL3NwZWNzL3B1Ymxpc2gvdjAuMSIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJuYW1lIjogIkBsYXVyZW50c2ltb24vcHJvdmVuYW5jZS1ucG0tdGVzdCIsCiAgICAidmVyc2lvbiI6ICIxLjAuMCIsCiAgICAicmVnaXN0cnkiOiAiaHR0cHM6Ly9yZWdpc3RyeS5ucG1qcy5vcmciCiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://github.com/npm/attestation/tree/main/specs/publish/v0.1",
"predicate": {
"name": "@laurentsimon/provenance-npm-test",
"version": "1.0.0",
"registry": "https://registry.npmjs.org"
}
}`)),
},
},
version: "1.0",
@@ -102,7 +121,15 @@ func Test_verifyPublishSubjectVersion(t *testing.T) {
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGVUeXBlIjogImh0dHBzOi8vZ2l0aHViLmNvbS9ucG0vYXR0ZXN0YXRpb24vdHJlZS9tYWluL3NwZWNzL3B1Ymxpc2gvdjAuMSIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJuYW1lIjogIkBsYXVyZW50c2ltb24vcHJvdmVuYW5jZS1ucG0tdGVzdCIsCiAgICAidmVyc2lvbiI6ICIxLjAuMCIsCiAgICAicmVnaXN0cnkiOiAiaHR0cHM6Ly9yZWdpc3RyeS5ucG1qcy5vcmciCiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://github.com/npm/attestation/tree/main/specs/publish/v0.1",
"predicate": {
"name": "@laurentsimon/provenance-npm-test",
"version": "1.0.0",
"registry": "https://registry.npmjs.org"
}
}`)),
},
},
version: "1.0.1",
@@ -113,7 +140,15 @@ func Test_verifyPublishSubjectVersion(t *testing.T) {
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGVUeXBlIjogImh0dHBzOi8vZ2l0aHViLmNvbS9ucG0vYXR0ZXN0YXRpb24vdHJlZS9tYWluL3NwZWNzL3B1Ymxpc2gvdjAuMSIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJuYW1lIjogIkBsYXVyZW50c2ltb24vcHJvdmVuYW5jZS1ucG0tdGVzdCIsCiAgICAidmVyc2lvbiI6ICIxLjAuMCIsCiAgICAicmVnaXN0cnkiOiAiaHR0cHM6Ly9yZWdpc3RyeS5ucG1qcy5vcmciCiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://github.com/npm/attestation/tree/main/specs/publish/v0.1",
"predicate": {
"name": "@laurentsimon/provenance-npm-test",
"version": "1.0.0",
"registry": "https://registry.npmjs.org"
}
}`)),
},
},
version: "1.1.0",
@@ -124,7 +159,15 @@ func Test_verifyPublishSubjectVersion(t *testing.T) {
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJwcmVkaWNhdGVUeXBlIjogImh0dHBzOi8vZ2l0aHViLmNvbS9ucG0vYXR0ZXN0YXRpb24vdHJlZS9tYWluL3NwZWNzL3B1Ymxpc2gvdjAuMSIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJuYW1lIjogIkBsYXVyZW50c2ltb24vcHJvdmVuYW5jZS1ucG0tdGVzdCIsCiAgICAidmVyc2lvbiI6ICIxLjAuMCIsCiAgICAicmVnaXN0cnkiOiAiaHR0cHM6Ly9yZWdpc3RyeS5ucG1qcy5vcmciCiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://github.com/npm/attestation/tree/main/specs/publish/v0.1",
"predicate": {
"name": "@laurentsimon/provenance-npm-test",
"version": "1.0.0",
"registry": "https://registry.npmjs.org"
}
}`)),
},
},
version: "2.0.0",
@@ -138,8 +181,8 @@ func Test_verifyPublishSubjectVersion(t *testing.T) {
err := verifyPublishSubjectVersion(tt.att, tt.version)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error (-want +got): \n%s", diff)
}
})
}
@@ -149,60 +192,151 @@ func Test_verifyProvenanceSubjectVersion(t *testing.T) {
t.Parallel()
tests := []struct {
name string
att *SignedAttestation
version string
err error
name string
builderID string
att *SignedAttestation
version string
err error
}{
{
name: "correct version",
name: "correct version",
builderID: common.NpmCLIHostedBuilderID,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJwa2c6bnBtLyU0MGxhdXJlbnRzaW1vbi9wcm92ZW5hbmNlLW5wbS10ZXN0QDEuMC4wIiwKICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAic2hhNTEyIjogIjI5ZDE5ZjI2MjMzZjQ0NDEzMjg0MTJiMzRmZDczZWQxMDRlY2ZlZjYyZjE0MDk3ODkwY2NjZjc0NTViNTIxYjY1YzVhY2ZmODUxODQ5ZmFhODVjODUzOTVhYTIyZDQwMTQzNmYwMWYzYWZiNjFiMTljNzgwZTkwNmM4OGM3ZjIwIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZFR5cGUiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGkvZ2hhQHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGlAOS41LjAiCiAgICB9CiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "pkg:npm/%40laurentsimon/provenance-npm-test@1.0.0",
"digest": {
"sha512": "29d19f26233f4441328412b34fd73ed104ecfef62f14097890cccf7455b521b65c5acff851849faa85c85395aa22d401436f01f3afb61b19c780e906c88c7f20"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "https://github.com/npm/cli/gha@v1",
"builder": {
"id": "https://github.com/npm/cli@9.5.0"
}
}
}`)),
},
},
version: "1.0.0",
},
{
name: "incorrect subset version",
name: "incorrect subset version",
builderID: common.NpmCLIHostedBuilderID,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJwa2c6bnBtLyU0MGxhdXJlbnRzaW1vbi9wcm92ZW5hbmNlLW5wbS10ZXN0QDEuMC4wIiwKICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAic2hhNTEyIjogIjI5ZDE5ZjI2MjMzZjQ0NDEzMjg0MTJiMzRmZDczZWQxMDRlY2ZlZjYyZjE0MDk3ODkwY2NjZjc0NTViNTIxYjY1YzVhY2ZmODUxODQ5ZmFhODVjODUzOTVhYTIyZDQwMTQzNmYwMWYzYWZiNjFiMTljNzgwZTkwNmM4OGM3ZjIwIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZFR5cGUiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGkvZ2hhQHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGlAOS41LjAiCiAgICB9CiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "pkg:npm/%40laurentsimon/provenance-npm-test@1.0.0",
"digest": {
"sha512": "29d19f26233f4441328412b34fd73ed104ecfef62f14097890cccf7455b521b65c5acff851849faa85c85395aa22d401436f01f3afb61b19c780e906c88c7f20"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "https://github.com/npm/cli/gha@v1",
"builder": {
"id": "https://github.com/npm/cli@9.5.0"
}
}
}`)),
},
},
version: "1.0",
err: serrors.ErrorMismatchPackageVersion,
},
{
name: "incorrect patch version",
name: "incorrect patch version",
builderID: common.NpmCLIHostedBuilderID,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJwa2c6bnBtLyU0MGxhdXJlbnRzaW1vbi9wcm92ZW5hbmNlLW5wbS10ZXN0QDEuMC4wIiwKICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAic2hhNTEyIjogIjI5ZDE5ZjI2MjMzZjQ0NDEzMjg0MTJiMzRmZDczZWQxMDRlY2ZlZjYyZjE0MDk3ODkwY2NjZjc0NTViNTIxYjY1YzVhY2ZmODUxODQ5ZmFhODVjODUzOTVhYTIyZDQwMTQzNmYwMWYzYWZiNjFiMTljNzgwZTkwNmM4OGM3ZjIwIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZFR5cGUiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGkvZ2hhQHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGlAOS41LjAiCiAgICB9CiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "pkg:npm/%40laurentsimon/provenance-npm-test@1.0.0",
"digest": {
"sha512": "29d19f26233f4441328412b34fd73ed104ecfef62f14097890cccf7455b521b65c5acff851849faa85c85395aa22d401436f01f3afb61b19c780e906c88c7f20"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "https://github.com/npm/cli/gha@v1",
"builder": {
"id": "https://github.com/npm/cli@9.5.0"
}
}
}`)),
},
},
version: "1.0.1",
err: serrors.ErrorMismatchPackageVersion,
},
{
name: "incorrect minor version",
name: "incorrect minor version",
builderID: common.NpmCLIHostedBuilderID,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJwa2c6bnBtLyU0MGxhdXJlbnRzaW1vbi9wcm92ZW5hbmNlLW5wbS10ZXN0QDEuMC4wIiwKICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAic2hhNTEyIjogIjI5ZDE5ZjI2MjMzZjQ0NDEzMjg0MTJiMzRmZDczZWQxMDRlY2ZlZjYyZjE0MDk3ODkwY2NjZjc0NTViNTIxYjY1YzVhY2ZmODUxODQ5ZmFhODVjODUzOTVhYTIyZDQwMTQzNmYwMWYzYWZiNjFiMTljNzgwZTkwNmM4OGM3ZjIwIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZFR5cGUiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGkvZ2hhQHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGlAOS41LjAiCiAgICB9CiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "pkg:npm/%40laurentsimon/provenance-npm-test@1.0.0",
"digest": {
"sha512": "29d19f26233f4441328412b34fd73ed104ecfef62f14097890cccf7455b521b65c5acff851849faa85c85395aa22d401436f01f3afb61b19c780e906c88c7f20"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "https://github.com/npm/cli/gha@v1",
"builder": {
"id": "https://github.com/npm/cli@9.5.0"
}
}
}`)),
},
},
version: "1.1.0",
err: serrors.ErrorMismatchPackageVersion,
},
{
name: "incorrect major version",
name: "incorrect major version",
builderID: common.NpmCLIHostedBuilderID,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJwa2c6bnBtLyU0MGxhdXJlbnRzaW1vbi9wcm92ZW5hbmNlLW5wbS10ZXN0QDEuMC4wIiwKICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAic2hhNTEyIjogIjI5ZDE5ZjI2MjMzZjQ0NDEzMjg0MTJiMzRmZDczZWQxMDRlY2ZlZjYyZjE0MDk3ODkwY2NjZjc0NTViNTIxYjY1YzVhY2ZmODUxODQ5ZmFhODVjODUzOTVhYTIyZDQwMTQzNmYwMWYzYWZiNjFiMTljNzgwZTkwNmM4OGM3ZjIwIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZFR5cGUiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGkvZ2hhQHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGlAOS41LjAiCiAgICB9CiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "pkg:npm/%40laurentsimon/provenance-npm-test@1.0.0",
"digest": {
"sha512": "29d19f26233f4441328412b34fd73ed104ecfef62f14097890cccf7455b521b65c5acff851849faa85c85395aa22d401436f01f3afb61b19c780e906c88c7f20"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "https://github.com/npm/cli/gha@v1",
"builder": {
"id": "https://github.com/npm/cli@9.5.0"
}
}
}`)),
},
},
version: "2.0.0",
@@ -213,11 +347,14 @@ func Test_verifyProvenanceSubjectVersion(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()
builderID, err := utils.TrustedBuilderIDNew(tt.builderID, false)
if err != nil {
panic(err)
}
err := verifyProvenanceSubjectVersion(tt.att, tt.version)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
err = verifyProvenanceSubjectVersion(builderID, tt.att, tt.version)
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error (-want +got): \n%s", diff)
}
})
}
@@ -439,49 +576,122 @@ func Test_verifyProvenanceSubjectName(t *testing.T) {
t.Parallel()
tests := []struct {
name string
att *SignedAttestation
subject string
err error
name string
builderID string
att *SignedAttestation
subject string
err error
}{
{
name: "correct name",
name: "correct name",
builderID: common.NpmCLIHostedBuilderID,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJwa2c6bnBtLyU0MGxhdXJlbnRzaW1vbi9wcm92ZW5hbmNlLW5wbS10ZXN0QDEuMC4wIiwKICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAic2hhNTEyIjogIjI5ZDE5ZjI2MjMzZjQ0NDEzMjg0MTJiMzRmZDczZWQxMDRlY2ZlZjYyZjE0MDk3ODkwY2NjZjc0NTViNTIxYjY1YzVhY2ZmODUxODQ5ZmFhODVjODUzOTVhYTIyZDQwMTQzNmYwMWYzYWZiNjFiMTljNzgwZTkwNmM4OGM3ZjIwIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZFR5cGUiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGkvZ2hhQHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGlAOS41LjAiCiAgICB9CiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "pkg:npm/%40laurentsimon/provenance-npm-test@1.0.0",
"digest": {
"sha512": "29d19f26233f4441328412b34fd73ed104ecfef62f14097890cccf7455b521b65c5acff851849faa85c85395aa22d401436f01f3afb61b19c780e906c88c7f20"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "https://github.com/npm/cli/gha@v1",
"builder": {
"id": "https://github.com/npm/cli@9.5.0"
}
}
}`)),
},
},
subject: "@laurentsimon/provenance-npm-test",
},
{
name: "incorrect name",
name: "incorrect name",
builderID: common.NpmCLIHostedBuilderID,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJwa2c6bnBtLyU0MGxhdXJlbnRzaW1vbi9wcm92ZW5hbmNlLW5wbS10ZXN0QDEuMC4wIiwKICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAic2hhNTEyIjogIjI5ZDE5ZjI2MjMzZjQ0NDEzMjg0MTJiMzRmZDczZWQxMDRlY2ZlZjYyZjE0MDk3ODkwY2NjZjc0NTViNTIxYjY1YzVhY2ZmODUxODQ5ZmFhODVjODUzOTVhYTIyZDQwMTQzNmYwMWYzYWZiNjFiMTljNzgwZTkwNmM4OGM3ZjIwIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZFR5cGUiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGkvZ2hhQHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGlAOS41LjAiCiAgICB9CiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "pkg:npm/%40laurentsimon/provenance-npm-test@1.0.0",
"digest": {
"sha512": "29d19f26233f4441328412b34fd73ed104ecfef62f14097890cccf7455b521b65c5acff851849faa85c85395aa22d401436f01f3afb61b19c780e906c88c7f20"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "https://github.com/npm/cli/gha@v1",
"builder": {
"id": "https://github.com/npm/cli@9.5.0"
}
}
}`)),
},
},
subject: "wrong name",
err: serrors.ErrorMismatchPackageName,
},
{
name: "incorrect scope",
name: "incorrect scope",
builderID: common.NpmCLIHostedBuilderID,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJwa2c6bnBtLyU0MGxhdXJlbnRzaW1vbi9wcm92ZW5hbmNlLW5wbS10ZXN0QDEuMC4wIiwKICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAic2hhNTEyIjogIjI5ZDE5ZjI2MjMzZjQ0NDEzMjg0MTJiMzRmZDczZWQxMDRlY2ZlZjYyZjE0MDk3ODkwY2NjZjc0NTViNTIxYjY1YzVhY2ZmODUxODQ5ZmFhODVjODUzOTVhYTIyZDQwMTQzNmYwMWYzYWZiNjFiMTljNzgwZTkwNmM4OGM3ZjIwIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZFR5cGUiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGkvZ2hhQHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGlAOS41LjAiCiAgICB9CiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "pkg:npm/%40laurentsimon/provenance-npm-test@1.0.0",
"digest": {
"sha512": "29d19f26233f4441328412b34fd73ed104ecfef62f14097890cccf7455b521b65c5acff851849faa85c85395aa22d401436f01f3afb61b19c780e906c88c7f20"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "https://github.com/npm/cli/gha@v1",
"builder": {
"id": "https://github.com/npm/cli@9.5.0"
}
}
}`)),
},
},
subject: "laurentsimon/provenance-npm-test",
err: serrors.ErrorMismatchPackageName,
},
{
name: "incorrect with version",
name: "incorrect with version",
builderID: common.NpmCLIHostedBuilderID,
att: &SignedAttestation{
Envelope: &dsselib.Envelope{
PayloadType: "application/vnd.in-toto+json",
Payload: "ewogICJfdHlwZSI6ICJodHRwczovL2luLXRvdG8uaW8vU3RhdGVtZW50L3YwLjEiLAogICJzdWJqZWN0IjogWwogICAgewogICAgICAibmFtZSI6ICJwa2c6bnBtLyU0MGxhdXJlbnRzaW1vbi9wcm92ZW5hbmNlLW5wbS10ZXN0QDEuMC4wIiwKICAgICAgImRpZ2VzdCI6IHsKICAgICAgICAic2hhNTEyIjogIjI5ZDE5ZjI2MjMzZjQ0NDEzMjg0MTJiMzRmZDczZWQxMDRlY2ZlZjYyZjE0MDk3ODkwY2NjZjc0NTViNTIxYjY1YzVhY2ZmODUxODQ5ZmFhODVjODUzOTVhYTIyZDQwMTQzNmYwMWYzYWZiNjFiMTljNzgwZTkwNmM4OGM3ZjIwIgogICAgICB9CiAgICB9CiAgXSwKICAicHJlZGljYXRlVHlwZSI6ICJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjAuMiIsCiAgInByZWRpY2F0ZSI6IHsKICAgICJidWlsZFR5cGUiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGkvZ2hhQHYxIiwKICAgICJidWlsZGVyIjogewogICAgICAiaWQiOiAiaHR0cHM6Ly9naXRodWIuY29tL25wbS9jbGlAOS41LjAiCiAgICB9CiAgfQp9Cg==",
Payload: base64.StdEncoding.EncodeToString([]byte(`{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "pkg:npm/%40laurentsimon/provenance-npm-test@1.0.0",
"digest": {
"sha512": "29d19f26233f4441328412b34fd73ed104ecfef62f14097890cccf7455b521b65c5acff851849faa85c85395aa22d401436f01f3afb61b19c780e906c88c7f20"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildType": "https://github.com/npm/cli/gha@v1",
"builder": {
"id": "https://github.com/npm/cli@9.5.0"
}
}
}`)),
},
},
subject: "@laurentsimon/provenance-npm-test@1.0.0",
@@ -492,11 +702,14 @@ func Test_verifyProvenanceSubjectName(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()
builderID, err := utils.TrustedBuilderIDNew(tt.builderID, false)
if err != nil {
panic(err)
}
err := verifyProvenanceSubjectName(tt.att, tt.subject)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
err = verifyProvenanceSubjectName(builderID, tt.att, tt.subject)
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error (-want +got): \n%s", diff)
}
})
}
@@ -572,6 +785,11 @@ func Test_verifyPackageName(t *testing.T) {
Envelope: env,
}
npm.verifiedBuilderID, err = utils.TrustedBuilderIDNew(common.NpmCLIHostedBuilderID, false)
if err != nil {
panic(err)
}
err = npm.verifyPackageName(&tt.subject)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
@@ -642,6 +860,11 @@ func Test_verifyPackageVersion(t *testing.T) {
Envelope: env,
}
npm.verifiedBuilderID, err = utils.TrustedBuilderIDNew(common.NpmCLIHostedBuilderID, false)
if err != nil {
panic(err)
}
env, err = getEnvelopeFromBundleBytes(npm.publishAttestation.BundleBytes)
if err != nil {
panic(fmt.Errorf("getEnvelopeFromBundleBytes: %w", err))
@@ -886,8 +1109,8 @@ func Test_NpmNew(t *testing.T) {
}
_, err = NpmNew(ctx, trustedRoot, content)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error (-want +got): \n%s", diff)
}
})
}

View File

@@ -77,7 +77,7 @@ func verifyBuilderIDLooseMatch(prov iface.Provenance, expectedBuilderID string)
}
// Verify source URI in provenance statement.
func verifySourceURI(prov iface.Provenance, expectedSourceURI string, allowNoMaterialRef bool) error {
func verifySourceURI(prov iface.Provenance, expectedSourceURI string) error {
source := utils.NormalizeGitURI(expectedSourceURI)
// We expect github.com URIs only.
@@ -97,7 +97,7 @@ func verifySourceURI(prov iface.Provenance, expectedSourceURI string, allowNoMat
return err
}
if triggerURI != source {
return fmt.Errorf("%w: expected source '%s' in configSource.uri, got %q", serrors.ErrorMismatchSource,
return fmt.Errorf("%w: expected trigger %q to match source-uri %q", serrors.ErrorMismatchSource,
source, fullTriggerURI)
}
// We expect the trigger URI to always have a ref.
@@ -116,22 +116,37 @@ func verifySourceURI(prov iface.Provenance, expectedSourceURI string, allowNoMat
return err
}
if sourceURI != source {
return fmt.Errorf("%w: expected source '%s' in material section, got %q", serrors.ErrorMismatchSource,
source, fullSourceURI)
return fmt.Errorf("%w: expected source %q to match source-uri %q", serrors.ErrorMismatchSource,
fullSourceURI, source)
}
buildType, err := prov.BuildType()
if err != nil {
return fmt.Errorf("checking buildType: %v", err)
}
if sourceRef == "" {
if allowNoMaterialRef {
// NOTE: this is an exception for npm packages built before GA,
// see https://github.com/slsa-framework/slsa-verifier/issues/492.
// We don't need to compare the ref since materialSourceURI does not contain it.
// NOTE: this is an exception for npm packages built before GA,
// see https://github.com/slsa-framework/slsa-verifier/issues/492.
// We don't need to compare the ref since materialSourceURI does not contain it.
if buildType == common.NpmCLIBuildTypeV1 {
return nil
}
// NOTE: BYOB builders can build from a different ref than the triggering ref.
// This most often happens when a TRW makes a commit as part of the release process.
// NOTE: Currently only building from a different git sha is supported
// which means the sourceRef is empty. Building from an arbitrary ref
// is currently not supported.
if buildType == common.BYOBBuildTypeV0 {
return nil
}
return fmt.Errorf("%w: missing ref: %q", serrors.ErrorMalformedURI, fullSourceURI)
}
// Last, verify that both fields match. We expect that the trigger URI and
// the source URI match but the ref used to trigger the build and source ref
// could be different.
if fullTriggerURI != fullSourceURI {
return fmt.Errorf("%w: material and config URIs do not match: %q != %q",
return fmt.Errorf("%w: source and trigger URIs do not match: %q != %q",
serrors.ErrorInvalidDssePayload,
fullTriggerURI, fullSourceURI)
}
@@ -191,9 +206,9 @@ func VerifyProvenanceSignature(ctx context.Context, trustedRoot *TrustedRoot,
// VerifyNpmPackageProvenance verifies provenance for an npm package.
func VerifyNpmPackageProvenance(env *dsselib.Envelope, workflow *WorkflowIdentity,
provenanceOpts *options.ProvenanceOpts, isTrustedBuilder bool,
provenanceOpts *options.ProvenanceOpts, trustedBuilderID *utils.TrustedBuilderID, isTrustedBuilder bool,
) error {
prov, err := slsaprovenance.ProvenanceFromEnvelope(env)
prov, err := slsaprovenance.ProvenanceFromEnvelope(trustedBuilderID.Name(), env)
if err != nil {
return err
}
@@ -239,7 +254,7 @@ func VerifyNpmPackageProvenance(env *dsselib.Envelope, workflow *WorkflowIdentit
}
// Also, the GitHub context is not recorded for the default builder.
if err := VerifyProvenanceCommonOptions(prov, provenanceOpts, true); err != nil {
if err := VerifyProvenanceCommonOptions(prov, provenanceOpts); err != nil {
return err
}
@@ -271,9 +286,8 @@ func isValidDelegatorBuilderID(prov iface.Provenance) error {
}
// 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)
func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceOpts, trustedBuilderID *utils.TrustedBuilderID, byob bool) error {
prov, err := slsaprovenance.ProvenanceFromEnvelope(trustedBuilderID.Name(), env)
if err != nil {
return err
}
@@ -295,15 +309,13 @@ func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceO
}
}
return VerifyProvenanceCommonOptions(prov, provenanceOpts, false)
return VerifyProvenanceCommonOptions(prov, provenanceOpts)
}
// VerifyProvenanceCommonOptions verifies the given provenance.
func VerifyProvenanceCommonOptions(prov iface.Provenance, provenanceOpts *options.ProvenanceOpts,
allowNoMaterialRef bool,
) error {
func VerifyProvenanceCommonOptions(prov iface.Provenance, provenanceOpts *options.ProvenanceOpts) error {
// Verify source.
if err := verifySourceURI(prov, provenanceOpts.ExpectedSourceURI, allowNoMaterialRef); err != nil {
if err := verifySourceURI(prov, provenanceOpts.ExpectedSourceURI); err != nil {
return err
}

View File

@@ -6,16 +6,18 @@ 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"
slsacommon "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/common"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/iface"
)
type testProvenance struct {
builderID string
buildType string
sourceURI string
triggerURI string
subjects []intoto.Subject
@@ -31,6 +33,7 @@ type testProvenance struct {
}
func (p *testProvenance) BuilderID() (string, error) { return p.builderID, nil }
func (p *testProvenance) BuildType() (string, error) { return p.buildType, nil }
func (p *testProvenance) SourceURI() (string, error) { return p.sourceURI, nil }
func (p *testProvenance) TriggerURI() (string, error) { return p.triggerURI, nil }
func (p *testProvenance) Subjects() ([]intoto.Subject, error) { return p.subjects, nil }
@@ -75,7 +78,7 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha1": "4506290e2e8feb1f34b27a044f7cc863c830ef6b",
},
},
@@ -90,7 +93,7 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha1": "4506290e2e8feb1f34b27a044f7cc863c830ef6b",
},
},
@@ -111,7 +114,7 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
@@ -125,7 +128,7 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
@@ -138,17 +141,17 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "03e7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha1": "4506290e2e8feb1f34b27a044f7cc863c830ef6b",
},
},
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
@@ -161,17 +164,17 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "03e7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha1": "4506290e2e8feb1f34b27a044f7cc863c830ef6b",
},
},
@@ -184,17 +187,17 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "03e7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha1": "4506290e2e8feb1f34b27a044f7cc863c830ef6b",
},
},
@@ -220,6 +223,7 @@ func Test_verifySourceURI(t *testing.T) {
t.Parallel()
tests := []struct {
name string
provBuildType string
provMaterialsURI string
provTriggerURI string
expectedSourceURI string
@@ -283,14 +287,37 @@ func Test_verifySourceURI(t *testing.T) {
expectedSourceURI: "https://github.com/some/repo",
},
{
name: "match source no git no material ref",
provTriggerURI: "git+https://github.com/some/repo@v1.2.3",
provMaterialsURI: "git+https://github.com/some/repo",
allowNoMaterialRef: true,
expectedSourceURI: "https://github.com/some/repo",
name: "match source no git no material ref (npm)",
provBuildType: common.NpmCLIBuildTypeV1,
provTriggerURI: "git+https://github.com/some/repo@v1.2.3",
provMaterialsURI: "git+https://github.com/some/repo",
expectedSourceURI: "https://github.com/some/repo",
},
{
name: "match source no git no material ref ref not allowed",
name: "mismatch source material ref (npm)",
provBuildType: common.NpmCLIBuildTypeV1,
provTriggerURI: "git+https://github.com/some/repo@v1.2.3",
provMaterialsURI: "git+https://github.com/some/repo@v1.2.4",
expectedSourceURI: "https://github.com/some/repo",
err: serrors.ErrorInvalidDssePayload,
},
{
name: "match source no git no material ref (byob)",
provBuildType: common.BYOBBuildTypeV0,
provTriggerURI: "git+https://github.com/some/repo@v1.2.3",
provMaterialsURI: "git+https://github.com/some/repo",
expectedSourceURI: "https://github.com/some/repo",
},
{
name: "mismatch source material ref (byob)",
provBuildType: common.BYOBBuildTypeV0,
provTriggerURI: "git+https://github.com/some/repo@v1.2.3",
provMaterialsURI: "git+https://github.com/some/repo@v1.2.4",
expectedSourceURI: "https://github.com/some/repo",
err: serrors.ErrorInvalidDssePayload,
},
{
name: "match source no git no material ref",
provTriggerURI: "git+https://github.com/some/repo@v1.2.3",
provMaterialsURI: "git+https://github.com/some/repo",
expectedSourceURI: "https://github.com/some/repo",
@@ -358,11 +385,12 @@ func Test_verifySourceURI(t *testing.T) {
t.Parallel()
prov02 := &testProvenance{
buildType: tt.provBuildType,
sourceURI: tt.provMaterialsURI,
triggerURI: tt.provTriggerURI,
}
err := verifySourceURI(prov02, tt.expectedSourceURI, tt.allowNoMaterialRef)
err := verifySourceURI(prov02, tt.expectedSourceURI)
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err))
}

View File

@@ -0,0 +1,30 @@
package common
var (
// BYOBBuildTypeV0 is the base buildType for BYOB delegated builders.
BYOBBuildTypeV0 = "https://github.com/slsa-framework/slsa-github-generator/delegator-generic@v0"
// ContainerBasedBuildTypeV01Draft is the buildType for the container-based builder.
ContainerBasedBuildTypeV01Draft = "https://slsa.dev/container-based-build/v0.1?draft"
// GoBuilderBuildTypeV1 is the buildType for the Go builder.
GoBuilderBuildTypeV1 = "https://github.com/slsa-framework/slsa-github-generator/go@v1"
// GenericGeneratorBuildTypeV1 is the buildType for the generic generator.
GenericGeneratorBuildTypeV1 = "https://github.com/slsa-framework/slsa-github-generator/generic@v1"
// ContainerGeneratorBuildTypeV1 is the buildType for the container generator.
ContainerGeneratorBuildTypeV1 = "https://github.com/slsa-framework/slsa-github-generator/container@v1"
// NpmCLIBuildTypeV1 is the buildType for provenance generated by the npm cli.
NpmCLIBuildTypeV1 = "https://github.com/npm/cli/gha@v1"
)
// Legacy buildTypes.
var (
// LegacyGoBuilderBuildTypeV1 is a legacy Go builder buildType.
LegacyGoBuilderBuildTypeV1 = "https://github.com/slsa-framework/slsa-github-generator-go@v1"
// LegacyBuilderBuildTypeV1 is a legacy generic build type for slsa-github-generator.
LegacyBuilderBuildTypeV1 = "https://github.com/slsa-framework/slsa-github-generator@v1"
)

View File

@@ -11,6 +11,9 @@ type Provenance interface {
// BuilderID returns the builder id in the predicate.
BuilderID() (string, error)
// BuildType returns the buildType.
BuildType() (string, error)
// SourceURI is the full URI (including tag) of the source material.
SourceURI() (string, error)

View File

@@ -6,7 +6,6 @@ import (
"fmt"
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"
@@ -17,40 +16,43 @@ import (
)
// provenanceConstructor creates a new Provenance instance for the given payload as a json Decoder.
type provenanceConstructor func(payload []byte) (iface.Provenance, error)
type provenanceConstructor func(builderID string, payload []byte) (iface.Provenance, 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,
common.ProvenanceV02Type: slsav02.New,
common.ProvenanceV1Type: slsav1.New,
}
// ProvenanceFromEnvelope returns a Provenance instance for the given DSSE Envelope.
func ProvenanceFromEnvelope(env *dsselib.Envelope) (iface.Provenance, error) {
// ProvenanceFromEnvelope returns a Provenance instance for the given builder
// ID and DSSE Envelope. The builder ID is retrieved from the signing certificate
// rather than from the payload itself in order to support delegated builders.
func ProvenanceFromEnvelope(builderID string, env *dsselib.Envelope) (iface.Provenance, error) {
if env.PayloadType != intoto.PayloadType {
return nil, fmt.Errorf("%w: expected payload type %q, got '%s'",
return nil, fmt.Errorf("%w: expected payload type %q, got %q",
serrors.ErrorInvalidDssePayload, intoto.PayloadType, env.PayloadType)
}
pyld, err := base64.StdEncoding.DecodeString(env.Payload)
if err != nil {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, err.Error())
return nil, fmt.Errorf("%w: %w", serrors.ErrorInvalidDssePayload, err)
}
// Load the in-toto attestation statement header.
pred := intoto.StatementHeader{}
if err := json.Unmarshal(pyld, &pred); err != nil {
return nil, fmt.Errorf("%w: decoding json: %v", serrors.ErrorInvalidDssePayload, err)
return nil, fmt.Errorf("%w: decoding json: %w", serrors.ErrorInvalidDssePayload, err)
}
// Verify the predicate type is one we can handle.
newProv, ok := predicateTypeMap[pred.PredicateType]
if !ok {
return nil, fmt.Errorf("%w: unexpected predicate type '%s'", serrors.ErrorInvalidDssePayload, pred.PredicateType)
return nil, fmt.Errorf("%w: unexpected predicate type %q", serrors.ErrorInvalidDssePayload, pred.PredicateType)
}
prov, err := newProv(pyld)
prov, err := newProv(builderID, pyld)
if err != nil {
return nil, fmt.Errorf("%w: %v", serrors.ErrorInvalidDssePayload, err)
return nil, fmt.Errorf("%w: %w", serrors.ErrorInvalidDssePayload, err)
}
return prov, nil

View File

@@ -12,6 +12,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/common"
slsav1 "github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/v1.0"
)
@@ -26,13 +27,15 @@ func mustJSON(o any) string {
func Test_ProvenanceFromEnvelope(t *testing.T) {
t.Parallel()
tests := []struct {
name string
envelope *dsse.Envelope
path string
err error
name string
builderID string
envelope *dsse.Envelope
path string
err error
}{
{
name: "valid dsse",
name: "valid dsse",
builderID: common.GenericDelegatorBuilderID,
envelope: &dsse.Envelope{
PayloadType: intoto.PayloadType,
Payload: mustJSON(&slsav1.Attestation{
@@ -48,7 +51,8 @@ func Test_ProvenanceFromEnvelope(t *testing.T) {
},
},
{
name: "invalid dsse: not SLSA predicate",
name: "invalid dsse: not SLSA predicate",
builderID: common.GenericDelegatorBuilderID,
envelope: &dsse.Envelope{
PayloadType: intoto.PayloadType,
Payload: mustJSON(&intoto.StatementHeader{
@@ -59,7 +63,8 @@ func Test_ProvenanceFromEnvelope(t *testing.T) {
err: serrors.ErrorInvalidDssePayload,
},
{
name: "invalid dsse: not base64",
name: "invalid dsse: not base64",
builderID: common.GenericDelegatorBuilderID,
envelope: &dsse.Envelope{
PayloadType: intoto.PayloadType,
// NOTE: Not valid base64.
@@ -68,7 +73,8 @@ func Test_ProvenanceFromEnvelope(t *testing.T) {
err: serrors.ErrorInvalidDssePayload,
},
{
name: "invalid dsse: not json",
name: "invalid dsse: not json",
builderID: common.GenericDelegatorBuilderID,
envelope: &dsse.Envelope{
PayloadType: intoto.PayloadType,
// NOTE: Not valid JSON.
@@ -77,7 +83,8 @@ func Test_ProvenanceFromEnvelope(t *testing.T) {
err: serrors.ErrorInvalidDssePayload,
},
{
name: "invalid dsse: not in-toto",
name: "invalid dsse: not in-toto",
builderID: common.GenericDelegatorBuilderID,
envelope: &dsse.Envelope{
// NOTE: Not an in-toto attestation payload type,
PayloadType: "http://github.com/other/payload/type",
@@ -101,7 +108,7 @@ func Test_ProvenanceFromEnvelope(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
_, err := ProvenanceFromEnvelope(tt.envelope)
_, err := ProvenanceFromEnvelope(tt.builderID, tt.envelope)
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Errorf("unexpected error (-want +got):\n%s", diff)
}

View File

@@ -9,8 +9,16 @@ import (
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"
)
func newLegacyBuilderProvenance(a *Attestation) iface.Provenance {
return &provenanceV02{
upperEnv: false,
prov: a,
}
}
// provenanceV02 implements basic logic for SLSA v0.2 provenance.
type provenanceV02 struct {
// upperEnv specifies if environment fields are in uppercase.
@@ -28,6 +36,11 @@ func (p *provenanceV02) BuilderID() (string, error) {
return p.prov.Predicate.Builder.ID, nil
}
// BuildType implements Provenance.BuildType.
func (p *provenanceV02) BuildType() (string, error) {
return p.prov.Predicate.BuildType, nil
}
// SourceURI implements Provenance.SourceURI.
func (p *provenanceV02) SourceURI() (string, error) {
if len(p.prov.Predicate.Materials) == 0 {

View File

@@ -0,0 +1,91 @@
package v02
import (
"fmt"
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"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
)
// byobProvenance is SLSA v0.2 provenance created by a BYOB builder.
type byobProvenance struct {
*provenanceV02
}
func newBYOBProvenance(att *Attestation) iface.Provenance {
return &byobProvenance{
provenanceV02: &provenanceV02{
prov: att,
upperEnv: true,
},
}
}
// GetBranch implements Provenance.GetBranch.
func (p *byobProvenance) GetBranch() (string, error) {
sourceURI, err := p.SourceURI()
if err != nil {
return "", fmt.Errorf("reading source uri: %w", err)
}
// Returns the branch from the source URI if available.
_, ref, err := utils.ParseGitURIAndRef(sourceURI)
if err != nil {
return "", fmt.Errorf("parsing source uri: %w", err)
}
if ref == "" {
return "", fmt.Errorf("%w: unable to get ref for source %q",
serrors.ErrorInvalidDssePayload, sourceURI)
}
refType, _ := utils.ParseGitRef(ref)
switch refType {
case "heads": // branch.
// NOTE: We return the full git ref.
return ref, nil
case "tags":
// NOTE: If the ref type is a tag we want to try to parse out the branch from the tag.
sysParams, ok := p.prov.Predicate.Invocation.Environment.(map[string]interface{})
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "parameters type")
}
return common.GetBranch(sysParams, true)
default:
return "", fmt.Errorf("%w: unknown ref type %q for ref %q",
serrors.ErrorInvalidDssePayload, refType, ref)
}
}
// GetTag implements Provenance.GetTag.
func (p *byobProvenance) GetTag() (string, error) {
sourceURI, err := p.SourceURI()
if err != nil {
return "", fmt.Errorf("reading source uri: %w", err)
}
// Returns the branch from the source URI if available.
_, ref, err := utils.ParseGitURIAndRef(sourceURI)
if err != nil {
return "", fmt.Errorf("parsing source uri: %w", err)
}
if ref == "" {
return "", fmt.Errorf("%w: unable to get ref for source %q",
serrors.ErrorInvalidDssePayload, sourceURI)
}
refType, _ := utils.ParseGitRef(ref)
switch refType {
case "heads": // branch.
return "", nil
case "tags":
// NOTE: We return the full git ref.
return ref, nil
default:
return "", fmt.Errorf("%w: unknown ref type %q for ref %q",
serrors.ErrorInvalidDssePayload, refType, ref)
}
}

View File

@@ -0,0 +1,280 @@
package v02
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
intoto "github.com/in-toto/in-toto-golang/in_toto"
slsacommon "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"
serrors "github.com/slsa-framework/slsa-verifier/v2/errors"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/iface"
)
func Test_byobProvenance_GetBranch(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
prov iface.Provenance
branch string
err error
}{
{
name: "empty provenance",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{},
},
),
err: serrors.ErrorInvalidDssePayload,
},
{
name: "materials uri @ refs/heads/main",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{
Materials: []slsacommon.ProvenanceMaterial{
{
URI: "git+https://github.com/kubernetes/kubernetes@refs/heads/main",
},
},
},
},
),
branch: "refs/heads/main",
},
{
name: "environment GITHUB_REF @ refs/heads/main",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{
Invocation: slsa02.ProvenanceInvocation{
Environment: map[string]interface{}{
"GITHUB_REF_TYPE": "branch",
"GITHUB_REF": "refs/heads/main",
},
},
},
},
),
err: serrors.ErrorInvalidDssePayload,
},
{
name: "materials uri @ refs/tags/v1.0.0",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{
Invocation: slsa02.ProvenanceInvocation{
Environment: map[string]interface{}{
"GITHUB_BASE_REF": "",
"GITHUB_REF_TYPE": "tag",
"GITHUB_REF": "refs/tags/v1.0.0",
"GITHUB_EVENT_NAME": "push",
"GITHUB_EVENT_PAYLOAD": map[string]any{
"base_ref": nil,
},
},
},
Materials: []slsacommon.ProvenanceMaterial{
{
URI: "git+https://github.com/kubernetes/kubernetes@refs/tags/v1.0.0",
},
},
},
},
),
branch: "",
},
{
name: "environment GITHUB_REF @ ref/tags/v1.0.0",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{
Invocation: slsa02.ProvenanceInvocation{
Environment: map[string]interface{}{
"GITHUB_BASE_REF": "",
"GITHUB_REF_TYPE": "tag",
"GITHUB_REF": "refs/tags/v1.0.0",
"GITHUB_EVENT_NAME": "push",
"GITHUB_EVENT_PAYLOAD": map[string]any{
"base_ref": nil,
},
},
},
},
},
),
err: serrors.ErrorInvalidDssePayload,
},
{
name: "materials uri no ref",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{
Invocation: slsa02.ProvenanceInvocation{
Environment: map[string]interface{}{
"GITHUB_REF_TYPE": "branch",
"GITHUB_REF": "refs/heads/main",
},
},
Materials: []slsacommon.ProvenanceMaterial{
{
URI: "git+https://github.com/kubernetes/kubernetes",
},
},
},
},
),
err: serrors.ErrorInvalidDssePayload,
},
}
for i := range testCases {
tt := testCases[i]
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
branch, err := tt.prov.GetBranch()
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error: %v", err)
}
if got, want := branch, tt.branch; got != want {
t.Fatalf("unexpected branch, got: %q, want: %q", got, want)
}
})
}
}
func Test_byobProvenance_GetTag(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
prov iface.Provenance
tag string
err error
}{
{
name: "empty provenance",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{},
},
),
err: serrors.ErrorInvalidDssePayload,
},
{
name: "materials uri @ refs/heads/main",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{
Materials: []slsacommon.ProvenanceMaterial{
{
URI: "git+https://github.com/kubernetes/kubernetes@refs/heads/main",
},
},
},
},
),
tag: "",
},
{
name: "environment GITHUB_REF @ refs/heads/main",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{
Invocation: slsa02.ProvenanceInvocation{
Environment: map[string]interface{}{
"GITHUB_REF_TYPE": "branch",
"GITHUB_REF": "refs/heads/main",
},
},
},
},
),
err: serrors.ErrorInvalidDssePayload,
},
{
name: "materials uri @ refs/tags/v1.0.0",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{
Materials: []slsacommon.ProvenanceMaterial{
{
URI: "git+https://github.com/kubernetes/kubernetes@refs/tags/v1.0.0",
},
},
},
},
),
tag: "refs/tags/v1.0.0",
},
{
name: "environment GITHUB_REF @ ref/tags/v1.0.0",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{
Invocation: slsa02.ProvenanceInvocation{
Environment: map[string]interface{}{
"GITHUB_REF_TYPE": "tag",
"GITHUB_REF": "refs/tags/v1.0.0",
},
},
},
},
),
err: serrors.ErrorInvalidDssePayload,
},
{
name: "materials uri no ref",
prov: newBYOBProvenance(
&Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa02.ProvenancePredicate{
Invocation: slsa02.ProvenanceInvocation{
Environment: map[string]interface{}{
"GITHUB_REF_TYPE": "tag",
"GITHUB_REF": "refs/tags/v1.0.0",
},
},
Materials: []slsacommon.ProvenanceMaterial{
{
URI: "git+https://github.com/kubernetes/kubernetes",
},
},
},
},
),
err: serrors.ErrorInvalidDssePayload,
},
}
for i := range testCases {
tt := testCases[i]
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
tag, err := tt.prov.GetTag()
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error: %v", err)
}
if got, want := tag, tt.tag; got != want {
t.Fatalf("unexpected tag, got: %q, want: %q", got, want)
}
})
}
}

View File

@@ -7,26 +7,12 @@ import (
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"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/iface"
)
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"
)
// Attestation is a SLSA v0.2 in-toto attestation statement.
type Attestation struct {
intoto.StatementHeader
@@ -38,8 +24,29 @@ type ProvenanceV02 interface {
Predicate() slsa02.ProvenancePredicate
}
type provFunc func(*Attestation) iface.Provenance
// buildTypeMap is a map of builder IDs to supported buildTypes.
var buildTypeMap = map[string]map[string]provFunc{
common.GenericDelegatorBuilderID: {common.BYOBBuildTypeV0: newBYOBProvenance},
common.GenericLowPermsDelegatorBuilderID: {common.BYOBBuildTypeV0: newBYOBProvenance},
common.GoBuilderID: {
common.GoBuilderBuildTypeV1: newLegacyBuilderProvenance,
common.LegacyBuilderBuildTypeV1: newLegacyBuilderProvenance,
common.LegacyGoBuilderBuildTypeV1: newLegacyBuilderProvenance,
},
common.GenericGeneratorBuilderID: {common.GenericGeneratorBuildTypeV1: newLegacyBuilderProvenance},
common.ContainerGeneratorBuilderID: {common.ContainerGeneratorBuildTypeV1: newLegacyBuilderProvenance},
common.NpmCLILegacyBuilderID: {common.NpmCLIBuildTypeV1: newLegacyBuilderProvenance},
common.NpmCLIHostedBuilderID: {common.NpmCLIBuildTypeV1: newLegacyBuilderProvenance},
// NOTE: we don't support Npm CLI on self-hosted.
}
// New returns a new Provenance for the given json payload.
func New(payload []byte) (iface.Provenance, error) {
func New(builderID string, payload []byte) (iface.Provenance, error) {
// Strict unmarshal.
// NOTE: this supports extensions because they are
// only used as part of interface{}-defined fields.
@@ -51,23 +58,15 @@ func New(payload []byte) (iface.Provenance, error) {
return nil, err
}
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)
btMap, ok := buildTypeMap[builderID]
if !ok {
return nil, fmt.Errorf("%w: %q", serrors.ErrorInvalidBuilderID, builderID)
}
pFunc, ok := btMap[a.Predicate.BuildType]
if !ok {
return nil, fmt.Errorf("%w: %q for builder ID %q", serrors.ErrorInvalidBuildType, a.Predicate.BuildType, builderID)
}
return pFunc(a), nil
}

View File

@@ -0,0 +1,188 @@
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"
)
// provenanceV1 is a base implementation for SLSA v1.0 provenance.
type provenanceV1 struct {
prov *Attestation
}
// Predicate implements ProvenanceV02.Predicate.
func (p *provenanceV1) Predicate() slsa1.ProvenancePredicate {
return p.prov.Predicate
}
// BuilderID implements Provenance.BuilderID.
func (p *provenanceV1) BuilderID() (string, error) {
return p.prov.Predicate.RunDetails.Builder.ID, nil
}
// BuildType implements Provenance.BuildType.
func (p *provenanceV1) BuildType() (string, error) {
return p.prov.Predicate.BuildDefinition.BuildType, nil
}
// SourceURI implements Provenance.SourceURI.
func (p *provenanceV1) 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
}
func (p *provenanceV1) 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 *provenanceV1) triggerInfo() (string, string, string, error) {
return p.builderTriggerInfo()
}
// TriggerURI implements Provenance.TriggerURI.
func (p *provenanceV1) 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 *provenanceV1) 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 *provenanceV1) GetBranch() (string, error) {
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 *provenanceV1) GetTag() (string, error) {
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 *provenanceV1) 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 *provenanceV1) GetBuildTriggerPath() (string, error) {
// TODO(#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 *provenanceV1) GetBuildInvocationID() (string, error) {
return p.prov.Predicate.RunDetails.BuildMetadata.InvocationID, nil
}
// GetBuildStartTime implements Provenance.GetBuildStartTime.
func (p *provenanceV1) GetBuildStartTime() (*time.Time, error) {
return p.prov.Predicate.RunDetails.BuildMetadata.StartedOn, nil
}
// GetBuildFinishTime implements Provenance.GetBuildFinishTime.
func (p *provenanceV1) GetBuildFinishTime() (*time.Time, error) {
return p.prov.Predicate.RunDetails.BuildMetadata.FinishedOn, nil
}
// GetNumberResolvedDependencies implements Provenance.GetNumberResolvedDependencies.
func (p *provenanceV1) GetNumberResolvedDependencies() (int, error) {
return len(p.prov.Predicate.BuildDefinition.ResolvedDependencies), nil
}
// GetSystemParameters implements Provenance.GetSystemParameters.
func (p *provenanceV1) 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
}

View File

@@ -2,14 +2,11 @@ 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"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/utils"
)
// BYOBBuildType is the base build type for BYOB delegated builders.
@@ -17,224 +14,72 @@ var BYOBBuildType = "https://github.com/slsa-framework/slsa-github-generator/del
// BYOBProvenance is SLSA v1.0 provenance for the slsa-github-generator BYOB build type.
type BYOBProvenance struct {
prov *Attestation
}
// 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
*provenanceV1
}
// 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")
sourceURI, err := p.SourceURI()
if err != nil {
return "", fmt.Errorf("reading source uri: %w", err)
}
return common.GetBranch(sysParams, true)
// Returns the branch from the source URI if available.
_, ref, err := utils.ParseGitURIAndRef(sourceURI)
if err != nil {
return "", fmt.Errorf("parsing source uri: %w", err)
}
if ref == "" {
return "", fmt.Errorf("%w: unable to get ref for source %q",
serrors.ErrorInvalidDssePayload, sourceURI)
}
refType, _ := utils.ParseGitRef(ref)
switch refType {
case "heads": // branch.
// NOTE: We return the full git ref.
return ref, nil
case "tags":
// NOTE: If the ref type is a tag we want to try to parse out the branch from the tag.
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)
default:
return "", fmt.Errorf("%w: unknown ref type %q for ref %q",
serrors.ErrorInvalidDssePayload, refType, ref)
}
}
// 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")
sourceURI, err := p.SourceURI()
if err != nil {
return "", fmt.Errorf("reading source uri: %w", err)
}
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")
// Returns the branch from the source URI if available.
_, ref, err := utils.ParseGitURIAndRef(sourceURI)
if err != nil {
return "", fmt.Errorf("parsing source uri: %w", err)
}
w, ok := sysParams["workflow"]
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "workflow parameters type")
if ref == "" {
return "", fmt.Errorf("%w: unable to get ref for source %q",
serrors.ErrorInvalidDssePayload, sourceURI)
}
wMap, ok := w.(map[string]string)
if !ok {
return "", fmt.Errorf("%w: %s", serrors.ErrorInvalidDssePayload, "workflow not a map")
refType, _ := utils.ParseGitRef(ref)
switch refType {
case "heads": // branch.
return "", nil
case "tags":
// NOTE: We return the full git ref.
return ref, nil
default:
return "", fmt.Errorf("%w: unknown ref type %q for ref %q",
serrors.ErrorInvalidDssePayload, refType, ref)
}
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
}

View File

@@ -0,0 +1,279 @@
package v1
import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
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"
)
func Test_BYOBProvenance_GetBranch(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
prov BYOBProvenance
branch string
err error
}{
{
name: "empty provenance",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{},
},
},
},
err: serrors.ErrorInvalidDssePayload,
},
{
name: "resolved dependency uri @ refs/heads/main",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
ResolvedDependencies: []slsa1.ResourceDescriptor{
{
URI: "git+https://github.com/kubernetes/kubernetes@refs/heads/main",
},
},
},
},
},
},
},
branch: "refs/heads/main",
},
{
name: "internalParameters GITHUB_REF @ refs/heads/main",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
InternalParameters: map[string]interface{}{
"GITHUB_REF_TYPE": "branch",
"GITHUB_REF": "refs/heads/main",
},
},
},
},
},
},
err: serrors.ErrorInvalidDssePayload,
},
{
name: "resolved dependency uri @ refs/tags/v1.0.0",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
InternalParameters: map[string]interface{}{
"GITHUB_BASE_REF": "",
"GITHUB_REF_TYPE": "tag",
"GITHUB_REF": "refs/tags/v1.0.0",
"GITHUB_EVENT_NAME": "push",
"GITHUB_EVENT_PAYLOAD": map[string]any{
"base_ref": nil,
},
},
ResolvedDependencies: []slsa1.ResourceDescriptor{
{
URI: "git+https://github.com/kubernetes/kubernetes@refs/tags/v1.0.0",
},
},
},
},
},
},
},
branch: "",
},
{
name: "resolved dependency uri @ refs/heads/main no ref",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
InternalParameters: map[string]interface{}{
"GITHUB_REF_TYPE": "branch",
"GITHUB_REF": "refs/heads/main",
},
ResolvedDependencies: []slsa1.ResourceDescriptor{
{
URI: "git+https://github.com/kubernetes/kubernetes",
},
},
},
},
},
},
},
err: serrors.ErrorInvalidDssePayload,
},
}
for i := range testCases {
tt := testCases[i]
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
branch, err := tt.prov.GetBranch()
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error (-want +got): \n%s", diff)
}
if got, want := branch, tt.branch; got != want {
t.Fatalf("unexpected branch, got: %q, want: %q", got, want)
}
})
}
}
func Test_BYOBProvenance_GetTag(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
prov BYOBProvenance
tag string
err error
}{
{
name: "empty provenance",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{},
},
},
},
err: serrors.ErrorInvalidDssePayload,
},
{
name: "resolved dependency uri @ refs/heads/main",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
ResolvedDependencies: []slsa1.ResourceDescriptor{
{
URI: "git+https://github.com/kubernetes/kubernetes@refs/heads/main",
},
},
},
},
},
},
},
tag: "",
},
{
name: "internalParameters GITHUB_REF @ refs/heads/main",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
InternalParameters: map[string]interface{}{
"GITHUB_REF_TYPE": "branch",
"GITHUB_REF": "refs/heads/main",
},
},
},
},
},
},
err: serrors.ErrorInvalidDssePayload,
},
{
name: "resolved dependency uri @ refs/tags/v1.0.0",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
ResolvedDependencies: []slsa1.ResourceDescriptor{
{
URI: "git+https://github.com/kubernetes/kubernetes@refs/tags/v1.0.0",
},
},
},
},
},
},
},
tag: "refs/tags/v1.0.0",
},
{
name: "internalParameters GITHUB_REF @ ref/tags/v1.0.0",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
InternalParameters: map[string]interface{}{
"GITHUB_REF_TYPE": "tag",
"GITHUB_REF": "refs/tags/v1.0.0",
},
},
},
},
},
},
err: serrors.ErrorInvalidDssePayload,
},
{
name: "resolved dependency uri @ refs/tags/v1.0.0 no ref",
prov: BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
StatementHeader: intoto.StatementHeader{},
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
ResolvedDependencies: []slsa1.ResourceDescriptor{
{
URI: "git+https://github.com/kubernetes/kubernetes",
},
},
},
},
},
},
},
err: serrors.ErrorInvalidDssePayload,
},
}
for i := range testCases {
tt := testCases[i]
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
tag, err := tt.prov.GetTag()
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error: %v", err)
}
if got, want := tag, tt.tag; got != want {
t.Fatalf("unexpected tag, got: %q, want: %q", got, want)
}
})
}
}

View File

@@ -1,11 +1,9 @@
package v1
// ContainerBasedBuildType is the build type for the container-based builder and is based on BYOB.
// ContainerBasedBuildType is the build type for the container-based builder.
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
*provenanceV1
}

View File

@@ -7,8 +7,9 @@ import (
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"
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"
)
@@ -23,8 +24,33 @@ type ProvenanceV1 interface {
Predicate() slsa1.ProvenancePredicate
}
type provFunc func(*Attestation) iface.Provenance
func newBYOB(a *Attestation) iface.Provenance {
return &BYOBProvenance{
provenanceV1: &provenanceV1{
prov: a,
},
}
}
func newContainerBased(a *Attestation) iface.Provenance {
return &ContainerBasedProvenance{
provenanceV1: &provenanceV1{
prov: a,
},
}
}
// buildTypeMap is a map of builder IDs to supported buildTypes.
var buildTypeMap = map[string]map[string]provFunc{
common.GenericDelegatorBuilderID: {common.BYOBBuildTypeV0: newBYOB},
common.GenericLowPermsDelegatorBuilderID: {common.BYOBBuildTypeV0: newBYOB},
common.ContainerBasedBuilderID: {common.ContainerBasedBuildTypeV01Draft: newContainerBased},
}
// New returns a new Provenance object based on the payload.
func New(payload []byte) (iface.Provenance, error) {
func New(builderID string, payload []byte) (iface.Provenance, error) {
// Strict unmarshal.
// NOTE: this supports extensions because they are
// only used as part of interface{}-defined fields.
@@ -33,21 +59,18 @@ func New(payload []byte) (iface.Provenance, error) {
a := &Attestation{}
if err := dec.Decode(a); err != nil {
return nil, err
return nil, fmt.Errorf("%w: %w", serrors.ErrorInvalidDssePayload, 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)
btMap, ok := buildTypeMap[builderID]
if !ok {
return nil, fmt.Errorf("%w: %q", serrors.ErrorInvalidBuilderID, builderID)
}
provFunc, ok := btMap[a.Predicate.BuildDefinition.BuildType]
if !ok {
return nil, fmt.Errorf("%w: %q for builder ID %q", serrors.ErrorInvalidBuildType, a.Predicate.BuildDefinition.BuildType, builderID)
}
return provFunc(a), nil
}

View File

@@ -0,0 +1,118 @@
package v1
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
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"
"github.com/slsa-framework/slsa-verifier/v2/verifiers/internal/gha/slsaprovenance/iface"
)
func Test_New(t *testing.T) {
testCases := []struct {
name string
builderID string
payload string
prov iface.Provenance
err error
}{
{
name: "BYOB build type",
builderID: common.GenericDelegatorBuilderID,
payload: fmt.Sprintf(`{
"predicate": {
"buildDefinition": {
"buildType": %q
}
}
}`, common.BYOBBuildTypeV0),
prov: &BYOBProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
BuildType: common.BYOBBuildTypeV0,
},
},
},
},
},
},
{
name: "Container-based build type",
builderID: common.ContainerBasedBuilderID,
payload: fmt.Sprintf(`{
"predicate": {
"buildDefinition": {
"buildType": %q
}
}
}`, common.ContainerBasedBuildTypeV01Draft),
prov: &ContainerBasedProvenance{
provenanceV1: &provenanceV1{
prov: &Attestation{
Predicate: slsa1.ProvenancePredicate{
BuildDefinition: slsa1.ProvenanceBuildDefinition{
BuildType: common.ContainerBasedBuildTypeV01Draft,
},
},
},
},
},
},
{
name: "Unknown fields",
payload: `{
"predicate": {
"unknown": "field",
"buildDefinition": {
"buildType": "foo"
}
}
}`,
err: serrors.ErrorInvalidDssePayload,
},
{
name: "Unknown builder ID",
builderID: "unknown",
payload: `{
"predicate": {
"buildDefinition": {
"buildType": "foo"
}
}
}`,
err: serrors.ErrorInvalidBuilderID,
},
{
name: "Unknown buildType",
builderID: common.GenericDelegatorBuilderID,
payload: `{
"predicate": {
"buildDefinition": {
"buildType": "foo"
}
}
}`,
err: serrors.ErrorInvalidBuildType,
},
}
for i := range testCases {
tt := testCases[i]
t.Run(tt.name, func(t *testing.T) {
p, err := New(tt.builderID, []byte(tt.payload))
if diff := cmp.Diff(tt.err, err, cmpopts.EquateErrors()); diff != "" {
t.Fatalf("unexpected error (-want +got): \n%s", diff)
}
if diff := cmp.Diff(tt.prov, p, cmp.AllowUnexported(provenanceV1{}, BYOBProvenance{}, ContainerBasedProvenance{})); diff != "" {
t.Fatalf("unexpected result (-want +got): \n%s", diff)
}
})
}
}

View File

@@ -55,7 +55,7 @@ func verifyEnvAndCert(env *dsse.Envelope,
}
// Verify the builder identity.
builderID, byob, err := VerifyBuilderIdentity(workflowInfo, builderOpts, defaultBuilders)
verifiedBuilderID, byob, err := VerifyBuilderIdentity(workflowInfo, builderOpts, defaultBuilders)
if err != nil {
return nil, nil, err
}
@@ -67,7 +67,7 @@ func verifyEnvAndCert(env *dsse.Envelope,
// Verify properties of the SLSA provenance.
// Unpack and verify info in the provenance, including the subject Digest.
provenanceOpts.ExpectedBuilderID = builderID.String()
provenanceOpts.ExpectedBuilderID = verifiedBuilderID.String()
// There is a corner-case to handle: if the verified builder ID from the cert
// is a delegator builder, the user MUST provide an expected builder ID
// and we MUST match it against the content of the provenance.
@@ -79,7 +79,7 @@ func verifyEnvAndCert(env *dsse.Envelope,
}
provenanceOpts.ExpectedBuilderID = *builderOpts.ExpectedID
}
if err := VerifyProvenance(env, provenanceOpts, byob); err != nil {
if err := VerifyProvenance(env, provenanceOpts, verifiedBuilderID, byob); err != nil {
return nil, nil, err
}
@@ -92,7 +92,7 @@ func verifyEnvAndCert(env *dsse.Envelope,
return nil, nil, err
}
return r, builderID, nil
return r, verifiedBuilderID, nil
}
func verifyNpmEnvAndCert(env *dsse.Envelope,
@@ -178,7 +178,7 @@ func verifyNpmEnvAndCert(env *dsse.Envelope,
return nil, fmt.Errorf("%w: re-usable workflow is GitHub-hosted", serrors.ErrorMismatchBuilderID)
}
default:
return nil, fmt.Errorf("%w: builder %q. Expected one of %q, %q", serrors.ErrorNotSupported, *builderOpts.ExpectedID,
return nil, fmt.Errorf("%w: builder %v. Expected one of %v, %v", serrors.ErrorNotSupported, *builderOpts.ExpectedID,
common.NpmCLISelfHostedBuilderID, common.NpmCLIHostedBuilderID)
}
@@ -193,7 +193,7 @@ func verifyNpmEnvAndCert(env *dsse.Envelope,
// Verify properties of the SLSA provenance.
// Unpack and verify info in the provenance, including the Subject Digest.
if err := VerifyNpmPackageProvenance(env, workflowInfo, provenanceOpts, isTrustedBuilder); err != nil {
if err := VerifyNpmPackageProvenance(env, workflowInfo, provenanceOpts, trustedBuilderID, isTrustedBuilder); err != nil {
return nil, err
}
@@ -330,6 +330,14 @@ func (v *GHAVerifier) VerifyNpmPackage(ctx context.Context,
return nil, nil, err
}
// Verify builder information.
builder, err := npm.verifyBuilderID(
provenanceOpts, builderOpts,
defaultBYOBReusableWorkflows)
if err != nil {
return nil, nil, err
}
// Verify attestation headers.
if err := npm.verifyIntotoHeaders(); err != nil {
return nil, nil, err
@@ -346,15 +354,6 @@ func (v *GHAVerifier) VerifyNpmPackage(ctx context.Context,
}
}
// Verify certificate information.
builder, err := verifyNpmEnvAndCert(npm.ProvenanceEnvelope(),
npm.ProvenanceLeafCertificate(),
provenanceOpts, builderOpts,
defaultBYOBReusableWorkflows)
if err != nil {
return nil, nil, err
}
prov, err := npm.verifiedProvenanceBytes()
if err != nil {
return nil, nil, err