OIDC in Buildkite Package Registries
Open ID Connect (OIDC) is an authentication protocol based on the OAuth 2.0 framework. With OIDC, one system or service issues an OIDC token, which is a signed JSON Web Token (JWT) containing metadata (or claims) about a user or object. This token can be consumed by another service (which may be offered by a third-party or by the same organization) to authenticate the user or object. An OIDC policy configured on this other service defines which OIDC tokens, based on their claims (also known as asserted claims) are permitted to perform the actions. If the OIDC token's asserted claims comply with those of the OIDC policy configured in the other service, the token is authenticated and the service issuing the token is permitted to perform its actions on the other service.
You can configure Buildkite registries with OIDC policies that allow access using OIDC tokens issued by Buildkite Agents and other OIDC identity providers. This is similar to how third-party products and services can be configured with OIDC policies to consume Buildkite Agent OIDC tokens for specific pipeline jobs, for deployment, or access management and security purposes.
A Buildkite Agent's OIDC tokens assert claims about the slugs of the pipeline it is building and organization that contains this pipeline, the ID of the job that created the token, as well as other claims, such as the name of the branch used in the build, the SHA of the commit that triggered the build, and the agent ID. If the token's claims do not comply with the registry's OIDC policy, the OIDC token is rejected, and any actions attempted with that token will fail. If the claims do comply, however, the OIDC token will have read and write access to packages in the registry.
The Buildkite Agent's oidc
command allows you to request an OIDC token from Buildkite containing claims about the pipeline's current job. These tokens can then be used by a Buildkite registry to determine (through its OIDC policy) if the organization, pipeline and any other metadata associated with the pipeline and its job are permitted to publish/upload packages to this registry.
OIDC token requirements
All Buildkite registries defined with an OIDC policy, require the following claims from an OIDC token (unless indicated as optional), regardless of the OIDC identity provider that issued the token.
Claim | Value |
---|---|
iat (issued at) |
Must be a UNIX timestamp in the past. |
nbf (not before) (Optional) |
If present, must be a UNIX timestamp in the past. |
exp (expiration time) |
Must be a UNIX timestamp in the future. The OIDC token's lifespan—that is, the exp minus the iat timestamp values—cannot be greater than 5 minutes. |
aud (audience) |
Must be equal to the registry's canonical URL, which has the format https://packages.buildkite.com/{org.slug}/{registry.slug} . |
When generating an OIDC token from:
A Buildkite Agent, the
--audience
option must explicitly be specified with the required value, whereasiat
,nbf
andexp
claims will automatically be included in the token.Another OIDC identity provider, ensure that its OIDC tokens contain these required claims. This should be the case by default, but if not, consult the relevant documentation for your OIDC identity provider on how to include these claims in the OIDC tokens it issues.
Define an OIDC policy for a registry
You can specify an OIDC policy for your Buildkite registry, which defines the criteria for which OIDC tokens, from the Buildkite Agent or another OIDC identity provider, will be accepted by your registry and authenticate a package publication/upload action from that system.
To define an OIDC policy for one or more Buildkite pipeline jobs in a registry:
Select Packages in the global navigation to access the Registries page.
Select the registry whose OIDC policy needs defining.
Select Settings > OIDC Policy to access the registry's OIDC Policy page.
In the Policy field, specify this using the following Basic OIDC policy format, or one based on a more complex example.
Learn more about how an OIDC policy for a registry is constructed in Policy structure and behavior.
Basic OIDC policy format
The basic format for a Buildkite registry's OIDC policy, to handle OIDC tokens issued by a Buildkite Agent is:
- iss: https://agent.buildkite.com
claims:
organization_slug: organization-slug
pipeline_slug: pipeline-slug
build_branch: main
where:
-
iss
(the issuer) must behttps://agent.buildkite.com
, representing the Buildkite Agent. -
the
claims:
field contains:-
organization-slug
, which can be obtained from the end of your Buildkite URL, after accessing Packages or Pipelines in the global navigation of your organization in Buildkite. -
pipeline-slug
, which can be obtained from the end of your Buildkite URL, after accessing Pipelines in the global navigation of your organization in Buildkite. -
main
or whichever branch of the repository you want to restrict package publication/uploads from pipeline builds.
-
However, more complex OIDC policies can be created.
Complex OIDC policy example
The following OIDC policy for a Buildkite registry contains two statements—one for a registry in Package Registries and another for GitHub Actions.
- iss: https://agent.buildkite.com
claims:
organization_slug:
equals: your-org
pipeline_slug:
in:
- one-pipeline
- another-pipeline
build_branch:
matches:
- main
- feature/*
not_equals: feature/not-this-one
- iss: https://token.actions.githubusercontent.com
claims:
repository:
matches: your-org/*
actor:
in:
- deploy-bot
- revert-bot
The first statement allows OIDC tokens representing a pipeline's job being built by a Buildkite Agent, but only when all of the following is true for the tokens' claims:
- The organization slug is
your-org
- The pipeline slug is either
one-pipeline
oranother-pipeline
- The build branch is either
main
or matches afeature/*
branch
The second statement allows OIDC tokens representing a GitHub Actions workflow, but only when all of the following is true for the tokens' claims:
- The repositories match
your-org/*
- The actor is either
deploy-bot
orrevert-bot
Policy structure and behavior
OIDC policy statements in Buildkite Package Registries are defined as a YAML- or JSON-formatted list, each of which includes a token issuer from an OIDC identity provider, along with a map of claim rules.
If an OIDC token's claims match both the token issuer and all claim rules defined by any statement within a registry's OIDC policy, then the token is accepted and the OIDC identity provider that issued the token is granted access to the registry. If no statements of the OIDC policy match, the token is rejected, and no registry access is granted.
When using YAML to define an OIDC policy, only simple YAML syntax is accepted—that is, YAML containing only scalar values, maps, and lists. Complex YAML syntax and features, such as anchors, aliases, and tagged values are not supported.
Statements
A statement defines a list of claim rules for a particular token issuer within an OIDC policy, where a token issuer is typically determined by an OIDC identity provider.
Each statement in the policy must contain contain a token issuer (iss
) field, whose value is determined by the OIDC identity provider, and permits OIDC tokens from that token issuer. While multiple statements are typically used to allow access from multiple token issuers (that is, one statement per issuer), more than one statement can also be defined for a single issuer or OIDC identity provider to handle more complex claim rule scenarios.
A statement must also contain a claims
field, which is a map of claim rules.
Currently, only OIDC tokens from the following token issuers are supported.
Token issuer name | The token issuer (iss ) value |
Relevant documentation link |
---|---|---|
Buildkite | https://agent.buildkite.com |
Buildkite Agent oidc command |
GitHub Actions | https://token.actions.githubusercontent.com |
GitHub Actions OIDC Tokens |
CircleCI |
https://oidc.circleci.com/org/$ORG where $ORG is your organization name |
CircleCI OIDC Tokens |
If you'd like to use OIDC tokens from a different token issuer or OIDC identity provider with Buildkite Package Registries, please contact support.
Claim rules
A statement contains a claims
field, which in turn contains a map of claim rules, where the rule's key is the name of the claim being verified, and the rule's value is the actual rule used to verify this claim. Each rule is a map of matchers, which are used to match a claim value in an OIDC token.
If at least one claim rule defined within an OIDC policy's statement is missing from an OIDC token and no other statements in that policy have complete matches with the token's claims, then the token is rejected. When a claim rule contains multiple matchers—such as the build_branch
claim rule in the complex example above—all of the rule's matchers must match a claim in the token for it to be granted registry access. In the build_branch
example above, this means that the token must have a build_branch
claim whose value is either main
or begins with feature/
, but whose value is not feature/not-this-one
.
Be aware that this means some combinations of matchers used in a claim rule may never match an OIDC token's claims. For example, the following OIDC policy statement will always reject a token, since the token's build_branch
claim cannot be both equal to main
and not equal to main
at the same time:
- iss: https://agent.buildkite.com
claims:
build_branch:
equals: main
not_equals: main
Claim rule matchers
The following matchers can be used within a claim rule.
Matcher | Argument type | Description |
---|---|---|
equals |
Scalar | The claim value must be exactly equal to the argument. |
not_equals |
Scalar | The claim value must not be exactly equal to the argument. |
in |
List of scalars | The claim value must be in the list of arguments. |
not_in |
List of scalars | The claim value must not be in the list of arguments. |
matches |
List of glob strings OR a single glob string | The claim value must match at least one of the globs provided. Note that this matcher is only applied when the claim value is a string, and is ignored otherwise. |
Argument type details:
A scalar is a single value, which must be a String, Number (float or integer), Boolean, or Null.
A glob string is a string that may contain wildcards, such as
*
or?
, which match zero or more characters, or a single character respectively. Glob strings are not regular expressions, and do not support the full range of features that regular expressions do.
As a special case, if a claim rule in its entirety is a scalar, it is treated as if it were a rule with the equals
matcher. This means that the following two claim rules are equivalent:
organization_slug: your-org
# is equivalent to
organization_slug:
equals: your-org
Configure a Buildkite pipeline to authenticate to a registry
Configuring a Buildkite pipeline command
step to request an OIDC token from Buildkite to interact with your Buildkite registry configured with an OIDC policy, is a two-part process.
Part 1: Request an OIDC token from Buildkite
To do this, use the following buildkite-agent oidc
command:
buildkite-agent oidc request-token --audience "https://packages.buildkite.com/{org.slug}/{registry.slug}" --lifetime 300
where:
-
--audience
is the target system that consumes this OIDC token. For Buildkite Package Registries, this value must be based on the URLhttps://packages.buildkite.com/{org.slug}/{registry.slug}
.
-
{org.slug}
can be obtained from the end of your Buildkite URL, after accessing Packages or Pipelines in the global navigation of your organization in Buildkite.
{registry.slug}
is the slug of your registry, which is the kebab-case version of your registry name, and can be obtained after accessing Packages in the global navigation > your registry from the Registries page.--lifetime
is the time (in seconds) that the OIDC token is valid for. By default, this value must be less than300
.
Part 2: Authenticate the registry with the OIDC token
To do this (using Docker as an example), authenticate the registry with the OIDC token obtained in part 1 by piping the output through to the docker login
command:
docker login packages.buildkite.com/{org.slug}/{registry.slug} --username buildkite --password-stdin
where:
{org.slug}
and{registry.slug}
are the same as the values used in thebuildkite-agent oidc request-token
command.--username
always has the valuebuildkite
.
Therefore, the full command
step would look like:
buildkite-agent oidc request-token --audience "https://packages.buildkite.com/{org.slug}/{registry.slug}" --lifetime 300 | docker login packages.buildkite.com/{org.slug}/{registry.slug} --username buildkite --password-stdin
Assuming a Buildkite organization with slug my-organization
and a pipeline slug my-pipeline
, this full command would look like:
buildkite-agent oidc request-token --audience "https://packages.buildkite.com/my-organization/my-pipeline" --lifetime 300 | docker login packages.buildkite.com/my-organization/my-pipeline --username buildkite --password-stdin
Example pipeline
The following example Buildkite pipeline YAML snippet demonstrates how to push Docker images to a Buildkite registry using OIDC token authentication: