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: Verification for when sha1 is specified in BYOB TRW #641

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5ec2e5f
Update buildTypes
ianlewis Jun 16, 2023
af33306
Update git ref checking
ianlewis Jun 16, 2023
77a0165
fix comment lint error
ianlewis Jun 18, 2023
1448434
Add tests to git utils
ianlewis Jun 18, 2023
dc1e6e3
add dot
ianlewis Jun 19, 2023
21ef45c
Get branch/tag from source URI
ianlewis Jun 19, 2023
a5ba959
Add byob provenance tests
ianlewis Jun 19, 2023
3c148a5
Add GetTag tests
ianlewis Jun 19, 2023
4759070
Update
ianlewis Jun 19, 2023
e3acd8f
Dedup code
ianlewis Jun 19, 2023
415db95
remove newline
ianlewis Jun 19, 2023
6ae0e82
Merge branch 'main' into 600-featurebyob-verification-for-sha1-provid…
ianlewis Jun 26, 2023
df1cf8c
Add test for New
ianlewis Jun 26, 2023
5512f19
Add test for resolvedDependency with no ref
ianlewis Jun 26, 2023
49086b4
Fix comment
ianlewis Jun 26, 2023
d6c9936
Remove old vars
ianlewis Jun 26, 2023
adb3f5b
Remove old vars
ianlewis Jun 26, 2023
2d53393
Verify buildType along with builder ID
ianlewis Jun 27, 2023
95f1328
Fix errors
ianlewis Jun 27, 2023
5041f82
fix builder ID
ianlewis Jun 27, 2023
f9ef890
Fix builderID
ianlewis Jun 27, 2023
61955a3
Merge branch 'main' into 600-featurebyob-verification-for-sha1-provid…
laurentsimon Jul 25, 2023
43fc1f9
Update provenance.go
laurentsimon Jul 25, 2023
c301c5e
Update provenance.go
laurentsimon Jul 25, 2023
6b6dbc2
Merge branch 'main' into 600-featurebyob-verification-for-sha1-provid…
laurentsimon Jul 25, 2023
59bf5c5
Don't fall back to previous source URI
ianlewis Jul 25, 2023
0c0ec92
Update tests
ianlewis Jul 25, 2023
59c7e97
Fix format
ianlewis Jul 25, 2023
c243903
Add tests
ianlewis Jul 25, 2023
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 @@ -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")
Expand Down
108 changes: 45 additions & 63 deletions verifiers/internal/gha/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,100 +76,84 @@ func verifyBuilderIDLooseMatch(prov iface.Provenance, expectedBuilderID string)
return nil
}

func asURI(s string) string {
source := s
if !strings.HasPrefix(source, "https://") &&
!strings.HasPrefix(source, "git+") {
source = "git+https://" + source
}
if !strings.HasPrefix(source, "git+") {
source = "git+" + source
}

return source
}

