Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
Signed-off-by: laurentsimon <laurentsimon@google.com>
  • Loading branch information
laurentsimon committed Apr 18, 2023
1 parent fb0810e commit 83d0fe1
Show file tree
Hide file tree
Showing 8 changed files with 472 additions and 51 deletions.
1 change: 1 addition & 0 deletions cli/slsa-verifier/verify/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ func (o *VerifyNpmOptions) AddFlags(cmd *cobra.Command) {
"[optional] print the verified provenance to stdout")

cmd.MarkFlagRequired("source-uri")
cmd.MarkFlagRequired("builder-id")
cmd.MarkFlagsMutuallyExclusive("source-versioned-tag", "source-tag")
}

Expand Down
4 changes: 2 additions & 2 deletions verifiers/internal/gcb/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,14 +270,14 @@ func (p *Provenance) VerifyBuilder(builderOpts *options.BuilderOpts) (*utils.Tru
return nil, err
}

provBuilderID, err := utils.TrustedBuilderIDNew(predicateBuilderID)
provBuilderID, err := utils.TrustedBuilderIDNew(predicateBuilderID, true)
if err != nil {
return nil, err
}

// Validate with user-provided value.
if builderOpts != nil && builderOpts.ExpectedID != nil {
if err := provBuilderID.Matches(*builderOpts.ExpectedID, false); err != nil {
if err := provBuilderID.MatchesLoose(*builderOpts.ExpectedID, false); err != nil {
return nil, err
}
}
Expand Down
18 changes: 11 additions & 7 deletions verifiers/internal/gha/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ var (
trustedBuilderRepository = "slsa-framework/slsa-github-generator"
e2eTestRepository = "slsa-framework/example-package"
certOidcIssuer = "https://token.actions.githubusercontent.com"
githubCom = "github.com/"
httpsGithubCom = "https://" + githubCom
// This is used in cosign's CheckOpts for validating the certificate. We
// do specific builder verification after this.
certSubjectRegexp = "https://github.com/*"
certSubjectRegexp = httpsGithubCom + "*"
)

var defaultArtifactTrustedReusableWorkflows = map[string]bool{
Expand All @@ -32,8 +34,10 @@ var defaultContainerTrustedReusableWorkflows = map[string]bool{
trustedBuilderRepository + "/.github/workflows/generator_container_slsa3.yml": true,
}

var delegatorGenericReusableWorkflow = trustedBuilderRepository + "/.github/workflows/delegator_generic_slsa3.yml"

var defaultBYOBReusableWorkflows = map[string]bool{
trustedBuilderRepository + "/.github/workflows/delegator_generic_slsa3.yml": true,
delegatorGenericReusableWorkflow: true,
}

// VerifyCertficateSourceRepository verifies the source repository.
Expand All @@ -43,7 +47,7 @@ func VerifyCertficateSourceRepository(id *WorkflowIdentity,
// The caller repository in the x509 extension is not fully qualified. It only contains
// {org}/{repository}.
expectedSource := strings.TrimPrefix(sourceRepo, "git+https://")
expectedSource = strings.TrimPrefix(expectedSource, "github.com/")
expectedSource = strings.TrimPrefix(expectedSource, githubCom)
if id.CallerRepository != expectedSource {
return fmt.Errorf("%w: expected source '%s', got '%s'", serrors.ErrorMismatchSource,
expectedSource, id.CallerRepository)
Expand Down Expand Up @@ -93,7 +97,7 @@ func VerifyBuilderIdentity(id *WorkflowIdentity,
func verifyTrustedBuilderID(certPath, certTag string, expectedBuilderID *string, defaultBuilders map[string]bool) (*utils.TrustedBuilderID, error) {
var trustedBuilderID *utils.TrustedBuilderID
var err error
certBuilderName := "https://github.com/" + certPath
certBuilderName := httpsGithubCom + certPath
// WARNING: we don't validate the tag here, because we need to allow
// refs/heads/main for e2e tests. See verifyTrustedBuilderRef().
// No builder ID provided by user: use the default trusted workflows.
Expand All @@ -102,22 +106,22 @@ func verifyTrustedBuilderID(certPath, certTag string, expectedBuilderID *string,
return nil, fmt.Errorf("%w: %s got %t", serrors.ErrorUntrustedReusableWorkflow, certPath, expectedBuilderID == nil)
}
// Construct the builderID using the certificate's builder's name and tag.
trustedBuilderID, err = utils.TrustedBuilderIDNew(certBuilderName + "@" + certTag)
trustedBuilderID, err = utils.TrustedBuilderIDNew(certBuilderName+"@"+certTag, true)
if err != nil {
return nil, err
}
} else {
// Verify the builderID.
// We only accept IDs on github.com.
trustedBuilderID, err = utils.TrustedBuilderIDNew(certBuilderName + "@" + certTag)
trustedBuilderID, err = utils.TrustedBuilderIDNew(certBuilderName+"@"+certTag, true)
if err != nil {
return nil, err
}

// BuilderID provided by user should match the certificate.
// Note: the certificate builderID has the form `name@refs/tags/v1.2.3`,
// so we pass `allowRef = true`.
if err := trustedBuilderID.Matches(*expectedBuilderID, true); err != nil {
if err := trustedBuilderID.MatchesLoose(*expectedBuilderID, true); err != nil {
return nil, fmt.Errorf("%w: %v", serrors.ErrorUntrustedReusableWorkflow, err)
}
}
Expand Down
5 changes: 2 additions & 3 deletions verifiers/internal/gha/npm.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (

const (
publishAttestationV01 = "https://github.com/npm/attestation/tree/main/specs/publish/"
builderGitHubRunnerID = "https://github.com/actions/runner"
ossfNpmBuilderID = "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_nodejs_slsa3.yml"
)

var errrorInvalidAttestations = errors.New("invalid npm attestations")
Expand Down Expand Up @@ -64,9 +66,6 @@ type Npm struct {
publishAttestation *attestation
}

// TODO(#494): update the builder name.
var builderGitHubRunnerID = "https://github.com/actions/runner@v0.1"

func (n *Npm) ProvenanceEnvelope() *dsse.Envelope {
return n.verifiedProvenanceAtt.Envelope
}
Expand Down
51 changes: 32 additions & 19 deletions verifiers/internal/gha/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,38 @@ func EnvelopeFromBytes(payload []byte) (env *dsselib.Envelope, err error) {
}

// Verify Builder ID in provenance statement.
// This function does an exact comparison, and expects certBuilderID to be the full
// This function does an exact comparison, and expects expectedBuilderID to be the full
// `name@refs/tags/<name>`.
func verifyBuilderIDExactMatch(prov slsaprovenance.Provenance, certBuilderID string) error {
builderID, err := prov.BuilderID()
func verifyBuilderIDExactMatch(prov slsaprovenance.Provenance, expectedBuilderID string) error {
id, err := prov.BuilderID()
if err != nil {
return err
}
if certBuilderID != builderID {
return fmt.Errorf("%w: expected '%s' in builder.id, got '%s'", serrors.ErrorMismatchBuilderID,
certBuilderID, builderID)
provBuilderID, err := utils.TrustedBuilderIDNew(id, false)
if err != nil {
return err
}
if err := provBuilderID.MatchesFull(expectedBuilderID, true); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}

// Verify Builder ID in provenance statement.
// This function verifies the names match. If the expected builder ID contains a version,
// it also evrifies the verisons match.
func verifyBuilderIDLooseMatch(prov slsaprovenance.Provenance, expectedBuilderID string) error {
id, err := prov.BuilderID()
if err != nil {
return err
}
provBuilderID, err := utils.TrustedBuilderIDNew(id, false)
if err != nil {
return err
}
if err := provBuilderID.MatchesLoose(expectedBuilderID, true); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}

Expand Down Expand Up @@ -207,21 +227,14 @@ func VerifyNpmPackageProvenance(env *dsselib.Envelope, provenanceOpts *options.P
return err
}

// Untrusted builder.
if provenanceOpts.ExpectedBuilderID == "" {
// Verify it's the npm CLI.
builderID, err := prov.BuilderID()
if err != nil {
return err
}
// TODO(#494): update the builder ID string.
if !strings.HasPrefix(builderID, "https://github.com/npm/cli@") {
return fmt.Errorf("%w: expected 'https://github.com/npm/cli' in builder.id, got '%s'",
serrors.ErrorMismatchBuilderID, builderID)
}
} else if err := verifyBuilderIDExactMatch(prov, provenanceOpts.ExpectedBuilderID); err != nil {
// TODO: Verify the buildType.
// This depends on the builder (delegator or CLI).

// Verify the builder ID.
if err := verifyBuilderIDLooseMatch(prov, provenanceOpts.ExpectedBuilderID); err != nil {
return err
}

// 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, true)
Expand Down
52 changes: 39 additions & 13 deletions verifiers/internal/gha/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func GHAVerifierNew() *GHAVerifier {
// generated by the builderID.
func (v *GHAVerifier) IsAuthoritativeFor(builderID string) bool {
// This verifier only supports builders defined on GitHub.
return strings.HasPrefix(builderID, "https://github.com/")
return strings.HasPrefix(builderID, httpsGithubCom)
}

func verifyEnvAndCert(env *dsse.Envelope,
Expand Down Expand Up @@ -98,7 +98,14 @@ func verifyNpmEnvAndCert(env *dsse.Envelope,
}

// Verify the workflow identity.
trustedBuilderID, err := VerifyBuilderIdentity(workflowInfo, builderOpts, defaultBuilders)
// We verify against the delegator re-usable workflow, not the user-provided
// builder. This is because the signing identity for delegator-based builders
// is *always* the delegator workflow.
expectedDelegatorWorkflow := httpsGithubCom + delegatorGenericReusableWorkflow
delegatorBuilderOpts := options.BuilderOpts{
ExpectedID: &expectedDelegatorWorkflow,
}
trustedBuilderID, err := VerifyBuilderIdentity(workflowInfo, &delegatorBuilderOpts, defaultBuilders)
// We accept a non-trusted builder for the default npm builder
// that uses npm CLI.
if err != nil && !errors.Is(err, serrors.ErrorUntrustedReusableWorkflow) {
Expand All @@ -113,25 +120,44 @@ func verifyNpmEnvAndCert(env *dsse.Envelope,
return nil, err
}

// Verify properties of the SLSA provenance.
// Unpack and verify info in the provenance, including the Subject Digest.
// WARNING: builderID may be empty if it's not a reusable builder workflow.
// Users must always provide the builder ID.
if builderOpts == nil || builderOpts.ExpectedID == nil {
return nil, fmt.Errorf("builder ID is empty")
}

// WARNING: builderID may be empty if it's not a trusted reusable builder workflow.
if trustedBuilderID != nil {
provenanceOpts.ExpectedBuilderID = trustedBuilderID.String()
// We only support builders built using the BYOB framework.
// The builder is guaranteed to be delegatorGenericReusableWorkflow, since this is the builder
// we compare against in the call to VerifyBuilderIdentity() above.
// The delegator workflow will set the builder ID to the caller's path,
// which is what users match against.
provenanceOpts.ExpectedBuilderID = *builderOpts.ExpectedID
} else {
if builderOpts == nil || builderOpts.ExpectedID == nil {
return nil, fmt.Errorf("builder mistmatch. No builder ID provided by user , got '%v'", builderGitHubRunnerID)
}
// TODO(#494): update the builder ID name.
trustedBuilderID, err = utils.TrustedBuilderIDNew(builderGitHubRunnerID)
// NOTE: if the user created provenance using a re-usable workflow
// that does not integrate with the BYOB framework, this code will be run.
// In this case, the re-usable workflow must set the builder ID to
// builderGitHubRunnerID. This means we treat arbitrary re-usable
// workflows like the default GitHub Action runner. Note that
// the SAN in the certificate is *different* from the builder ID
// provided by users during verification.
// We may add support for verifying provenance from arbitrary re-usable workflows
// later; which may be useful for org-level builders.

// The builder.id is set to builderGitHubRunnerID by the npm CLI.
trustedBuilderID, err = utils.TrustedBuilderIDNew(builderGitHubRunnerID, false)
if err != nil {
return nil, err
}
if err := trustedBuilderID.Matches(*builderOpts.ExpectedID, false); err != nil {
return nil, fmt.Errorf("builder mistmatch. %w", err)
if err := trustedBuilderID.MatchesLoose(*builderOpts.ExpectedID, false); err != nil {
return nil, fmt.Errorf("%w", err)
}
// On GitHub we only support the default GitHub runner builder.
provenanceOpts.ExpectedBuilderID = builderGitHubRunnerID
}

// Verify properties of the SLSA provenance.
// Unpack and verify info in the provenance, including the Subject Digest.
if err := VerifyNpmPackageProvenance(env, provenanceOpts); err != nil {
return nil, err
}
Expand Down
35 changes: 32 additions & 3 deletions verifiers/utils/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ type TrustedBuilderID struct {
}

// TrustedBuilderIDNew creates a new BuilderID structure.
func TrustedBuilderIDNew(builderID string) (*TrustedBuilderID, error) {
name, version, err := ParseBuilderID(builderID, true)
func TrustedBuilderIDNew(builderID string, needVersion bool) (*TrustedBuilderID, error) {
name, version, err := ParseBuilderID(builderID, needVersion)
if err != nil {
return nil, err
}
Expand All @@ -31,7 +31,7 @@ func TrustedBuilderIDNew(builderID string) (*TrustedBuilderID, error) {
// match. In this case, if the BuilderID version is a GitHub ref
// `refs/tags/name`, we will consider it equal to user-provided
// builderID `name`.
func (b *TrustedBuilderID) Matches(builderID string, allowRef bool) error {
func (b *TrustedBuilderID) MatchesLoose(builderID string, allowRef bool) error {
name, version, err := ParseBuilderID(builderID, false)
if err != nil {
return err
Expand All @@ -55,6 +55,32 @@ func (b *TrustedBuilderID) Matches(builderID string, allowRef bool) error {
return nil
}

// Matches matches the builderID string against the reference builderID.
// Both the name and versions are always verified.
func (b *TrustedBuilderID) MatchesFull(builderID string, allowRef bool) error {
name, version, err := ParseBuilderID(builderID, false)
if err != nil {
return err
}

if name != b.name {
return fmt.Errorf("%w: expected name '%s', got '%s'", serrors.ErrorMismatchBuilderID,
name, b.name)
}

if version != b.version {
// If allowRef is true, try the long version `refs/tags/<name>` match.
if allowRef &&
"refs/tags/"+version == b.version {
return nil
}
return fmt.Errorf("%w: expected version '%s', got '%s'", serrors.ErrorMismatchBuilderID,
version, b.version)
}

return nil
}

func (b *TrustedBuilderID) Name() string {
return b.name
}
Expand All @@ -64,6 +90,9 @@ func (b *TrustedBuilderID) Version() string {
}

func (b *TrustedBuilderID) String() string {
if b.version == "" {
return b.name
}
return fmt.Sprintf("%s@%s", b.name, b.version)
}

Expand Down
Loading

0 comments on commit 83d0fe1

Please sign in to comment.