GitHub Actions needs credentials before it can change AWS resources. The expedient approach is to create an access key, put it in repository secrets, and promise to rotate it later. “Later” then joins “temporary” in the museum of permanent infrastructure.
OIDC replaces that long-lived secret with a short-lived exchange. GitHub signs an identity token for a workflow, AWS validates its issuer and claims, and STS returns temporary role credentials. Nothing durable needs to be copied into GitHub.
Create the trust relationship with Terraform #
The AWS side needs an OIDC provider for GitHub and a role whose trust policy permits sts:AssumeRoleWithWebIdentity.
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [var.github_oidc_thumbprint]
}
resource "aws_iam_role" "github_actions" {
name = "TerraformCICDRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "sts:AssumeRoleWithWebIdentity"
Principal = {
Federated = aws_iam_openid_connect_provider.github.arn
}
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:OWNER/REPOSITORY:*"
}
}
}]
})
}The sub condition matters. Without it, the trust relationship is much broader than the repository that needs access. In production, constrain organization, repository, branch, tag, or GitHub environment as tightly as the deployment workflow allows.
The original walkthrough attached AdministratorAccess to prove the path end to end. Treat that as a connectivity test, not a design recommendation. Replace it with a policy containing only the actions and resources the workflow actually needs.
Request the identity in Actions #
The workflow must explicitly request permission to mint an OIDC token:
name: AWS identity test
on:
push:
branches: [test]
permissions:
id-token: write
contents: read
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT_ID:role/TerraformCICDRole
aws-region: ap-northeast-1
- run: aws sts get-caller-identityid-token: write does not grant AWS access by itself. It lets the job request a GitHub token. AWS still decides whether the token’s audience, repository, ref, and other claims satisfy the role’s trust policy.
That separation is the useful mental model: GitHub proves which workflow is running; AWS decides what that identity may do; STS limits how long the resulting credentials live. The next step is not storing a better secret. It is deleting the permanent one.