Skip to content

Commit

Permalink
feat: BYOB verification support (#604)
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>

---------

Signed-off-by: laurentsimon <laurentsimon@google.com>
  • Loading branch information
laurentsimon authored May 23, 2023
1 parent a86957c commit bda35e0
Show file tree
Hide file tree
Showing 7 changed files with 236 additions and 73 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/pre-submit.lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ jobs:
with:
go-version: "1.18"
- env:
GOLANGCI_LINT_VERSION: "1.46.2"
GOLANGCI_LINT_CHECKSUM: "242cd4f2d6ac0556e315192e8555784d13da5d1874e51304711570769c4f2b9b"
GOLANGCI_LINT_VERSION: "1.52.2"
GOLANGCI_LINT_CHECKSUM: "c9cf72d12058a131746edd409ed94ccd578fbd178899d1ed41ceae3ce5f54501"
run: |
set -euo pipefail
Expand Down
3 changes: 3 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ issues:
- EXC0013
- EXC0014
- EXC0015
exclude:
# TODO(#589): Remove after updating to Go 1.20
- "non-wrapping format verb for fmt.Errorf. Use `%w` to format errors"
# Maximum issues count per one linter.
# Set to 0 to disable.
# Default: 50
Expand Down
54 changes: 39 additions & 15 deletions verifiers/internal/gha/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,71 +67,95 @@ func VerifyCertficateSourceRepository(id *WorkflowIdentity,
func VerifyBuilderIdentity(id *WorkflowIdentity,
builderOpts *options.BuilderOpts,
defaultBuilders map[string]bool,
) (*utils.TrustedBuilderID, error) {
) (*utils.TrustedBuilderID, bool, error) {
// Issuer verification.
// NOTE: this is necessary before we do any further verification.
if id.Issuer != certOidcIssuer {
return nil, fmt.Errorf("%w: %s", serrors.ErrorInvalidOIDCIssuer, id.Issuer)
return nil, false, fmt.Errorf("%w: %s", serrors.ErrorInvalidOIDCIssuer, id.Issuer)
}

// cert URI path is /org/repo/path/to/workflow@ref
workflowPath := strings.SplitN(id.SubjectWorkflowRef, "@", 2)
if len(workflowPath) < 2 {
return nil, fmt.Errorf("%w: workflow uri: %s", serrors.ErrorMalformedURI, id.SubjectWorkflowRef)
return nil, false, fmt.Errorf("%w: workflow uri: %s", serrors.ErrorMalformedURI, id.SubjectWorkflowRef)
}

// Verify trusted workflow.
reusableWorkflowPath := strings.Trim(workflowPath[0], "/")
reusableWorkflowTag := strings.Trim(workflowPath[1], "/")
builderID, err := verifyTrustedBuilderID(reusableWorkflowPath, reusableWorkflowTag,
builderID, byob, err := verifyTrustedBuilderID(reusableWorkflowPath, reusableWorkflowTag,
builderOpts.ExpectedID, defaultBuilders)
if err != nil {
return nil, err
return nil, byob, err
}

// Verify the ref is a full semantic version tag.
if err := verifyTrustedBuilderRef(id, reusableWorkflowTag); err != nil {
return nil, err
return nil, byob, err
}

return builderID, nil
return builderID, byob, nil
}

// Verifies the builder ID at path against an expected builderID.
// If an expected builderID is not provided, uses the defaultBuilders.
func verifyTrustedBuilderID(certPath, certTag string, expectedBuilderID *string, defaultBuilders map[string]bool) (*utils.TrustedBuilderID, error) {
func verifyTrustedBuilderID(certPath, certTag string, expectedBuilderID *string, defaultTrustedBuilders map[string]bool) (*utils.TrustedBuilderID, bool, error) {
var trustedBuilderID *utils.TrustedBuilderID
var err error
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.
if expectedBuilderID == nil || *expectedBuilderID == "" {
if _, ok := defaultBuilders[certPath]; !ok {
return nil, fmt.Errorf("%w: %s got %t", serrors.ErrorUntrustedReusableWorkflow, certPath, expectedBuilderID == nil)
if _, ok := defaultTrustedBuilders[certPath]; !ok {
return nil, false, 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, true)
if err != nil {
return nil, err
return nil, false, err
}
} else {
// Verify the builderID.
// We only accept IDs on github.com.
trustedBuilderID, err = utils.TrustedBuilderIDNew(certBuilderName+"@"+certTag, true)
if err != nil {
return nil, err
return nil, false, err
}

// Check if:
// - the builder in the cert is a BYOB builder
// - the caller trusts the BYOB builder
// If both are true, we don't match the user-provided builder ID
// against the certificate. Instead that will be done by the caller.
if isTrustedDelegatorBuilder(trustedBuilderID, defaultTrustedBuilders) {
return trustedBuilderID, true, nil
}

// BuilderID provided by user should match the certificate.
// Not a BYOB builder. 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.MatchesLoose(*expectedBuilderID, true); err != nil {
return nil, fmt.Errorf("%w: %v", serrors.ErrorUntrustedReusableWorkflow, err)
return nil, false, fmt.Errorf("%w: %v", serrors.ErrorUntrustedReusableWorkflow, err)
}
}

return trustedBuilderID, nil
return trustedBuilderID, false, nil
}

func isTrustedDelegatorBuilder(certBuilder *utils.TrustedBuilderID, trustedBuilders map[string]bool) bool {
for byobBuilder := range defaultBYOBReusableWorkflows {
// Check that the certificate builder is a BYOB workflow.
if err := certBuilder.MatchesLoose(httpsGithubCom+byobBuilder, true); err == nil {
// We found a delegator workflow that matches the certificate identity.
// Check that the BYOB builder is trusted by the caller.
if _, ok := trustedBuilders[byobBuilder]; !ok {
return false
}
return true
}
}
return false
}

// Only allow `@refs/heads/main` for the builder and the e2e tests that need to work at HEAD.
Expand Down
Loading

0 comments on commit bda35e0

Please sign in to comment.