Secure CI/CD Pipelines: Integrating GitHub OIDC with AWS Using Terraform

GitHub OIDC with AWS Using Terraform

GitHub OIDC with AWS Using Terraform 

In the world of DevOps and cloud infrastructure, security is not a checkbox. Traditional methods of authenticating CI/CD pipelines to cloud providers like AWS often rely on long-lived access keys, which pose significant risks if compromised.

Enter OpenID Connect (OIDC) a modern, token-based authentication protocol that allows your GitHub Actions workflows to securely assume AWS IAM roles without storing sensitive credentials. By leveraging OIDC, you can automate deployments to services like Amazon ECR, App Runner, and EC2 while minimizing your attack surface.

In this article, we will walk through how to set up OIDC integration between GitHub and AWS using Terraform for infrastructure as code (IaC). We will cover the why behind this approach, its usefulness in real-world scenarios, and a feature-by-feature breakdown of the authentication flow in your CI/CD pipeline. This setup is perfect for automating deployments, such as building Docker images and pushing them to AWS App Runner, all while ensuring compliance and security best practices.

Whether you are a DevOps engineer, a developer managing cloud resources, or just dipping your toes into secure automation, this guide will equip you with actionable steps to enhance your pipelines.

Why Use GitHub OIDC with AWS? The Benefits

Before diving into the implementation, let’s address the how this is useful part. OIDC integration replaces static AWS access keys (which you might hardcode or store in secrets) with short-lived, dynamically generated tokens. Here’s why this matters:

  • Enhanced Security: No more long-term credentials in your GitHub repo or secrets manager. Tokens expire quickly (typically within an hour), reducing the window for misuse. This aligns with AWS’s principle of least privilege and zero-trust models.
  • Simplified Secret Management: GitHub Actions handles the OIDC token issuance, so you don’t need to rotate keys manually or worry about credential leaks in logs.
  • Cost and Efficiency Gains: Automate infrastructure provisioning and deployments without human intervention. For example, trigger builds on every push to main, deploy to AWS App Runner, and scale resources dynamically—all without manual logins.
  • Compliance and Auditing: AWS CloudTrail logs role assumptions via OIDC, making it easier to audit who (or what) accessed what. This is crucial for regulated industries like finance or healthcare.
  • Scalability for Pipelines: In a typical CI/CD flow, this setup authenticates your GitHub workflow to AWS, allowing seamless interactions with services like ECR (for container registries), S3 (for artifacts), and App Runner (for serverless web apps). It eliminates bottlenecks in pipeline flow, enabling faster, more reliable releases.

In short, this integration turns your GitHub repo into a secure gateway to AWS, automating the entire deployment lifecycle while keeping things locked down.

Prerequisites

To follow along, you will need:

  • An AWS account with permissions to create IAM roles and OIDC providers.
  • A GitHub repository for your application (e.g., a Node.js Express app with a Dockerfile).
  • Terraform installed (version 1.0+).
  • GitHub Actions enabled on your repo.
  • Environment variables set in your GitHub repo secrets: AWS_ACCOUNT_ID (your AWS account ID), AWS_REGION (e.g., us-east-1), GITHUB_REPO (e.g., your-org/your-repo), ECR_REPO_NAME (e.g., express-apprunner), and APP_NAME (e.g., my-app).

No AWS CLI credentials are needed in GitHub—that's the beauty of OIDC!

Step-by-Step Setup: Provisioning IAM Roles with Terraform

Terraform shines here as it declaratively defines your AWS infrastructure. We will create an IAM role that trusts GitHub’s OIDC provider, attach necessary policies, and ensure the role can only be assumed by your specific repo.

Define the IAM Role for OIDC Trust

github openid connect 

github openid connect 

Start with the core resource: an IAM role that GitHub Actions can assume. This role’s trust policy specifies the OIDC provider (token.actions.githubusercontent.com) and conditions the assumption to your repo.

Here's the Terraform code:

