AWS OIDC
Last chapter made the case. This chapter does the actual setup: telling AWS to trust GitHub, and creating an IAM role scoped narrowly enough that trusting it doesn’t feel reckless. Everything here happens on the AWS side — you won’t touch a workflow file until the next chapter.
Note
Every step below was verified against AWS and GitHub’s current documentation while writing this chapter. OIDC setup steps have genuinely changed over time — this is exactly the kind of chapter where copying instructions from an old blog post can quietly break. If something on the AWS console looks different by the time you’re following along, trust what’s in front of you over what’s printed here.
Step 1: Register GitHub As An OIDC Identity Provider
This is a once-per-AWS-account setup — every repository that needs AWS access afterward reuses the same provider, so you won’t repeat this step in any chapters beyond this one.
Console path: In the AWS IAM console, go to Identity providers → Add provider, choose OpenID Connect, and fill in:
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
After you enter the provider URL, the console has a Get thumbprint button that fetches GitHub’s current TLS certificate thumbprint for you automatically — you don’t need to know or type the thumbprint value by hand through the console.
CLI equivalent, if you’d rather script it (or add it to infrastructure-as-code later):
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1The CLI path does need an explicit thumbprint, since there’s no “fetch it for me” button on the command line. The value above is GitHub’s current certificate thumbprint as of this writing — thumbprints rotate when a certificate authority changes, so if AWS ever rejects tokens with a thumbprint mismatch down the line, re-fetching it is the first thing to check.
Either way, once this finishes you’ll have an OIDC provider resource with an ARN that looks like:
arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.comYou’ll reference that ARN in the trust policy next.
Step 2: Understand What The Trust Policy is Actually Checking
Before writing it, it’s worth understanding what a trust policy condition is doing, because getting it wrong either locks you out or — worse — leaves it wider than intended.
When your workflow requests a token from GitHub, that token carries a sub (subject) claim identifying exactly which repository and ref triggered it, in a format like:
repo:YOUR_GITHUB_ORG/YOUR_REPO_NAME:ref:refs/heads/mainThe trust policy’s job is to check that claim before letting anything assume the role. You have real, meaningful choices here about how tightly to scope it:
| Pattern | Matches | Use when |
|---|---|---|
repo:org/repo:* | Any branch, any PR, any tag in that repo | Rarely — this is broader than you usually want |
repo:org/repo:ref:refs/heads/main | Only pushes/merges to main | Deploy roles — the exact case here |
repo:org/repo:ref:refs/heads/* | Any branch in that repo | Testing/staging roles, not production deploys |
For a deploy role, scoping to your default branch is the right default: only workflow runs triggered from main should ever be able to assume a role that can touch production infrastructure.
Note
AWS made a change in mid-2026: newly created repositories (and existing ones that opt in) can use an immutable version of this claim, keyed to your repository’s numeric ID instead of its name, so a future repo rename can’t accidentally widen or break the trust policy. It’s a worthwhile setting to enable if it’s available for your repo, but the name-based sub format above is still the standard, documented approach and is what the rest of this chapter uses.
Step 3: Create The IAM Role And Tts Trust Policy
In the IAM console, go to Roles → Create role, choose Web identity as the trusted entity type, select the token.actions.githubusercontent.com provider you just created, set the audience to sts.amazonaws.com, and — where the console lets you — restrict it to your specific GitHub organization and repository.
Rather than clicking through every field, it’s clearer to see the trust policy as JSON and paste it directly into the role’s Trust relationships tab. Replace <YOUR_AWS_ACCOUNT_ID>, <YOUR_GITHUB_ORG>, and <YOUR_REPO_NAME> with your actual values:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:<YOUR_GITHUB_ORG>/<YOUR_REPO_NAME>:ref:refs/heads/main"
}
}
}
]
}Two conditions are doing the real work here: aud confirms the token was actually meant for AWS (not some other service GitHub can issue tokens for), and sub confirms it came from your specific repository, on your specific branch. Both have to match, or AssumeRoleWithWebIdentity fails.
Name the role something you’ll recognize later — github-actions-deploy-role is a reasonable choice — and finish creating it. You’ll attach a permissions policy next; skip past any “attach policy” step in the console wizard for now if it’s easier to add it separately.
Step 4: Attach A Minimal Permission Policy
The trust policy controls who can assume the role. A separate permissions policy controls what the role can do once assumed — and this is where least privilege (chapter 56) applies just as much as it did to GITHUB_TOKEN. Don’t reach for AdministratorAccess; scope this down to exactly the S3 bucket from chapter 46 and the ECR repository from chapter 50.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3DeployAccess",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::<YOUR_S3_BUCKET_NAME>",
"arn:aws:s3:::<YOUR_S3_BUCKET_NAME>/*"
]
},
{
"Sid": "EcrPushAccess",
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Sid": "EcrRepoAccess",
"Effect": "Allow",
"Action": [
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload"
],
"Resource": "arn:aws:ecr:<YOUR_AWS_REGION>:<YOUR_AWS_ACCOUNT_ID>:repository/<YOUR_ECR_REPO_NAME>"
}
]
}Replace <YOUR_S3_BUCKET_NAME>, <YOUR_ECR_REPO_NAME>, <YOUR_AWS_REGION>, and <YOUR_AWS_ACCOUNT_ID> with the real values from your own Section F chapters. Note that ecr:GetAuthorizationToken is unavoidably a wildcard-resource action — AWS doesn’t support scoping it to a specific repository, since it’s the step that authenticates you to ECR in general before you can push to any particular repo. Everything else here is scoped exactly to the two resources this role actually needs to touch.
Save this as a customer-managed policy (name it something like github-actions-deploy-policy) and attach it to the role you created in step 3.
Where This Leaves You
At this point you have an OIDC provider trusting GitHub, and an IAM role that only your repository’s main branch can assume, permitted to do exactly two things: push to one S3 bucket and push to one ECR repository. Nothing in your workflow files has changed yet — the old access-key secrets from Section F are, for the moment, still sitting there, unused by anything you’ve built in this chapter.
The next chapter is where that changes: modifying the actual deploy workflow to assume this role instead, confirming it works, and then deleting those old keys for good.
Cleanup
If you created this role purely to work through the chapter and don’t plan to continue the next chapter right away (but recommended to complete before removing), remove it so it isn’t sitting unused in your account:
aws iam detach-role-policy \
--role-name github-actions-deploy-role \
--policy-arn arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:policy/github-actions-deploy-policy
aws iam delete-role --role-name github-actions-deploy-role
aws iam delete-policy \
--policy-arn arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:policy/github-actions-deploy-policyThe OIDC provider itself is harmless to leave in place — it’s account-wide and shared by anything else you build later — but if you want it gone too:
aws iam delete-open-id-connect-provider \
--open-id-connect-provider-arn arn:aws:iam::<YOUR_AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.comIf you’re continuing straight into the next chapter, leave everything as-is — you’ll need it.