Image description

Step-by-Step Guide to Setting OIDC With Terraform for GitHub Actions Workflows with AWS

Your GitHub Actions Secrets Are the Weakest Link in Your AWS Security Chain

Are you still using AWS access keys and secrets to authenticate your GitHub Actions with AWS in 2025?

Please don’t. Unless you want to wake up someday with 1000 GPU machines mining Bitcoin in your account at your expense, footing a million-dollar bill.

Using long-term secrets can be a security nightmare that could expose your cloud account to a possible security incident.

You might be just one exposed GitHub secret away from an AWS billing catastrophe.

Then what should you do?

You should use OpenID Connect (OIDC), a modern, more secure way to authenticate your GitHub Actions workflows with AWS without storing long-lived credentials.

You can configure the OIDC to work with certain GitHub org and GitHub repos, or you can go more granular and allow it with certain branches, tag formats, etc.

What the heck is OIDC?

OIDC enables token-based authentication between GitHub Actions and AWS, eliminating the need to store long-lived access keys. Establishing a trusting relationship with temporary credentials significantly enhances security while simplifying the authentication process.

The better way to authenticate GitHub Actions with AWS is with OpenID Connect (OIDC).

Setting up OIDC for your AWS account is very simple,

  • Step 1: Create an OIDC Identity Provider in AWS
  • Step 2: Create an IAM Role for GitHub Actions
  • Step 3: Attach the Permissions your CICD workflow needs to the Role
  • Step 4: Update Your GitHub Actions Workflow to use the Identity provider ARN instead of access keys and secrets

You can configure it from the AWS console, with AWS CLI commands or use Terraform to configure it.

I will use AWS CLI to configure the OIDC first and later with Terraform(with more flexible options).

Real-time implementation

Scenario

I have a 3-tier application running on an EKS cluster.
I want to build and deploy new versions of applications using the GitHub Action workflow. For simplicity, I will run kubectl commands from the workflow to deploy the new version of the application.

Workflow

Look for the change in frontend/backend code and only build the images upon the code change and push them to their respective ECR repos.

Configure the kubectl and authenticate to the EKS cluster
Use the newly built images (for backend/frontend) and deploy them to the frontend/backend service.

Setting Up OIDC Between GitHub Actions and AWS: A Step-by-Step Guide

Create the file structure below

Image description

Run the below command to create the directory structure
touch configure-oidc-github.sh eks-policy.json trust-policy.json
Copy the code to their respective files.
trust-policy.json

{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Federated": "arn:aws:iam::$AWS_ACCOUNT_ID:oidc-provider/$OIDC_PROVIDER"
        },
        "Action": "sts:AssumeRoleWithWebIdentity",
        "Condition": {
          "StringLike": {
            "$OIDC_PROVIDER:sub": "repo:$GITHUB_ORG/$GITHUB_REPO:*"
          }
        }
      }
    ]
  }

eks-policy.json

{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "eks:DescribeCluster",
          "eks:ListClusters"
        ],
        "Resource": "*"
      },
      {
        "Effect": "Allow",
        "Action": [
          "ecr:GetAuthorizationToken",
          "ecr:BatchCheckLayerAvailability",
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage",
          "ecr:InitiateLayerUpload",
          "ecr:UploadLayerPart",
          "ecr:CompleteLayerUpload",
          "ecr:PutImage"
        ],
        "Resource": "*"
      }
    ]
  }

configure-oidc-github.sh

#!/bin/bash
set -e

export OIDC_PROVIDER="token.actions.githubusercontent.com"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
export GITHUB_ORG="akhileshmishrabiz" # GitHub Org/Owner
export GITHUB_REPO="DevOpsDojo"  # Repo you want to allow to use OIDC with AWS

# Create the OIDC provider
aws iam create-open-id-connect-provider 
  --url https://$OIDC_PROVIDER 
  --client-id-list sts.amazonaws.com 
  --thumbprint-list "6938fd4d98bab03faadb97b34396831e3780aea1"

aws iam create-role 
  --role-name GitHubActionsEKSDeployRole 
  --assume-role-policy-document file://trust-policy.json

aws iam create-policy 
  --policy-name GitHubActionsEKSPolicy 
  --policy-document file://eks-policy.json

# Attach the policy to the role
aws iam attach-role-policy 
  --role-name GitHubActionsEKSDeployRole 
  --policy-arn arn:aws:iam::$AWS_ACCOUNT_ID:policy/GitHubActionsEKSPolicy

This script will configure the OIDC to allow my GitHub workflows in the repo https://github.com/akhileshmishrabiz/DevOpsDojo to use OIDC to authenticate with AWS.