resource "aws_iam_role" "github_oidc" {
  name = "GitHubOIDCRole"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = "arn:aws:iam::${var.account_id}:oidc-provider/token.actions.githubusercontent.com"
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringLike = {
            "token.actions.githubusercontent.com:sub" = "repo:${var.github_repo}:*"
          }
        }
      }
    ]
  })
}

Feature Breakdown:

  • Principal (Federated): Points to GitHub’s OIDC endpoint. Terraform uses your account_id variable to construct the ARN.
  • Action: sts:AssumeRoleWithWebIdentity is the OIDC-specific AWS STS action for token exchange.
  • Condition: The StringLike ensures only workflows from your specified github_repo (e.g., myorg/myapp) can assume the role. The * wildcard allows actions like pull or push but restricts to your repo—zero trust in action!
  • Usefulness: This prevents unauthorized repos from accessing your AWS resources, authenticating the pipeline at the source.
variable "github_repo" {
  description = "GitHub organization/repository for OIDC trust"
  type        = string
}

variable "account_id" {
  description = "AWS account ID"
  type        = string
}

variable "region" {
  description = "AWS region"
  type        = string
}

variable "ecr_repo_name" {
  description = "ECR repository name"
  type        = string
}

variable "app_name" {
  description = "App name"
  type        = string
}

Attach AWS Managed Policies

Next, attach policies to grant permissions for your pipeline’s needs (e.g., ECR for Docker pushes, App Runner for deployments, S3 for storage).

resource "aws_iam_role_policy_attachment" "github_oidc_ec2" {
  role       = aws_iam_role.github_oidc.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
}

resource "aws_iam_role_policy_attachment" "github_oidc_ecr" {
  role       = aws_iam_role.github_oidc.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess"
}

resource "aws_iam_role_policy_attachment" "github_oidc_apprunner" {
  role       = aws_iam_role.github_oidc.name
  policy_arn = "arn:aws:iam::aws:policy/AWSAppRunnerFullAccess"
}

resource "aws_iam_role_policy_attachment" "github_oidc_apprunner_ecr" {
  role       = aws_iam_role.github_oidc.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess"
}

resource "aws_iam_role_policy_attachment" "github_oidc_s3" {
  role       = aws_iam_role.github_oidc.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}

resource "aws_iam_role_policy_attachment" "github_oidc_iam" {
  role       = aws_iam_role.github_oidc.name
  policy_arn = "arn:aws:iam::aws:policy/IAMFullAccess"
}

Feature Breakdown:

  • Policy Attachments: Each resource links a pre-built AWS policy to your role. For instance, AmazonEC2ContainerRegistryFullAccess allows pushing/pulling images to ECR, while AWSAppRunnerFullAccess enables creating and updating App Runner services.
  • Granular Permissions: Use IAMFullAccess sparingly (as shown); in production, create custom policies for least privilege (e.g., only ecr:BatchGetImage instead of full access).
  • Usefulness: This automates resource access in your pipeline. Without it, your workflow couldn’t interact with AWS services securely.

Pro Tip: For finer control, add inline policies via aws_iam_role_policy to restrict actions like ecr:PutImage to specific repositories using your ecr_repo_name variable.

Initialize, Plan, and Apply Terraform

Run these commands in your infra directory (export variables first or use -var flags):

terraform init
terraform plan -var="account_id=${AWS_ACCOUNT_ID}" -var="region=${AWS_REGION}" -var="github_repo=${GITHUB_REPO}" -var="ecr_repo_name=${ECR_REPO_NAME}" -var="app_name=${APP_NAME}"
terraform apply -auto-approve -var="account_id=${AWS_ACCOUNT_ID}" -var="region=${AWS_REGION}" -var="github_repo=${GITHUB_REPO}" -var="ecr_repo_name=${ECR_REPO_NAME}" -var="app_name=${APP_NAME}"

Feature Breakdown:

  • Init/Plan/Apply: Standard Terraform workflow. plan previews changes (e.g., role creation), while apply provisions the OIDC provider if it doesn’t exist.
  • Usefulness: Idempotent and version-controlled—run it in your pipeline for infrastructure updates without manual tweaks.

