AWS assume-role-with-web-identity
A Buildkite plugin to assume-role-with-web-identity using a Buildkite OIDC token before running the build command.
[!IMPORTANT] You will need to configure an appropriate OIDC identity provider in your AWS account with a Provider URL of
https://agent.buildkite.comand an Audience ofsts.amazonaws.com. This can be automated with Terraform. Then you can create a role to be assumed.
Example
Use the plugin in your steps like this:
steps:
- command: aws sts get-caller-identity
plugins:
- aws-assume-role-with-web-identity#v1.8.1:
role-arn: arn:aws:iam::AWS-ACCOUNT-ID:role/SOME-ROLE
This will call buildkite-agent oidc request-token --audience sts.amazonaws.com and exchange the resulting token for AWS credentials which are then added into the environment so tools like the AWS CLI will use the assumed role.
Configuration
role-arn (required, string)
ARN of the IAM role this plugin should assume.
audience (optional, string)
The audience (aud claim) requested in the Buildkite OIDC token. Passed as the value of the --audience parameter in the OIDC request.
Defaults to sts.amazonaws.com. Set a custom value when a role’s trust policy should require a specific audience — e.g. to scope a sensitive role so that a token minted for one role cannot be used to assume another (audience restriction). Note that the OIDC identity provider’s Audience list must include any custom value used.
role-session-name (optional, string)
The value of the role-session-name to pass with the STS request. This value can be referred to in assume-role policy, and will be recorded in Cloudtrail.
Defaults to buildkite-job-${BUILDKITE_JOB_ID}.
role-session-duration (optional, integer)
An integer number of seconds that the assumed role session should last. Passed as the value of the duration-seconds parameter in the STS request.
Defaults to 3600 (via the AWS CLI). When oidc-token-lifetime is not set, this value is also used as the OIDC token lifetime, capped at 7200 (2 hours), the maximum enforced by Buildkite.
oidc-token-lifetime (optional, integer)
An integer number of seconds that the buildkite oidc token should last. Passed as the value of the lifetime parameter in the OIDC request.
Defaults to 300 (via the BUILDKITE AGENT) unless role-session-duration is set, in which case it defaults to whichever is smaller: role-session-duration or 7200.
Note: Buildkite enforces a maximum OIDC token lifetime of 7200 (2 hours) — values above this will be rejected.
session-tags (optional, array)
A list of Buildkite OIDC claims to also pass to AWS as session tags. Session tags are available in the role session as aws:PrincipalTag/<claim> condition keys which are useful for attribute-based access control, and are recorded in AWS CloudTrail. You don’t need session tags to restrict which pipelines can assume a role. Using session tags requires sts:TagSession in the role’s trust policy. See Using AWS session tags for details.
Eg.
session-tags:
- organization_slug
- pipeline_slug
- build_branch
Defaults to “ (empty).
[!NOTE]
session-tagsrequires buildkite-agent v3.83.0 or better
region (optional, string)
Exports AWS_REGION and AWS_DEFAULT_REGION with the value you set. If not set
the values of AWS_REGION and AWS_DEFAULT_REGION will not be changed.
Note that and AWS_REGION is used by the AWS CLI v2 and most SDKs.
AWS_DEFAULT_REGION is included for compatibility with older SDKs and CLI
versions.
credential-name-prefix (optional, string)
The plugin supports a credential-name-prefix option to prefix the default AWS credential names with a specific string. This is useful when you have multiple AWS credentials in your environment and you want to avoid conflicts.
For example, by setting credential-name-prefix: MY_PREFIX_ the plugin will export MY_PREFIX_AWS_ACCESS_KEY_ID, MY_PREFIX_AWS_SECRET_ACCESS_KEY and MY_PREFIX_AWS_SESSION_TOKEN instead of the default AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN.
plugins:
- aws-assume-role-with-web-identity#v1.8.1:
role-arn: arn:aws:iam::111111111111:role/example-role
credential-name-prefix: MY_PREFIX_
chain-role-arn (optional, string)
ARN of a second IAM role to assume after the initial OIDC-based role assumption. Useful for multi-account setups where the OIDC provider only exists in one account (e.g. an Identity Account), but the target resources live in another account.
The typical pattern is: Buildkite OIDC → entry role in Identity Account → sts:AssumeRole into Target Account.
plugins:
- aws-assume-role-with-web-identity#v1.8.1:
role-arn: arn:aws:iam::IDENTITY-ACCOUNT-ID:role/CI-ENTRY-ROLE
chain-role-arn: arn:aws:iam::TARGET-ACCOUNT-ID:role/TARGET-ROLE
The chained sts:AssumeRole call uses the credentials obtained from the first hop, so the entry role must have sts:AssumeRole permission on the target role. The chained session uses the same role-session-name as the first one. The final exported credentials will be those of the chained role.
When credential-name-prefix is also set, the chained credentials are exported under the same prefix.
AWS limits sessions obtained through role chaining to a maximum of one hour, even when role-session-duration is longer. Jobs that run for longer than that will hit expired credentials.
The agent.buildkite.com:* trust policy conditions and session-tags only apply to the entry role. The target role can only trust the entry role, so any pipeline that can assume the entry role can chain into any role the entry role is allowed to assume. To keep this scoped:
- Limit the entry role’s
sts:AssumeRolepermission to the specific target role ARNs it needs. - Have each target role trust only the entry role.
credential-process (optional, boolean)
When true, the plugin does not export the assumed-role credentials as static environment variables. Instead it writes a self-contained credential_process helper script and an AWS config file referencing it, then exports AWS_CONFIG_FILE (and AWS_SDK_LOAD_CONFIG=1) so AWS SDKs and the AWS CLI pick it up.
AWS SDKs and the AWS CLI invoke the helper whenever credentials are needed. Each invocation requests a fresh Buildkite OIDC token and assumes the role again. The AWS CLI does not cache credential_process output across processes, so a script that runs aws repeatedly requests a token each time; SDKs cache the response inside the calling process until it nears expiry. The job keeps valid AWS credentials for its entire lifetime. This is useful for long-running jobs that would otherwise outlive a single role session — AssumeRoleWithWebIdentity sessions are capped at 12 hours, and static credentials exported at step start cannot be renewed.
plugins:
- aws-assume-role-with-web-identity#v1.8.1:
role-arn: arn:aws:iam::111111111111:role/example-role
role-session-duration: 43200
credential-process: true
Notes:
- The plugin still performs its usual assume-role call up front, so configuration errors (bad role ARN, trust policy, or session tags) fail fast at step start rather than on the first AWS call. It then runs
sts get-caller-identitythrough the generated config and fails the step if the resolved identity does not match the assumed role, so a credential source accidentally shadowing the config fails loudly instead of running as the wrong identity. - The helper invokes
buildkite-agent,aws, andjqby the absolute paths resolved when the hook ran, so those binaries must stay in place for the job’s lifetime. The job’sBUILDKITE_AGENT_ACCESS_TOKENmust also be available in the environment wherever AWS calls run — it already is for build commands. - Each helper invocation writes the fresh OIDC token to a
0600file and passes it to the AWS CLI asfile://..., so the token never appears in the process list. Stderr from those commands is discarded; AWS SDKs can logcredential_processstderr. - Static
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKEN, IRSA-styleAWS_ROLE_ARN/AWS_WEB_IDENTITY_TOKEN_FILE, and container credential variables (AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,AWS_CONTAINER_CREDENTIALS_FULL_URI,AWS_CONTAINER_AUTHORIZATION_TOKEN,AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE) are unset in this mode, andAWS_SHARED_CREDENTIALS_FILEis pointed at an empty file. All of those can outrankcredential_process, and clients that do not implementcredential_processwould otherwise keep an ECS task role or EKS Pod Identity. AWS_EC2_METADATA_DISABLED=trueis exported, so a tool that ignoresAWS_CONFIG_FILEfails instead of silently falling back to the node’s instance profile via IMDS. IMDS region lookup is disabled too. Set the pluginregionoption, orAWS_REGION, when the job needs a region.- The generated config replaces
AWS_CONFIG_FILEand contains onlycredential_process. Other settings from the previous AWS config are not copied. - The config is written for the active profile:
AWS_PROFILE, otherwiseAWS_DEFAULT_PROFILE, otherwise[default].AWS_PROFILE=defaultstill uses[default], because[profile default]matches nothing. - The temporary directory holding the helper and config file is removed by the plugin’s
pre-exithook when the job ends. - Cannot be combined with
credential-name-prefix. - With
chain-role-arn, the chainedsts:AssumeRolecall happens inside the helper, so chained sessions (capped at one hour by AWS) are refreshed automatically as well. The first hop’s keys are written to a0600credentials file for that call rather than passed in the environment.
hook (optional, string)
The hook to run. Can be either environment (the default) or pre-command.
Useful for when using a plugin such as the artifacts plugin that runs as a
pre-command hook, and you need that to run with the pipeline role before then
assuming your IAM role like so:
plugins:
- artifacts#v1.9.4:
download: "out/*"
- aws-assume-role-with-web-identity#v1.8.1:
hook: pre-command
role-arn: arn:aws:iam::111111111111:role/example-role
IAM Role Trust Policies
AWS reads a subset of the Buildkite OIDC claims directly from the token, so a role’s trust policy can restrict which Buildkite organizations and pipelines can assume it using agent.buildkite.com:<claim> condition keys. See OIDC with AWS in the Buildkite docs for a full walkthrough.
For example, given a Buildkite pipeline step like so:
steps:
- command: aws sts get-caller-identity
plugins:
- aws-assume-role-with-web-identity#v1.8.1:
role-arn: arn:aws:iam::111111111111:role/example-role
The following trust policy on the IAM role will permit a Buildkite job to assume the role if the job belongs to the organization ORG_SLUG with the UUID ORG_ID, and the pipeline PIPELINE_SLUG with the UUID PIPELINE_ID:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::111111111111:oidc-provider/agent.buildkite.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"agent.buildkite.com:sub": "organization:ORG_SLUG:pipeline:PIPELINE_SLUG:*"
},
"StringEquals": {
"agent.buildkite.com:aud": "sts.amazonaws.com",
"agent.buildkite.com:organization_slug": "ORG_SLUG",
"agent.buildkite.com:organization_id": "ORG_ID",
"agent.buildkite.com:pipeline_slug": "PIPELINE_SLUG",
"agent.buildkite.com:pipeline_id": "PIPELINE_ID"
}
}
}
]
}
-
AWS requires a condition on
agent.buildkite.com:subin every trust policy for a Buildkite OIDC provider. -
All conditions must match. To allow more than one value for a condition, list the values in an array, and any one of them can match. For example, this condition allows builds on either the
mainorproductionbranch:"agent.buildkite.com:build_branch": ["main", "production"] -
The
organization_idandpipeline_idUUIDs never change and can’t be reused by another organization or pipeline. -
The
organization_slugandpipeline_slugconditions are optional, and make it easier to see which organization and pipeline the policy applies to. If you include them, renaming the organization or pipeline causes role assumption to fail until you update the policy. -
For restricting access to a set of IP addresses, see Step 2 of OIDC with AWS.
-
For a complete list of supported condition keys, see Restricting access with Buildkite OIDC claims.
-
The
cluster_idandcluster_namecondition keys require optional claims. See Optional claims.
Optional claims
Optional claims, such as cluster_id and cluster_name, are only included in the OIDC token when requested. To request them, set the BUILDKITE_OIDC_TOKEN_CLAIMS environment variable on the step. The buildkite-agent oidc request-token command that this plugin runs reads this variable as its --claim option. Separate multiple claims with commas.
steps:
- label: ":aws: assume role with OIDC claim conditions"
command: aws sts get-caller-identity
env:
BUILDKITE_OIDC_TOKEN_CLAIMS: cluster_id
plugins:
- aws-assume-role-with-web-identity#v1.8.1:
role-arn: arn:aws:iam::123456789123:role/placeholder-role
Without these claims in the token, trust policies using agent.buildkite.com:cluster_id or agent.buildkite.com:cluster_name conditions will deny role assumption.
Session tags
You don’t need session-tags to restrict which pipelines can assume a role. Use them when you need claim values in the role session, for example, in aws:PrincipalTag/<claim> conditions for attribute-based access control, or in AWS CloudTrail records. The role’s trust policy must also allow sts:TagSession. See Using AWS session tags for details.
AWS configuration with Terraform
If you automate your infrastructure with Terraform, the following configuration will setup a valid OIDC IdP in AWS — adapted from an example for using OIDC with EKS:
locals {
agent_endpoint = "https://agent.buildkite.com"
}
data "tls_certificate" "buildkite-agent" {
url = local.agent_endpoint
}
resource "aws_iam_openid_connect_provider" "buildkite-agent" {
url = local.agent_endpoint
client_id_list = [
"sts.amazonaws.com",
]
thumbprint_list = [
data.tls_certificate.buildkite-agent.certificates[0].sha1_fingerprint,
]
}
By default, the OIDC token that this plugin requests has the following audience and subject, which the trust policy below matches:
- Audience:
sts.amazonaws.com, unless you set the plugin’saudienceoption - Subject:
organization:<ORG_SLUG>:pipeline:<PIPELINE_SLUG>:ref:<REF>:commit:<BUILD_COMMIT>:step:<STEP_KEY>, where<REF>is a Git ref such asrefs/heads/main
In addition to the aws_iam_openid_connect_provider the role being assumed should have a trust policy that can be defined like so.
Be sure to replace the <ORG_SLUG>, <ORG_ID>, <PIPELINE_SLUG> and <PIPELINE_ID> placeholders.
data "aws_iam_policy_document" "buildkite-oidc-assume-role-trust-policy" {
statement {
sid = "BuildkiteAssumeRole"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.buildkite-agent.arn]
}
condition {
test = "StringLike"
variable = "agent.buildkite.com:sub"
values = ["organization:<ORG_SLUG>:pipeline:<PIPELINE_SLUG>:*"]
}
condition {
test = "StringEquals"
variable = "agent.buildkite.com:aud"
values = ["sts.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "agent.buildkite.com:organization_slug"
values = ["<ORG_SLUG>"]
}
condition {
test = "StringEquals"
variable = "agent.buildkite.com:organization_id"
values = ["<ORG_ID>"]
}
condition {
test = "StringEquals"
variable = "agent.buildkite.com:pipeline_slug"
values = ["<PIPELINE_SLUG>"]
}
condition {
test = "StringEquals"
variable = "agent.buildkite.com:pipeline_id"
values = ["<PIPELINE_ID>"]
}
}
}
Compatibility
| Elastic Stack | Agent Stack K8s | Hosted (Mac) | Hosted (Linux) | Notes |
|---|---|---|---|---|
| ✅ | ❌ | ⚠️ | ⚠️ | k8s stack does not possess aws Hosted agents require AWS credentials on machine to perform aws command |
- ✅ Fully supported (all combinations of attributes have been tested to pass)
- ⚠️ Partially supported (some combinations cause errors/issues)
- ❌ Not supported