Back to blog

Delivery Workflows

Deploy OpenTofu/Terraform to AWS using GitHub Actions and OIDC

How to securely deploy to AWS from GitHub Actions using OIDC

September 8, 2025 Platform Engineering 6 min read

GitHub Actions make CI/CD straightforward, but securely accessing AWS from runners requires care. Historically that often meant storing long-lived AWS access keys as GitHub secrets, which creates obvious security and rotation problems. With OpenID Connect (OIDC), GitHub Actions can instead assume AWS roles using short-lived credentials.

In this post, we will walk through how GitHub OIDC works with AWS, how to set it up, and how to integrate it with OpenTofu or Terraform for infrastructure deployment. Sources: GitHub OIDC in AWS, AWS IAM OIDC providers.

Understanding the OIDC Flow

At a high level, the GitHub to AWS flow looks like this:

  1. GitHub Actions generates a signed token for the workflow run.
  2. AWS validates that token against the configured OIDC provider.
  3. AWS STS checks the IAM role trust policy.
  4. If the claims match, AWS issues temporary credentials.
  5. The workflow uses those credentials to access AWS resources.

That removes the need to store static AWS keys in GitHub.

Setting Up the AWS OIDC Identity Provider

First, create an OIDC identity provider in AWS that trusts GitHub’s issuer:

resource "aws_iam_openid_connect_provider" "github" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = [
    "sts.amazonaws.com",
  ]

  thumbprint_list = [
    "6938fd4d98bab03faadb97b34396831e3780aea1"
  ]

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

If you build this manually with the AWS CLI, make sure the provider URL and audience match the GitHub OIDC documentation for AWS.

Creating the IAM Role

The next step is an IAM role that GitHub Actions can assume. The most important part is the trust policy.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::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-org/your-repo:*"
        }
      }
    }
  ]
}

A Terraform or OpenTofu role resource would follow the same pattern.

Understanding JWT Claims and Conditions

GitHub’s OIDC token includes several useful claims:

  • aud for the audience
  • sub for the repository and ref context
  • repository
  • repository_owner
  • ref
  • actor

Those claims let you narrow trust policies so only the right repositories, branches, or pull requests can assume the role.

For example:

{
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
      "token.actions.githubusercontent.com:repository_owner": "your-org"
    },
    "StringLike": {
      "token.actions.githubusercontent.com:sub": "repo:your-org/*:*"
    }
  }
}

GitHub Actions Workflow Implementation

The workflow needs id-token: write so GitHub can mint an OIDC token for the run:

name: Deploy Infrastructure

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  plan:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/OpenTofuExecutionRole
          role-session-name: github-actions-opentofu-session
          aws-region: eu-west-2

      - name: Validate and Plan
        uses: coresolutionsltd/tf-github-action@v1.0.0
        with:
          workdir: ./infra
          env: prod
          steps: validate plan

You can extend the same pattern for apply, multi-environment workflows, or cross-account deployment.

Multi-Environment and Cross-Account Patterns

For multiple environments, use different roles:

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: ${{ github.ref == 'refs/heads/main' &&
      'arn:aws:iam::123456789012:role/ProdOpenTofuRole' ||
      'arn:aws:iam::123456789012:role/DevOpenTofuRole' }}
    role-session-name: ${{ github.run_id }}-${{ github.run_attempt }}
    aws-region: eu-west-2

For multiple AWS accounts, a matrix strategy works well:

strategy:
  matrix:
    environment: [dev, staging, prod]
    include:
      - environment: dev
        account_id: '111111111111'
        region: 'eu-west-1'
      - environment: staging
        account_id: '222222222222'
        region: 'eu-west-2'
      - environment: prod
        account_id: '333333333333'
        region: 'eu-west-2'

Security Best Practices

1. Principle of Least Privilege

Grant only the permissions the workflow actually needs.

2. Branch and Repository Restrictions

Use specific subject conditions so only the right repositories and branches can assume the role.

3. Session Duration Limits

Keep role sessions as short as practical:

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/OpenTofuExecutionRole
    role-session-name: session
    aws-region: eu-west-2
    role-duration-seconds: 1800

Troubleshooting Common Issues

”No OpenIDConnect provider found”

This usually means the IAM OIDC provider was not created, was created in the wrong account, or does not match GitHub’s issuer URL.

”AssumeRoleWithWebIdentity is not authorised”

This almost always points to a trust policy mismatch. Common causes are:

  • the wrong repository name in the sub claim
  • missing or incorrect aud
  • branch restrictions that do not match the current ref

Insufficient Permissions

If the role assumption succeeds but the workflow still fails, inspect CloudTrail or the action logs to identify which AWS actions are missing.

Conclusion

GitHub OIDC with AWS gives you a secure, keyless way to run OpenTofu or Terraform from GitHub Actions. By configuring the OIDC provider, tightening the IAM trust policy, and keeping permissions narrow, you can build a pipeline that is both more secure and easier to operate than long-lived access keys.