OIDC with AWS
The Buildkite Agent's oidc command allows you to request an Open ID Connect (OIDC) token containing claims about the current pipeline and its job. These tokens can be consumed by AWS and exchanged for an Identity and Access Management (IAM) role with AWS-scoped permissions.
This process uses the following Buildkite plugins to implement OIDC with AWS and your Buildkite pipelines:
Learn more about:
How OIDC tokens are constructed and how to extract and use claims in the OpenID Connect Core documentation.
Amazon's implementation of OIDC with their federated system in Create an OpenID Connect (OIDC) identity provider in IAM of the AWS IAM User Guide.
Step 1: Set up an OIDC provider in your AWS account
First, you'll need to set up an IAM OIDC provider in your AWS account.
Learn more about how to do this in the Create an OpenID Connect (OIDC) identity provider in IAM page of the AWS IAM User Guide.
On this page, as part of the Creating and managing an OIDC provider (console) process, specify the following values for the:
Provider URL:
https://agent.buildkite.comAudience:
sts.amazonaws.com
Step 2: Create a new (or update an existing) IAM role to use with your pipelines
Creating new or updating existing IAM roles is conducted through your AWS account.
Learn more about how to do this in the Creating a role using custom trust policies (console) page of the AWS IAM User Guide.
As part of this process:
Choose the Custom trust policy role type.
-
Copy the following example trust policy in the following JSON code block and paste it into a code editor:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::AWS_ACCOUNT_ID:oidc-provider/agent.buildkite.com" }, "Action": [ "sts:TagSession", "sts:AssumeRoleWithWebIdentity" ], "Condition": { "StringLike": { "agent.buildkite.com:sub": "organization:ORGANIZATION_SLUG:pipeline:PIPELINE_SLUG:ref:REF:commit:BUILD_COMMIT:step:STEP_KEY" }, "StringEquals": { "agent.buildkite.com:aud": "sts.amazonaws.com", "aws:RequestTag/organization_slug": "ORGANIZATION_SLUG", "aws:RequestTag/organization_id": "ORGANIZATION_ID", "aws:RequestTag/pipeline_slug": "PIPELINE_SLUG" }, "IpAddress": { "aws:SourceIp": [ "AGENT_PUBLIC_IP_ONE", "AGENT_PUBLIC_IP_TWO" ] } } } ] }Learn more about creating custom trust policies in Creating IAM policies of the AWS IAM User Guide.
-
Modify the
Principalsection of the pasted code snippet accordingly:- Ensure that this is set to
Federated, and points to theoidc-providerAmazon Resource Name (ARN) from the Provider URL you configured above (that is,agent.buildkite.com). - Change
AWS_ACCOUNT_IDto your actual AWS account ID.
- Ensure that this is set to
-
Modify the
Conditionsection of the code snippet accordingly:- Ensure the
StringLikesubsection'sagent.buildkite.com:subfield name has at least one value that matches the format:organization:ORGANIZATION_SLUG:pipeline:PIPELINE_SLUG:ref:REF:commit:BUILD_COMMIT:step:STEP_KEY. You can choose to wildcard sections of this string to make your trust policy more permissive, e.g.organization:acme-inc:*will match for any invocation of pipeline in Buildkite organization "acme-inc". Buildkite recommends that the subject claim is used to narrow the trust policy scope to a Buildkite organization, andaws:RequestTagstyle claims to be used to further narrow the trust policy scope e.g. to a pipeline.aws:RequestTagstyle claims allow you to specify immutable UUIDs in your trust policy. Note that AWS requires theagent.buildkite.com:subclaim to be specified in the trust policies associated with IAM roles using a Buildkite OIDC provider federated principal. - Ensure the
StringEqualssubsection's audience field name has a value that matches the Audience you configured above (that is,sts.amazonaws.com). The audience field name is your provider URL appended by:aud—agent.buildkite.com:aud. -
Ensure the
StringEqualssubsection'sRequestTagfields have values match the Buildkite pipeline that will use this role. Buildkite strongly recommends using the immutable UUIDs in your trust policy. When formulating such values, the following constituent field's value:-
ORGANIZATION_SLUGcan be obtained:- From the end of your Buildkite URL, after accessing Pipelines in the global navigation of your organization in Buildkite.
-
By running the List organizations REST API query to obtain this value from
slugin the response. For example:curl - X GET "https://api.buildkite.com/v2/organizations" \ -H "Authorization: Bearer $TOKEN" From the
BUILDKITE_ORGANIZATION_SLUGvalue displayed on theEnvironmenttab of any job that ran in the organization.
-
ORGANIZATION_IDis a UUID and can be obtained:- By running the same List organizations REST API query used to obtain
ORGANIZATION_SLUG. - From the
BUILDKITE_ORGANIZATION_IDvalue displayed on theEnvironmenttab of any job that ran in the organization.
- By running the same List organizations REST API query used to obtain
-
PIPELINE_SLUG(optional) can be obtained:- From the end of your Buildkite URL, after accessing Pipelines in the global navigation of your organization in Buildkite, then accessing the specific pipeline to be specified in the custom trust policy.
-
By running the List pipelines REST API query to obtain this value from
slugin the response from the specific pipeline. For example:curl - X GET "https://api.buildkite.com/v2/organizations/{org.slug}/pipelines" \ -H "Authorization: Bearer $TOKEN"
-
- Ensure the
-
If you have dedicated/static public IP addresses and wish to implement defense in depth against an attacker stealing an OIDC token to access your cloud environment, retain the
Conditionsection'sIpAddresssubsection, and modify its values (AGENT_PUBLIC_IP_ONEandAGENT_PUBLIC_IP_TWO) with a list of your agent's IP addresses or CIDR range or block.Only OIDC token exchange requests (for IAM roles) from Buildkite Agents with these IP addresses will be permitted.
-
Verify that your custom trust policy is complete. The following example trust policy (noting that
AWS_ACCOUNT_IDhas not been specified) will only allow the exchange of an agent's OIDC tokens with IAM roles when:- The Buildkite organization is
example-org, with an ID ofab3883b1-9596-4312-a09c-4527ae997ba7. - The Buildkite pipeline is
example-pipeline. - On Buildkite Agents whose IP addresses are either
192.0.2.0or198.51.100.0.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::AWS_ACCOUNT_ID:oidc-provider/agent.buildkite.com" }, "Action": [ "sts:TagSession", "sts:AssumeRoleWithWebIdentity" ], "Condition": { "StringLike": { "agent.buildkite.com:sub": "organization:example-org:*" }, "StringEquals": { "agent.buildkite.com:aud": "sts.amazonaws.com", "aws:RequestTag/organization_slug": "example-org", "aws:RequestTag/organization_id": "b3883b1-9596-4312-a09c-4527ae997ba7", "aws:RequestTag/pipeline_slug": "example-pipeline" } "IpAddress": { "aws:SourceIp": [ "192.0.2.0", "198.51.100.0" ] } } } ] }Note: AWS requires that the
subclaim is matched for all trust policies used with OIDC in Buildkite Pipelines. Therefore, it is recommended that you use thesubclaim to match your Buildkite organization, and then useaws:RequestTagconditions for more granular trust policy restrictions, as demonstrated in the example above. - The Buildkite organization is
In the Custom trust policy section, copy your modified custom trust policy, paste it into your IAM role, and complete the next few steps up to specifying the Role name.
Specify an appropriate Role name, for example,
example-pipeline-oidc-for-ssm, and complete the remaining steps.
Step 3: Configure your IAM role with AWS actions
Add an inline or managed IAM policy (separate to the custom trust policy configured above) to allow the IAM role to perform any actions your pipeline needs. Learn more about how to do this in Managed policies and inline policies of the AWS IAM User Guide.
Common examples are permissions to read secrets from SSM and push images to ECR, although this would depend on the purpose of your pipeline.
In the following example, we'll allow access to read an SSM Parameter Store key named /pipelines/example-pipeline/oidc-for-ssm/example-deploy-key by attaching the following inline policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ssm:GetParameters"
],
"Resource": "arn:aws:ssm:us-east-1:012345678910:parameter/pipelines/example-pipeline/oidc-for-ssm/example-deploy-key"
}
]
}
Step 4: Configure your pipeline to assume the role
Finally, use the two Buildkite plugins to use the IAM role and to pull in the SSM parameter (added above):
Incorporate the following into your pipeline (modifying as required):
agents:
queue: mac-small
steps:
- label: ":aws: Deploy to Production"
key: deploy-to-production
command: echo "Example Deploy Key equals \$EXAMPLE_DEPLOY_KEY"
env:
AWS_DEFAULT_REGION: us-east-1
AWS_REGION: us-east-1
plugins:
- aws-assume-role-with-web-identity#v1.2.0:
role-arn: arn:aws:iam::012345678910:role/example-pipeline-oidc-for-ssm
session-tags:
- organization_slug
- organization_id
- pipeline_slug
- aws-ssm#v1.0.0:
parameters:
EXAMPLE_DEPLOY_KEY: /pipelines/example-pipeline/oidc-for-ssm/example-deploy-key
The backslash (\) before $EXAMPLE_DEPLOY_KEY in the example above prevents this environment variable from being interpolated during the pipeline's upload to Buildkite Pipelines. You could alternatively use a $ symbol for this purpose (resulting in $$EXAMPLE_DEPLOY_KEY).
AWS CloudTrail
A Buildkite job that successfully assumes an AWS IAM Role using this pattern will leave a record in AWS CloudTrail. That record will include details like the IP address of the agent that ran the job, plus the values for any of the session-tags that were listed in the pipeline.yml.
Here is a fragment of an AWS CloudTrail event with the relevant tags:
{
"eventVersion": "1.08",
"userIdentity": {
"type": "WebIdentityUser",
"principalId": "arn:aws:iam::AWS_ACCOUNT_ID:oidc-provider/agent.buildkite.com:sts.amazonaws.com:organization:example-org:pipeline:example-pipeline:ref:refs/heads/main:commit:1da177e4c3f41524e886b7f1b8a0c1fc7321cac2:step:",
"userName": "organization:example-org:pipeline:example-pipeline:ref:refs/heads/main:commit:1da177e4c3f41524e886b7f1b8a0c1fc7321cac2:step:",
"identityProvider": "arn:aws:iam::AWS_ACCOUNT_ID:oidc-provider/agent.buildkite.com"
},
"eventTime": "2025-02-18T13:34:48Z",
"eventSource": "sts.amazonaws.com",
"eventName": "AssumeRoleWithWebIdentity",
"awsRegion": "us-east-1",
"sourceIPAddress": "192.0.2.0",
"userAgent": "aws-cli/2.13.0 Python/3.11.4 Linux/6.7.12 exe/x86_64.ubuntu.22 prompt/off command/sts.assume-role-with-web-identity",
"requestParameters": {
"principalTags": {
"pipeline_slug": "example-pipeline",
"organization_id": "ab3883b1-9596-4312-a09c-4527ae997ba7",
"organization_slug": "example-org"
},
"roleArn": "arn:aws:iam::AWS_ACCOUNT_ID:role/example-pipeline-oidc-for-ssm",
"roleSessionName": "buildkite-job-01951944-87df-428f-ad92-90709ee78a59"
},
...
}
Including the build branch in your custom trust policy
When creating a custom trust policy for your IAM role, you can include the build branch within this policy. However, be aware that doing so comes with potential risks, since this doesn't necessarily guarantee that the entire build will be run from the branch defined in the policy. For instance, the policy might allow a build to commence off the main branch. However, the next step of the pipeline might check out a different branch and run the remainder of the pipeline's build from that branch.
Nevertheless, being aware of these risks, if you do wish to include the build branch in your custom trust policy, you can do so by making the following modifications to the steps above.
-
When defining your trust policy in the code editor, add the
RequestTag/build_branchentry to yourConditionsection'sStringEqualssubsection:... "Condition": { "StringEquals": { ... "aws:RequestTag/build_branch": "BRANCH_NAME" } ...where
BRANCH_NAMEis usually replaced withmainto initially restrict the IAM role's access to themainbranch. If thisRequestTagcondition is omitted, the role can initially be assumed by a build on any branch. -
When configuring your pipeline to use the IAM role, ensure
build_branchis included in the AWS assume-role-with-web-identitypluginsattribute'ssession-tagsvalue, for example:steps: - ... plugins: - aws-assume-role-with-web-identity#v1.2.0: role-arn: arn:aws:iam::012345678910:role/example-pipeline-oidc-for-ssm session-tags: - ... - build_branch
Note also that the build_branch property and value is also included in AWS CloudTrail events:
{
...
"requestParameters": {
"principalTags": {
...
"build_branch": "main"
},
...
},
...
}