Let me explain the steps.

Setting the environment variable

export OIDC_PROVIDER="token.actions.githubusercontent.com"
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity - query "Account" - output text)
export GITHUB_ORG="akhileshmishrabiz"
export GITHUB_REPO="DevOpsDojo"

Creating the OIDC IAM provider

aws iam create-open-id-connect-provider 
--url https://$OIDC_PROVIDER 
--client-id-list sts.amazonaws.com 
--thumbprint-list "6938fd4d98bab03faadb97b34396831e3780aea1"

Creating an IAM Role for GitHub Actions

aws iam create-role 
 --role-name GitHubActionsEKSDeployRole 
--assume-role-policy-document file://trust-policy.json

Creating an IAM policy for the GitHub Action role

aws iam create-policy 
--policy-name GitHubActionsEKSPolicy 
--policy-document file://eks-policy.json

Attaching IAM policy with the role

aws iam attach-role-policy 
--role-name GitHubActionsEKSDeployRole 
--policy-arn arn:aws:iam::$AWS_ACCOUNT_ID:policy/GitHubActionsEKSPolicy

Configuring the OIDC provider for GitHub Action with AWS CLI.

Install the AWS CLI if you haven’t already done

curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /

Configure the AWS credentials - with Access key and security key

aws configure
# This will as you access and secret key
Run the script to

chmod u+ x configure-oidc-github.sh
./configure-oidc-github.sh

Note: You can find the entire code for OIDC with AWS CLI here.

This will allow us to use the role arn:aws:iam::366140438193:role/GitHubActionsEKSDeployRole
to authenticate with the GitHub repo https://github.com/akhileshmishrabiz/DevOpsDojo

Terraform implementation

In a production environment, we mostly use Terraform to deploy the OIDC provider for GitHub Action.

Let me show how you can set up the OIDC provider with the help of Terraform and how to allow more than one repository.

  • Create aproviders.tf file and paste the below code into it. This will configure the Terraform version and AWS provider.

  • Create the files,variables.tf, output.tf, main.tf, iam.tfand paste the below code into them.

  • Create a terraform.tfvars file to create a map of repositories and branches.

  • Deploy the Terraform code, it will return the Role ARN that we can use with GitHub Action to authenticate with AWS.

  • This will create an IAM role to use with GitHub Action, IAM policies with permissions you want your GitHub Action workflow to have, and attach them to the role.

IAM roles’s trust policy will allow the particular repositories and branches to use this role to authenticate with AWS.

providers.tf

terraform {
  required_version = "1.8.1"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-south-1"
}

variables.tf

# Variables
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "ap-south-1"
}

variable "github_repositories" {
  description = "List of GitHub repositories to grant access to"
  type = list(object({
    org    = string
    repo   = string
    branch = optional(string, "*")
  }))
  default = [
    {
      org    = "akhileshmishrabiz"
      repo   = "DevOpsDojo"
      branch = "*"
    }
  ]
}

**main.tf
**

# OIDC Provider
resource "aws_iam_openid_connect_provider" "github_actions" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = [
    "sts.amazonaws.com"
  ]

  thumbprint_list = [
    "6938fd4d98bab03faadb97b34396831e3780aea1"
  ]

  tags = {
    Name = "GitHub-Actions-OIDC-Provider"
  }
}

# IAM Role
resource "aws_iam_role" "github_actions_eks_deploy_role" {
  name = "GitHubActionsEKSDeployRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = aws_iam_openid_connect_provider.github_actions.arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringLike = {
            "token.actions.githubusercontent.com:sub" = [
              for repo in var.github_repositories : 
                "repo:${repo.org}/${repo.repo}:${repo.branch}"
            ]
          }
        }
      }
    ]
  })

  tags = {
    Name = "GitHub-Actions-EKS-Deploy-Role"
  }
}

iam.tf

# IAM Policy
resource "aws_iam_policy" "github_actions_eks_policy" {
  name        = "GitHubActionsEKSPolicy"
  description = "Policy for GitHub Actions to access EKS and ECR"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "eks:DescribeCluster",
          "eks:ListClusters"
        ]
        Resource = "*"
      },
      {
        Effect = "Allow"
        Action = [
          "ecr:GetAuthorizationToken",
          "ecr:BatchCheckLayerAvailability",
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage",
          "ecr:InitiateLayerUpload",
          "ecr:UploadLayerPart",
          "ecr:CompleteLayerUpload",
          "ecr:PutImage"
        ]
        Resource = "*"
      }
    ]
  })

  tags = {
    Name = "GitHub-Actions-EKS-Policy"
  }
}

