Working with Buildkite agents

A job running on a Buildkite agent can use workload identity federation to exchange its OIDC token for a short-lived Buildkite API access token. The job doesn't need a stored API access token.

Create the workload identity

Create a workload identity as described in Create a workload identity, and select Buildkite Agents as its Provider. The preset fills in the following settings:

  • OIDC issuer: https://agent.buildkite.com.
  • OIDC subject: organization:acme-inc:pipeline:<pipeline-slug>:ref:refs/heads/main:commit:*:step:*, with your Buildkite organization's slug in place of acme-inc. Replace <pipeline-slug> with your pipeline's slug. This subject matches every job in builds of the pipeline's main branch.
  • Claims: An organization_id claim with your Buildkite organization's UUID. Keep it. Buildkite agents in every Buildkite organization share the same issuer, so Buildkite requires an organization_id or organization_slug claim.

Exchange the token in a job

A job requests an OIDC token with the buildkite-agent oidc request-token command, using the workload identity's OIDC audience, then exchanges it for an API access token:

pipeline.yml
steps:
  - label: "Call the Buildkite API"
    env:
      BUILDKITE_CLIENT_ID: "YOUR_CLIENT_ID"
      BUILDKITE_AUDIENCE: "YOUR_OIDC_AUDIENCE"
    command: |
      OIDC_TOKEN="$$(buildkite-agent oidc request-token --audience "$$BUILDKITE_AUDIENCE")"

      BUILDKITE_API_TOKEN="$$(curl -sSf https://buildkite.com/oauth/token \
        -d "grant_type=client_credentials" \
        -d "client_id=$$BUILDKITE_CLIENT_ID" \
        -d "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
        -d "client_assertion=$$OIDC_TOKEN" | jq -r .access_token)"

      curl -sSf -H "Authorization: Bearer $$BUILDKITE_API_TOKEN" \
        https://api.buildkite.com/v2/access-token

Replace YOUR_CLIENT_ID and YOUR_OIDC_AUDIENCE with the workload identity's Client ID and OIDC audience. The last command calls the current token API, which needs no scope.

Run buildkite-agent oidc request-token again before each exchange, including retries, because a workload identity accepts each OIDC token only once. Learn more in Get a new OIDC token for each exchange.

The API access token expires no later than the OIDC token, which lasts five minutes by default. If a job calls the API for longer, either set --lifetime to how long the job needs the API access token, up to 3600 seconds, or request and exchange a new OIDC token before the API access token expires. Learn more in Token lifetime.

Limit which jobs can exchange tokens

Write claim conditions on claims in the job's OIDC token. These claims are the most useful. The OIDC token claims reference lists every claim.

Claim Value Use it to
Claim organization_id Value UUID of the Buildkite organization Use it to Accept only jobs from your Buildkite organization. Required in Claims, unless you use organization_slug.
Claim pipeline_id Value UUID of the pipeline Use it to Accept jobs from specific pipelines.
Claim build_branch Value Branch of the build, for example, main Use it to Accept builds of specific branches.
Claim build_tag Value Tag of the build, only when it has one Use it to Accept only tagged builds, such as releases.
Claim step_key Value key of the step, or null Use it to Accept only specific steps, such as a deploy step.
Claim build_source Value ui, api, webhook, trigger_job, or schedule Use it to Accept only builds created in specific ways.
Claim runner_environment Value buildkite-hosted or self-hosted Use it to Accept only jobs on Buildkite hosted agents, or only on your own agents.

For example, this claim condition only accepts the deploy step of one pipeline, in builds of its main branch:

claims.pipeline_id == "00000000-0000-4000-8000-000000000002"
  && claims.build_branch == "main"
  && claims.step_key == "deploy"

Prefer IDs to slugs, such as pipeline_slug. Renaming a pipeline or organization changes its slug, and someone else can then use the old slug.

Claims such as cluster_id, queue_id, and queue_key are only in the token when the job requests them with --claim. For example, to accept only jobs on the deploy queue, request the queue_key claim:

buildkite-agent oidc request-token --audience "$BUILDKITE_AUDIENCE" --claim queue_key

Then check it in the claim condition:

claims.queue_key == "deploy"