Skip to content

Commit

Permalink
feat: verify sourceURI for npm packages (#521)
Browse files Browse the repository at this point in the history
* update

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

* update

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

* update

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

* update

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

* Update verifiers/internal/gha/provenance.go

Co-authored-by: Ian Lewis <ianlewis@google.com>
Signed-off-by: laurentsimon <64505099+laurentsimon@users.noreply.github.com>

* update

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

---------

Signed-off-by: laurentsimon <laurentsimon@google.com>
Signed-off-by: laurentsimon <64505099+laurentsimon@users.noreply.github.com>
Co-authored-by: Ian Lewis <ianlewis@google.com>
  • Loading branch information
laurentsimon and ianlewis committed Mar 10, 2023
1 parent 5a77b25 commit ae38103
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 20 deletions.
3 changes: 3 additions & 0 deletions verifiers/internal/gcb/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ func (p *Provenance) VerifySourceURI(expectedSourceURI string, builderID utils.T
return fmt.Errorf("%w: no materials", serrors.ErrorInvalidDssePayload)
}
uri := materials[0].URI
// NOTE: the material URI did not contain 'git+' for GCB versions <= v0.3.
// A change occurred sometimes in v0.3 witout version bump.
// Versions >= 0.3 contain the prefix (https://github.com/slsa-framework/slsa-verifier/pull/519).
uri = strings.TrimPrefix(uri, "git+")

// It is possible that GCS builds at level 2 use GCS sources, prefixed by gs://.
Expand Down
36 changes: 22 additions & 14 deletions verifiers/internal/gha/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func asURI(s string) string {
}

// Verify source URI in provenance statement.
func verifySourceURI(prov slsaprovenance.Provenance, expectedSourceURI string, verifyMaterials bool) error {
func verifySourceURI(prov slsaprovenance.Provenance, expectedSourceURI string, allowNoMaterialRef bool) error {
source := asURI(expectedSourceURI)

// We expect github.com URIs only.
Expand All @@ -85,7 +85,7 @@ func verifySourceURI(prov slsaprovenance.Provenance, expectedSourceURI string, v
if err != nil {
return err
}
configURI, err := sourceFromURI(fullConfigURI)
configURI, err := sourceFromURI(fullConfigURI, false)
if err != nil {
return err
}
Expand All @@ -95,15 +95,11 @@ func verifySourceURI(prov slsaprovenance.Provenance, expectedSourceURI string, v
}

// Verify source from material section.
// TODO(#492): disable this option.
if !verifyMaterials {
return nil
}
materialSourceURI, err := prov.SourceURI()
if err != nil {
return err
}
materialURI, err := sourceFromURI(materialSourceURI)
materialURI, err := sourceFromURI(materialSourceURI, allowNoMaterialRef)
if err != nil {
return err
}
Expand All @@ -114,6 +110,12 @@ func verifySourceURI(prov slsaprovenance.Provenance, expectedSourceURI string, v

// 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 {
// 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 fullConfigURI != materialSourceURI {
return fmt.Errorf("%w: material and config URIs do not match: '%s' != '%s'",
serrors.ErrorInvalidDssePayload,
Expand All @@ -123,13 +125,19 @@ func verifySourceURI(prov slsaprovenance.Provenance, expectedSourceURI string, v
return nil
}

func sourceFromURI(uri string) (string, error) {
// 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.SplitN(uri, "@", 2)
if len(r) < 2 {
r := strings.Split(uri, "@")
if len(r) < 2 && !allowNoRef {
return "", fmt.Errorf("%w: %s", serrors.ErrorMalformedURI,
uri)
}
Expand Down Expand Up @@ -217,7 +225,7 @@ func VerifyNpmPackageProvenance(env *dsselib.Envelope, provenanceOpts *options.P
}
// NOTE: for the non trusted builders, the information may be forgeable.
// Also, the GitHub context is not recorded for the default builder.
return VerifyProvenanceCommonOptions(prov, provenanceOpts, false)
return VerifyProvenanceCommonOptions(prov, provenanceOpts, true)
}

func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceOpts,
Expand All @@ -234,14 +242,14 @@ func VerifyProvenance(env *dsselib.Envelope, provenanceOpts *options.ProvenanceO
return err
}

return VerifyProvenanceCommonOptions(prov, provenanceOpts, true)
return VerifyProvenanceCommonOptions(prov, provenanceOpts, false)
}

func VerifyProvenanceCommonOptions(prov slsaprovenance.Provenance, provenanceOpts *options.ProvenanceOpts,
verifyMaterials bool,
allowNoMaterialRef bool,
) error {
// Verify source.
if err := verifySourceURI(prov, provenanceOpts.ExpectedSourceURI, verifyMaterials); err != nil {
if err := verifySourceURI(prov, provenanceOpts.ExpectedSourceURI, allowNoMaterialRef); err != nil {
return err
}

Expand Down
52 changes: 46 additions & 6 deletions verifiers/internal/gha/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,11 @@ func Test_VerifyDigest(t *testing.T) {
func Test_verifySourceURI(t *testing.T) {
t.Parallel()
tests := []struct {
name string
prov *intoto.ProvenanceStatement
sourceURI string
expected error
name string
prov *intoto.ProvenanceStatement
sourceURI string
allowNoMaterialRef bool
expected error
// v1 provenance does not include materials
skipv1 bool
}{
Expand Down Expand Up @@ -288,6 +289,45 @@ func Test_verifySourceURI(t *testing.T) {
},
sourceURI: "https://github.com/some/repo",
},
{
name: "match source no git no material ref",
prov: &intoto.ProvenanceStatement{
Predicate: slsa02.ProvenancePredicate{
Invocation: slsa02.ProvenanceInvocation{
ConfigSource: slsa02.ConfigSource{
URI: "git+https://github.com/some/repo@v1.2.3",
},
},
Materials: []slsacommon.ProvenanceMaterial{
{
URI: "git+https://github.com/some/repo",
},
},
},
},
allowNoMaterialRef: true,
sourceURI: "https://github.com/some/repo",
},
{
name: "match source no git no material ref ref not allowed",
prov: &intoto.ProvenanceStatement{
Predicate: slsa02.ProvenancePredicate{
Invocation: slsa02.ProvenanceInvocation{
ConfigSource: slsa02.ConfigSource{
URI: "git+https://github.com/some/repo@v1.2.3",
},
},
Materials: []slsacommon.ProvenanceMaterial{
{
URI: "git+https://github.com/some/repo",
},
},
},
},
sourceURI: "https://github.com/some/repo",
expected: serrors.ErrorMalformedURI,
skipv1: true,
},
{
name: "match source no git+https",
prov: &intoto.ProvenanceStatement{
Expand Down Expand Up @@ -412,7 +452,7 @@ func Test_verifySourceURI(t *testing.T) {
ProvenanceStatement: tt.prov,
}

err := verifySourceURI(prov02, tt.sourceURI, true)
err := verifySourceURI(prov02, tt.sourceURI, tt.allowNoMaterialRef)
if !errCmp(err, tt.expected) {
t.Errorf(cmp.Diff(err, tt.expected))
}
Expand All @@ -433,7 +473,7 @@ func Test_verifySourceURI(t *testing.T) {
},
},
}
err = verifySourceURI(prov1, tt.sourceURI, true)
err = verifySourceURI(prov1, tt.sourceURI, tt.allowNoMaterialRef)
if !errCmp(err, tt.expected) {
t.Errorf(cmp.Diff(err, tt.expected))
}
Expand Down

0 comments on commit ae38103

Please sign in to comment.