# Policy Attachment
resource "aws_iam_role_policy_attachment" "github_actions_eks_policy_attachment" {
  role       = aws_iam_role.github_actions_eks_deploy_role.name
  policy_arn = aws_iam_policy.github_actions_eks_policy.arn
}

terraform.tfvars

github_repositories = [
  {
    org    = "akhileshmishrabiz"
    repo   = "DevOpsDojo"
    branch = "*"  # All branches
  },
  {
    org    = "akhileshmishrabiz"
    repo   = "Devops-zero-to-hero" 
    branch = "main"  # Only main branch
  }
]

You can find the Terraform code to set up OIDC for Github Action here.

Apply the Terraform

This example uses the Terraform version 1.8.1 , make sure you have the same Terraform version installed.

If you have some other version of Terraform installed then update the providers.tf

If you want to use multiple versions of Terraform then use tfenv CLI.

terraform init
terraform apply

Image description
This will allow us to use the role arn:aws:iam::366140438193:role/GitHubActionsEKSDeployRole
to authenticate with the GitHub repo https://github.com/akhileshmishrabiz/DevOpsDojo on all branches.
And https://github.com/akhileshmishrabiz/Devops-zero-to-hero on the main branch.

Let’s use this OIDC provider in our GitHub Action workflow

Using the OIDC provider to authenticate GitHub Action workflow with AWS to push the docker images to ECR repositories.

For this example, I will use my public GitHub Repo https://github.com/akhileshmishrabiz/DevOpsDojo
This repo has a 3-tier app:

  • Flask backend
  • React frontend
  • RDS Postgres database
    I will use GitHub Action to build the backend and frontend images and push them to AWS ECR repositories. GitHub Action will use OIDC to authenticate with AWS.

Let me create 2 ECR repositories

Create ECR repositories for frontend and backend

aws ecr create-repository --repository-name devopsdozo/frontend
aws ecr create-repository --repository-name devopsdozo/backend

Image description
I want to use the workflow to build the backend and frontend docker images and push them to ECR repositories.

If you look at the below GitHub Action workflow, you can see that I have used GitHubActionsEKSDeployRole instead of access and secret keys.
role-to-assume:arn:aws:iam::366140438193:role/GitHubActionsEKSDeployRole
instead of AWS access and security keys.

When GitHub sends an auth request to AWS, it verifies the trust policy attached to the role to check if it is allowed to authenticate the workflow for the specific GitHub repo, and branch.

If it finds the conditions to be true, it will generate a short-term token that GH workflow uses to authenticate with AWS.

name: Build and Push Docker Images

on:
  push:
    branches: [ main ]
  workflow_dispatch:  # Enable manual triggering

env:
  AWS_REGION: ap-south-1
  ECR_REPOSITORY_FRONTEND: devopsdozo/frontend
  ECR_REPOSITORY_BACKEND: devopsdozo/backend
  IMAGE_TAG: latest

jobs:
  build-and-push:
    name: Build and Push Images
    runs-on: ubuntu-latest

    permissions:
      id-token: write
      contents: read

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v3
        with:
          aws-region: ${{ env.AWS_REGION }}
          role-to-assume: arn:aws:iam::366140438193:role/GitHubActionsEKSDeployRole

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push frontend image to Amazon ECR
        working-directory: ./frontend
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG 
                     --platform=linux/amd64 .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG
          echo "Frontend image pushed to ECR: $ECR_REGISTRY/$ECR_REPOSITORY_FRONTEND:$IMAGE_TAG"

      - name: Build, tag, and push backend image to Amazon ECR
        working-directory: ./backend
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG 
                     --platform=linux/amd64 .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG
          echo "Backend image pushed to ECR: $ECR_REGISTRY/$ECR_REPOSITORY_BACKEND:$IMAGE_TAG"

      - name: Summary
        run: |
          echo "### Docker Images Built and Pushed" >> $GITHUB_STEP_SUMMARY
          echo "✅ Frontend: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY_FRONTEND }}:${{ env.IMAGE_TAG }}" >> $GITHUB_STEP_SUMMARY
          echo "✅ Backend: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY_BACKEND }}:${{ env.IMAGE_TAG }}" >> $GITHUB_STEP_SUMMARY

Image description
Note: You can find a better version of this workflow here.

You can see from the screenshot above that GH workflows are authenticated with AWS using the OIDC, build the Docker images, and push to ECR.

This is all for this blog post.
Let’s connect

  • Connect with me on Linkedin
    **
    **- Connect with me on twitter (@livingdevops)

Similar Posts