// Verify source URI in provenance statement.
func verifySourceURI(prov iface.Provenance, expectedSourceURI string, allowNoMaterialRef bool) error {
source := asURI(expectedSourceURI)
func verifySourceURI(prov iface.Provenance, expectedSourceURI string) error {
ianlewis marked this conversation as resolved.
Show resolved Hide resolved
source := utils.NormalizeGitURI(expectedSourceURI)

// We expect github.com URIs only.
if !strings.HasPrefix(source, "git+https://github.com/") {
return fmt.Errorf("%w: expected source github.com repository '%s'", serrors.ErrorMalformedURI,
return fmt.Errorf("%w: expected source github.com repository %q", serrors.ErrorMalformedURI,
source)
}

// Verify source in the trigger
fullConfigURI, err := prov.TriggerURI()
fullTriggerURI, err := prov.TriggerURI()
if err != nil {
return err
}

configURI, err := sourceFromURI(fullConfigURI, false)
triggerURI, triggerRef, err := utils.ParseGitURIAndRef(fullTriggerURI)
if err != nil {
return err
}
if configURI != source {
return fmt.Errorf("%w: expected source '%s' in configSource.uri, got '%s'", serrors.ErrorMismatchSource,
source, fullConfigURI)
if triggerURI != source {
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.
if triggerRef == "" {
return fmt.Errorf("%w: missing ref: %q", serrors.ErrorMalformedURI, fullTriggerURI)
}

// Verify source from material section.
materialSourceURI, err := prov.SourceURI()
fullSourceURI, err := prov.SourceURI()
if err != nil {
return err
}

materialURI, err := sourceFromURI(materialSourceURI, allowNoMaterialRef)
sourceURI, sourceRef, err := utils.ParseGitURIAndRef(fullSourceURI)
if err != nil {
return err
}
if materialURI != source {
return fmt.Errorf("%w: expected source '%s' in material section, got '%s'", serrors.ErrorMismatchSource,
source, materialSourceURI)
if sourceURI != source {
return fmt.Errorf("%w: expected source %q to match source-uri %q", serrors.ErrorMismatchSource,
fullSourceURI, source)
}

// Last, verify that both fields match.
// We use the full URI to match on the tag as well.
if allowNoMaterialRef && len(strings.Split(materialSourceURI, "@")) == 1 {
buildType, err := prov.BuildType()
if err != nil {
return fmt.Errorf("checking buildType: %v", err)
}
if sourceRef == "" {
// 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.
return nil
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 {
ianlewis marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
return fmt.Errorf("%w: missing ref: %q", serrors.ErrorMalformedURI, fullSourceURI)
}
if fullConfigURI != materialSourceURI {
return fmt.Errorf("%w: material and config URIs do not match: '%s' != '%s'",

// 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: source and trigger URIs do not match: %q != %q",
serrors.ErrorInvalidDssePayload,
fullConfigURI, materialSourceURI)
fullTriggerURI, fullSourceURI)
}

return nil
}

// sourceFromURI retrieves the source repository given a repository URI with ref.
//
// NOTE: `allowNoRef` is to allow for verification of npm packages
// generated before GA. Their provenance did not have a ref,
// see https://github.com/slsa-framework/slsa-verifier/issues/492.
// `allowNoRef` should be set to `false` for all other cases.
func sourceFromURI(uri string, allowNoRef bool) (string, error) {
if uri == "" {
return "", fmt.Errorf("%w: empty uri", serrors.ErrorMalformedURI)
}

r := strings.Split(uri, "@")
if len(r) < 2 && !allowNoRef {
return "", fmt.Errorf("%w: %s", serrors.ErrorMalformedURI,
uri)
}
if len(r) < 1 {
return "", fmt.Errorf("%w: %s", serrors.ErrorMalformedURI,
uri)
}

return r[0], nil
}

// Verify Subject Digest from the provenance statement.
func verifyDigest(prov iface.Provenance, expectedHash string) error {
subjects, err := prov.Subjects()
Expand Down Expand Up @@ -270,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
}

Expand Down Expand Up @@ -320,15 +304,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
}

Expand Down
70 changes: 49 additions & 21 deletions verifiers/internal/gha/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ 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"
"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
Expand All @@ -33,6 +35,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 }
Expand Down Expand Up @@ -121,7 +124,7 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha1": "4506290e2e8feb1f34b27a044f7cc863c830ef6b",
},
},
Expand All @@ -136,7 +139,7 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha1": "4506290e2e8feb1f34b27a044f7cc863c830ef6b",
},
},
Expand All @@ -157,7 +160,7 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
Expand All @@ -171,7 +174,7 @@ func Test_VerifyDigest(t *testing.T) {
prov: &testProvenance{
subjects: []intoto.Subject{
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
Expand All @@ -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{
"sha1": "4506290e2e8feb1f34b27a044f7cc863c830ef6b",
},
},
{
Digest: common.DigestSet{
Digest: slsacommon.DigestSet{
"sha256": "0ae7e4fa71686538440012ee36a2634dbaa19df2dd16a466f52411fb348bbc4e",
},
},
Expand All @@ -207,17 +210,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",
},
},
Expand All @@ -230,17 +233,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",
},
},
Expand All @@ -266,6 +269,7 @@ func Test_verifySourceURI(t *testing.T) {
t.Parallel()
tests := []struct {
name string
provBuildType string
provMaterialsURI string
provTriggerURI string
expectedSourceURI string
Expand Down Expand Up @@ -322,14 +326,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: "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,
ianlewis marked this conversation as resolved.
Show resolved Hide resolved
},
{
name: "match source no git no material ref ref not allowed",
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",
Expand Down Expand Up @@ -397,11 +424,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))
}
Expand Down
Loading
Loading