Logo

Using AWS OIDC Roles in GitHub Actions for CI/CD

IAM

🤝 Intro : GHA + AWS Without Leaking Your Secrets

I wanted to migrate away from using AWS secrets in GitHub Actions — not because it wasn’t working, but because storing long-lived credentials in CI just felt… sketchy. Like, “I really shouldn’t be doing this but I am anyway” kind of sketchy.

But now with OIDC, I can securely assume AWS IAM roles in GitHub Actions without needing to store any AWS secrets in my repo. It’s like giving GitHub a VIP pass to AWS, but only for the right actions and repos.

Here’s how I set it up using CloudFormation and wired it into my workflow.

🛠️ Step 1: Set Up the OIDC Provider in AWS

First, we need to tell AWS about GitHub’s OIDC provider. This is like giving AWS a heads-up that GitHub is going to come knocking with an identity token. This is needed only once per AWS account, so this step can be skipped if it's already done.

In CloudFormation, define the OIDC provider:

1Resources:
2 GithubOidcProvider:
3 Type: AWS::IAM::OIDCProvider
4 Properties:
5 Url: https://token.actions.githubusercontent.com
6 ClientIdList:
7 - sts.amazonaws.com
8 ThumbprintList:
9 - '6938fd4d98bab03faadb97b34396831e3780aea1' # GitHub’s root CA thumbprint

🔐 Step 2: Create the IAM Role for GHA

a.k.a. Give GitHub the Keys, but Only a Copy

In CloudFormation, Define a role that GitHub Actions can assume via OIDC. The idea is: let GitHub knock on the AWS door, show its identity token, and get in — but only if it’s the right repo, branch, and vibes.

1Type: AWS::IAM::Role
2Properties:
3 RoleName: your-service-gha
4 AssumeRolePolicyDocument:
5 Version: 2012-10-17
6 Statement:
7 - Effect: Allow
8 Action: sts:AssumeRoleWithWebIdentity
9 Principal:
10 Federated: arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com
11 Condition:
12 StringEquals:
13 token.actions.githubusercontent.com:aud: sts.amazonaws.com
14 ForAnyValue:StringLike:
15 token.actions.githubusercontent.com:sub:
16 - repo:your-org/your-repo:ref:refs/heads/main
17 - repo:your-org/your-repo:pull_request
18 Policies:
19 - PolicyName: your-service-gha
20 PolicyDocument:
21 Version: 2012-10-17
22 Statement:
23 - Sid: AllowECR
24 Effect: Allow
25 Action:
26 - ecr:GetAuthorizationToken
27 - ecr:BatchCheckLayerAvailability
28 - ecr:GetDownloadUrlForLayer
29 - ecr:PutImage
30 - ecr:InitiateLayerUpload
31 - ecr:UploadLayerPart
32 - ecr:CompleteLayerUpload
33 Resource: '*'

Customize this to restrict to specific ECR repos, S3 buckets, or even different branches.

📘 Helpful Docs:

⚙️ Step 3: Use the Role in GitHub Actions

In GHA workflow file:

1env:
2 GITHUB_ACTIONS_ROLE_ARN: arn:aws:iam::123456789012:role/your-service-gha
3
4jobs:
5 deploy:
6 runs-on: ubuntu-latest
7 steps:
8 - name: Configure AWS credentials
9 uses: aws-actions/configure-aws-credentials@v4
10 with:
11 role-to-assume: ${{ env.GITHUB_ACTIONS_ROLE_ARN }}
12 aws-region: ap-northeast-1

Once the role is assumed, all subsequent aws CLI or SDK commands in the job are authenticated with this role — no secrets required 🎉

🧠 Gotchas to Watch Out For

  • Make sure GitHub’s OIDC provider is added to IAM
  • Be super specific with Condition blocks to avoid privilege creep
  • OIDC tokens expire quickly — assume the role once per job

✅ Wrap Up

  • Ditch long-lived AWS secrets in GitHub
  • Use sts:AssumeRoleWithWebIdentity and GitHub’s OIDC token instead
  • Lock roles down to specific branches or repos for security
  • Simpler, safer CI/CD setup 💪