-
Notifications
You must be signed in to change notification settings - Fork 140
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
Update Buildkite issuer to include some of the new certificate extensions #1307
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,9 +17,10 @@ package buildkite | |
import ( | ||
"context" | ||
"crypto/x509" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"net/url" | ||
"strconv" | ||
|
||
"github.com/coreos/go-oidc/v3/oidc" | ||
"github.com/sigstore/fulcio/pkg/certificate" | ||
|
@@ -35,16 +36,38 @@ type jobPrincipal struct { | |
// https://agent.buildkite.com/.well-known/openid-configuration | ||
issuer string | ||
|
||
// The URL of the pipeline, the container of many builds. This will be | ||
// set as a human-friendly SubjectAlternativeName URI in the certificate. | ||
// Buildkite's domain | ||
url string | ||
|
||
// Unique identifier for a Buildkite customer | ||
organizationSlug string | ||
|
||
// Unique identifier (within the scope of an OrganizationSlug) for a container of many builds. | ||
pipelineSlug string | ||
|
||
// Incrementing number within each pipeline | ||
buildNumber int64 | ||
|
||
// The commit sha being tested by a build | ||
buildCommit string | ||
|
||
// UUID that identifies a single unique execution within a build | ||
jobId string | ||
|
||
// Did the job run in a cloud hosted environment or self hosted by the customer. All | ||
// Buildkite jobs execute on self hosted agents, so this will always be `self-hosted` | ||
runnerEnvironment string | ||
} | ||
|
||
func JobPrincipalFromIDToken(_ context.Context, token *oidc.IDToken) (identity.Principal, error) { | ||
var claims struct { | ||
OrganizationSlug string `json:"organization_slug"` | ||
PipelineSlug string `json:"pipeline_slug"` | ||
OrganizationSlug string `json:"organization_slug"` | ||
PipelineSlug string `json:"pipeline_slug"` | ||
BuildNumber json.Number `json:"build_number"` | ||
BuildCommit string `json:"build_commit"` | ||
JobId string `json:"job_id"` | ||
} | ||
|
||
if err := token.Claims(&claims); err != nil { | ||
return nil, err | ||
} | ||
|
@@ -57,10 +80,29 @@ func JobPrincipalFromIDToken(_ context.Context, token *oidc.IDToken) (identity.P | |
return nil, errors.New("missing pipeline_slug claim in ID token") | ||
} | ||
|
||
buildNumber, err := claims.BuildNumber.Int64() | ||
if err != nil { | ||
return nil, errors.New("error parsing build_number claim in ID token") | ||
} | ||
|
||
if claims.BuildCommit == "" { | ||
return nil, errors.New("missing build_commit claim in ID token") | ||
} | ||
|
||
if claims.JobId == "" { | ||
return nil, errors.New("missing job_id claim in ID token") | ||
} | ||
|
||
return &jobPrincipal{ | ||
subject: token.Subject, | ||
issuer: token.Issuer, | ||
url: fmt.Sprintf("https://buildkite.com/%s/%s", claims.OrganizationSlug, claims.PipelineSlug), | ||
subject: token.Subject, | ||
issuer: token.Issuer, | ||
url: "https://buildkite.com", | ||
organizationSlug: claims.OrganizationSlug, | ||
pipelineSlug: claims.PipelineSlug, | ||
buildNumber: buildNumber, | ||
buildCommit: claims.BuildCommit, | ||
jobId: claims.JobId, | ||
runnerEnvironment: "self-hosted", | ||
}, nil | ||
} | ||
|
||
|
@@ -69,16 +111,23 @@ func (p jobPrincipal) Name(_ context.Context) string { | |
} | ||
|
||
func (p jobPrincipal) Embed(_ context.Context, cert *x509.Certificate) error { | ||
// Set SubjectAlternativeName to the pipeline URL on the certificate | ||
parsed, err := url.Parse(p.url) | ||
baseURL, err := url.Parse(p.url) | ||
if err != nil { | ||
return err | ||
} | ||
cert.URIs = []*url.URL{parsed} | ||
|
||
pipelineUrl := baseURL.JoinPath(p.organizationSlug, p.pipelineSlug) | ||
jobUrl := baseURL.JoinPath(p.organizationSlug, p.pipelineSlug, "builds", strconv.FormatInt(p.buildNumber, 10)+"#"+p.jobId) | ||
|
||
// Set SubjectAlternativeName to the pipeline URL on the certificate | ||
cert.URIs = []*url.URL{pipelineUrl} | ||
|
||
// Embed additional information into custom extensions | ||
cert.ExtraExtensions, err = certificate.Extensions{ | ||
Issuer: p.issuer, | ||
Issuer: p.issuer, | ||
RunInvocationURI: jobUrl.String(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the one I'm most interested in - having the specific job URL in the certificate to provide provenance would be excellent There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @yob I think it makes sense, we went job-specific for the GitLab provider: https://github.com/sigstore/fulcio/blob/04e4ac9b2d867d0f94e58a858f373a831d87653d/pkg/identity/gitlabcom/principal.go#L233C63-L233C63 Some related discussion in #1182. I found it confusing that this extension was named GitHub does not have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 job url makes sense to me. "run invocation" terminology was cribbed from the slsa spec. |
||
RunnerEnvironment: p.runnerEnvironment, | ||
SourceRepositoryDigest: p.buildCommit, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would love to see support for a few additional extensions so we could get richer provenance info:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Buildkite has no access to the source repository. Because of this, Similarly for "build config" beyond those initial steps is not an immutable input to a build for us, but an output. (There are ways to make it immutable, but it's not the default, and not something we can currently provide at platform level.)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (I'd love to see the contents of this PR land as an increment, and consider additional extensions later.) |
||
}.Render() | ||
if err != nil { | ||
return err | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this support a use case to write a verification policy to only allow builds from a particular buildkite project/pipeline/thing. Looks like this identifier would be stable across jobs but unique to the parent resource responsible for running the job. Could you have different build's from different repos running with the same org and pipeline slug?
I would also use this value to populate
BuildSignerURI
as these are currently mirrored for github and gitlab. If a pipeline slug is always configured from a pipeline.yml and we know the sha of the pipleline file we could also use this to populateBuildConfigDigest
andBuildSignerDigest
.