ARTICLES · 08 May 2026

Securing AWS deployments with GitHub Actions OIDC — a production walkthrough

A technical walkthrough of GitHub Actions OIDC federation and scoped AWS IAM roles as alternatives to static CI credentials.

Originally published at hashnode.

The Problem Nobody Talks About Enough

Every tutorial shows you how to deploy to AWS from GitHub Actions using `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` stored as repository secrets.

That is a liability. Static keys don’t expire. If your repo is compromised, your AWS account goes with it. If a team member leaves and you forget to rotate, the key still works. At scale, this becomes a ticking clock.

The production answer is OIDC — and most engineers using it don’t fully understand how the trust chain works. This article fixes that.

-–

What OIDC Actually Does

OIDC (OpenID Connect) replaces static keys with short-lived tokens issued per workflow run.

The trust chain works like this

1. GitHub acts as an **Identity Provider (IdP)** — it issues a signed JWT token for each workflow run

2. AWS trusts GitHub’s IdP — you configure this once

3. Your workflow assumes an IAM role — AWS verifies the JWT against GitHub’s public keys and issues temporary credentials

4. Credentials expire when the job ends — no rotation needed, no long-lived secrets

No secrets stored anywhere. No rotation schedule. No exposure surface after the job completes.

-–

AWS Side Setup

Step 1 — Create the OIDC Identity Provider

```bash

aws iam create-open-id-connect-provider \

--url https://token.actions.githubusercontent.com \

--client-id-list sts.amazonaws.com \

--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1

This tells AWS: “Trust tokens issued by GitHub Actions.”

Step 2 — Create the IAM Role with a Trust Policy

{

“Version”: “2012-10-17”,

“Statement”: [

{

“Effect”: “Allow”,

“Principal”: {

“Federated”: “arn:aws:iam::500888951988: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:EdwinJdevops/damolak-challenge:*”

}

}

}

]

}

The sub condition is critical — it restricts which repo can assume this role. Without it, any GitHub Actions workflow can assume your role.

aws iam create-role \

--role-name GitHubActionsDeployRole \

--assume-role-policy-document file://trust-policy.json

Step 3 — Attach Permissions

Attach only what the workflow needs. For ECR and ECS (what I used in my Damolak Technologies challenge):

aws iam attach-role-policy \

--role-name GitHubActionsDeployRole \

--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess

aws iam attach-role-policy \

--role-name GitHubActionsDeployRole \

--policy-arn arn:aws:iam::aws:policy/AmazonECS_FullAccess

Principle of least privilege applies — scope this tighter in production.

GitHub Actions Side Setup

The Workflow

name: Deploy to AWS

on:

push:

branches: [main]

permissions:

id-token: write # Required — this is what lets GitHub issue the OIDC token

contents: read

jobs:

deploy:

runs-on: ubuntu-latest

steps:

- name: Checkout

uses: actions/checkout@v4

- name: Configure AWS Credentials via OIDC

uses: aws-actions/configure-aws-credentials@v4

with:

role-to-assume: arn:aws:iam::500888951988:role/GitHubActionsDeployRole

aws-region: us-east-1

- name: Login to ECR

id: login-ecr

uses: aws-actions/amazon-ecr-login@v2

- name: Build and Push Image

env:

REGISTRY: ${{ steps.login-ecr.outputs.registry }}

REPOSITORY: your-ecr-repo

IMAGE_TAG: ${{ github.sha }}

run: |

docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG .

docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG

- name: Deploy to ECS Fargate

run: |

aws ecs update-service \

--cluster your-cluster \

--service your-service \

--force-new-deployment

Key thing to notice: Zero secrets in this workflow. No AWS_ACCESS_KEY_ID. No AWS_SECRET_ACCESS_KEY. Nothing stored in repository secrets.

The id-token: write permission is what unlocks this — without it, GitHub won’t issue the OIDC token and the entire flow breaks.

Proof It Works in Production

I built this exact pipeline for the Damolak Technologies DevOps Engineer challenge — a full CI/CD pipeline pushing Docker images to ECR and deploying to ECS Fargate with an ALB in front.

The pipeline runs on every push to main. AWS credentials are assumed fresh each run, scoped to exactly the permissions needed, and expire the moment the job completes.

No static keys. No rotation. No exposure surface.

Repository: github.com/EdwinJdevops/damolak-challenge

Why This Is Non-Negotiable in Production

Static AWS keys in CI/CD pipelines are one of the most common attack vectors in cloud breaches. OIDC eliminates the entire class of risk:

No secrets to rotate — tokens are ephemeral by design

No blast radius on leak — a leaked token is already expired

Auditable — every credential issuance is logged in CloudTrail with the exact repo and workflow that triggered it

IAM Conditions — you can lock down to specific branches, specific environments, or specific workflow file paths

If you’re still using static keys in your GitHub Actions pipelines, rotate them and implement OIDC today.

What’s Next

OIDC is just the authentication layer. The next level is pairing this with:

Environment protection rules — require manual approval before production deploys

Reusable workflows — centralize your OIDC configuration across multiple repos

Separate roles per environment — dev, staging, and prod each get their own IAM role with separate permission boundaries

I’m currently building SENTINEL — an automated Kubernetes security posture engine — where this OIDC pattern gates every deployment through a security scan pipeline before anything hits the cluster. More on that soon.

Edwin Jonathan — Cloud/DevOps Engineer | ALX Cloud DevOps Programme

GitHub · LinkedIn