After applying, verify in the AWS Console: IAM > Roles > GitHubOIDCRole. The trust policy should reference your repo.

Integrating with GitHub Actions: Authenticating the Pipeline Flow

Now, wire this into your CI/CD pipeline. The GitHub Actions workflow uses OIDC to assume the role, then performs tasks like building Docker images and deploying to App Runner.

Here's a sample .github/workflows/deploy.yml:

name: Deploy to App Runner
on:
  push:
    branches: ["main"]
permissions:
  id-token: write
  contents: read
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: Aws
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHubOIDCRole
          aws-region: us-east-1
      - name: Terraform Init & Apply
        working-directory: infra
        run: |
          terraform init -input=false
          terraform apply -auto-approve
      - name: Login to Amazon ECR
        run: |
          aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com
      - name: Build and push Docker image
        run: |
          IMAGE_TAG=latest
          ECR_REPO=${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/express-apprunner
          docker build -t $ECR_REPO:$IMAGE_TAG .
          docker push $ECR_REPO:$IMAGE_TAG

Feature-by-Feature Authentication Flow:

  1. Trigger (on: push): Workflow runs on pushes to main. Permissions grant id-token: write for OIDC token generation—essential for secure auth.
  2. Checkout Code: Pulls your repo, including Dockerfile and infra code.
  3. Configure AWS Credentials (OIDC Magic): The aws-actions/configure-aws-credentials action exchanges GitHub's OIDC token (JWT) for AWS STS credentials. It uses your role ARN and verifies the token against the condition (repo match).
    • How Authentication Works: GitHub signs the token with its private key. AWS validates it against the public OIDC provider. If the sub claim matches repo:your-org/your-repo:*, temporary credentials (access key, secret, session token) are issued—valid for ~1 hour.
    • Usefulness: No secrets exposed! The pipeline now has AWS CLI access without keys.
  4. Terraform Apply: Re-provisions infra if needed (e.g., updates to ECR or App Runner). Runs under the assumed role.
  5. ECR Login & Docker Push: Uses AWS CLI (authenticated via OIDC) to get a login password and push your image. The ECR policy ensures only your ecr_repo_name is accessible.
    • Pipeline Flow Benefit: Automates container registry ops, enabling blue-green deployments.
  6. Post-Deployment (Not Shown): Add a step to update App Runner with the new image tag using aws apprunner update-service—leveraging the App Runner policies.

Security Note: Store only non-sensitive vars like AWS_ACCOUNT_ID as GitHub secrets. The OIDC token is ephemeral and scoped.

Troubleshooting and Best Practices

  • Common Issues: If assumption fails, check the OIDC provider exists (Terraform creates it implicitly) and repo condition matches exactly. Use AWS STS GetCallerIdentity in your workflow for debugging.
  • Enhancements: Add multi-account support, branch-specific roles, or integrate with AWS CDK for hybrid IaC.
  • Monitoring: Enable AWS GuardDuty for anomaly detection on role assumptions.

Conclusion: Streamlining Secure Deployments

Integrating GitHub OIDC with AWS via Terraform is not just a technical setup—it’s a game-changer for automating secure CI/CD pipelines. By eliminating static credentials, you authenticate your pipeline flow end-to-end: from code checkout to resource deployment. Feature by feature, it enforces repo-specific access, policy-based permissions, and token-based trust, making your workflows faster, safer, and more scalable.

For your blog, this could be a cornerstone post on modern DevOps. Experiment with the code above, tweak for your app (e.g., add S3 artifact uploads), and watch your deployments soar. If you’re deploying a web app to App Runner, this setup could shave hours off your release cycles. For the complete code and contribution opportunities, check out the GitHub repository at moovendhan-v/OpenIdConnect. Happy coding—and stay secure!

This article is based on AWS and GitHub best practices as of September 2025. Always review official docs for updates.

Post a Comment

Post a Comment (0)

Previous Post Next Post