OIDC: AWS OIDC Federation
Introduction
In my previous posts, I briefly mentioned the important topic of zero static credentials. This is especially relevant today, as interactions with AI agents in protected environments can unexpectedly expose credentials.
In this part, I explain how to authenticate my GitHub Actions jobs with the AWS API to provision AWS resources without static credentials.
GitHub OIDC Provider
GitHub provides a GitHub Actions OIDC provider, so our repository, refs, pull requests, and even environments can act as identities for resource servers that accept OIDC tokens.
Token
Token example from the official documentation:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "example-thumbprint",
"kid": "example-key-id"
}
{
"jti": "example-id",
"sub": "repo:octo-org/octo-repo:environment:prod",
"environment": "prod",
"aud": "https://github.com/octo-org",
"ref": "refs/heads/main",
"sha": "example-sha",
"repository": "octo-org/octo-repo",
"repository_owner": "octo-org",
"actor_id": "12",
"repository_visibility": "private",
"repository_id": "74",
"repository_owner_id": "65",
"run_id": "example-run-id",
"run_number": "10",
"run_attempt": "2",
"runner_environment": "github-hosted",
"actor": "octocat",
"workflow": "example-workflow",
"head_ref": "",
"base_ref": "",
"event_name": "workflow_dispatch",
"repo_property_workspace_id": "ws-abc123",
"ref_type": "branch",
"job_workflow_ref": "octo-org/octo-automationhttps://github.com/AleksandrSor/demo-infra/blob/main/.github/workflows/oidc.yml@refs/heads/main",
"iss": "https://token.actions.githubusercontent.com",
"nbf": 1632492967,
"exp": 1632493867,
"iat": 1632493567
}
The most important part is the sub claim.
sub is the stable subject identifier for the token issuer, and it is usually the safest way to correlate a returning principal. The sub claim can vary depending on whether the job runs against a ref, tag, pull request, or environment.
Environment
The subject claim includes the environment name when the job references an environment.
Syntax:
repo:ORG-NAME/REPO-NAME:environment:ENVIRONMENT-NAME
Example:
repo:octo-org/octo-repo:environment:Production
Specific branch
The subject claim includes the branch name of the workflow, but only if the job doesn't reference an environment, and if the workflow is not triggered by a pull request event.
Syntax:
repo:ORG-NAME/REPO-NAME:ref:refs/heads/BRANCH-NAME
Example:
repo:octo-org/octo-repo:ref:refs/heads/demo-branch
More information is available in the official documentation.
Token request
There is an official GitHub Action that helps not only with requesting a token, but also with configuring the AWS CLI to use it in a simple way.
name: AWS example workflow
on:
push
env:
AWS_REGION : "AWS-REGION"
ROLE-TO-ASSUME: "ROLE-ARN"
# permission can be added at job level or workflow level
permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout
jobs:
AWSGetCallerIdentity:
runs-on: ubuntu-latest
steps:
- name: Git clone the repository
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: configure aws credentials
uses: aws-actions/configure-aws-credentials@v6.1.0
with:
role-to-assume: ${{ env.ROLE-TO-ASSUME }}
aws-region: ${{ env.AWS_REGION }}
output-credentials: true
- name: get caller identity
run: |
aws sts get-caller-identity
More information is available in the GitHub Action repository.
AWS Federation
AWS OIDC Federation allows us to use any OIDC IdP (Identity Provider) to obtain AWS STS tokens with role permissions, which we can then use to manage AWS resources.
First, I need to register the GitHub OIDC provider as an IdP in my AWS account. This can be done through the AWS Console or CLI, as described here.
aws iam create-open-id-connect-provider --url \
"https://token.actions.githubusercontent.com" --thumbprint-list \
"6938fd4d98bab03faadb97b34396831e3780aea1" --client-id-list \
'sts.amazonaws.com'
Next, I need to create a role with the correct trust policy.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam:::oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo::*"
},
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
}
]
}
Pay attention to the sub claim. It can vary depending on the event type described in the previous section. When using the aws-actions/configure-aws-credentials action, the token includes the "aud": "sts.amazonaws.com" claim.
GitHub Actions Job
Let's put all of this together in a working workflow for the demo project.
Registering GitHub OIDC as an IdP in AWS
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
}
Create a role for GitHub Actions jobs
data "aws_iam_policy_document" "tf_execution_role_policy" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
effect = "Allow"
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.github.arn]
}
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = ["repo:${local.config.env.repository.name}:environment:${local.config.env.repository.protected_environment}"]
}
}
}
resource "aws_iam_role" "tf_execution_role" {
name = local.config.env.tf_role_name
assume_role_policy = data.aws_iam_policy_document.tf_execution_role_policy.json
}
Note that I allow tokens only from a specific environment in the sub claim.
Create a GitHub Actions workflow
permissions:
contents: read
id-token: write
jobs:
deploy:
name: IaC Deploy
environment:
name: production
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure AWS Credentials
id: creds
uses: aws-actions/configure-aws-credentials@v6.1.0
with:
aws-region: ${{ vars.AWS_REGION }}
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME_ARN }}
output-credentials: true
- name: get caller identity
run: |
aws sts get-caller-identity
Additional links
- Use IAM roles to connect GitHub Actions to actions in AWS - AWS Security Blog