A photo of Geoffrey Hayward

Configure Terraform Cloud to Assume an AWS IAM Role via OIDC

Published June 11, 2025

A simple graph that says configure Terraform Cloud to assume an AWS IAM role via OIDC.

Here is how to configure Terraform Cloud to assume an AWS IAM role via OIDC (OpenID Connect) using only environment variables—no static AWS keys are required.

Modern security best practices dictate minimising long-lived credentials in CI/CD pipelines, and when it comes to AWS, Terraform Cloud has your back.

Terraform Cloud’s is comfortable with AWS via OIDC, letting you run Terraform without ever storing an AWS access key in your workspace. This guide walks through setting it up. And yes, the official documentation from AWS and Terraform Cloud does cover this however, I didn’t find it fully joined all the dots.

In case you are wondering, does this solution work with both remote execution and remote state only, yes, it does.

So here is how I managed to get Terraform Cloud to assume a tight-scope AWS role and orchestrate AWS.

1. AWS Console: Set Up OIDC & Role

1.1 Create OIDC Identity Provider

  1. Navigate to IAM → Identity providers → Add provider.
  2. Choose Provider type: OIDC.
  3. Provider URL: https://app.terraform.io
  4. Audience: aws.workload.identity
  5. Click Add provider.

1.2 Create the IAM Role

  1. Go to IAM → Roles → Create roleWeb identity.
  2. Select your new app.terraform.io provider and audience aws.workload.identity.
  3. Add a Condition to restrict to your workspace’s app.terraform.io:sub claim, e.g. :organization:[Your Terraform Org Name]:project:[Leave black]:workspace:[Your workspace name]:run_phase:*
  4. Click Next: Permissions.

1.3 Attach the Inline Permissions Policy

Your policy will be based on your needs, mine at the time of writing this was to orchestrate AWS SES.

  1. Under Permissions, choose Create inline policy.
  2. Switch to JSON and add your policy:
// Example policy, but will work with AWS SES.
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SesAndIamPermissions",
      "Effect": "Allow",
      "Action": [
        "ses:VerifyDomainIdentity",
        "ses:VerifyDomainDkim",
        "ses:GetIdentityVerificationAttributes",
        "ses:GetIdentityDkimAttributes",
        "iam:CreateUser",
        "iam:GetUser",
        "iam:CreatePolicy",
        "iam:GetPolicy",
        "iam:ListPolicies",
        "iam:AttachUserPolicy",
        "iam:CreateAccessKey",
        "iam:ListAccessKeys",
        "iam:DeleteAccessKey",
        "iam:TagPolicy",
        "iam:UntagPolicy",
        "iam:ListPolicyVersions",
        "iam:GetPolicyVersion",
        "iam:DeletePolicy",
        "iam:DeletePolicyVersion",
        "iam:DetachUserPolicy",
        "iam:ListAttachedUserPolicies",
        "iam:TagUser",
        "iam:UntagUser"
      ],
      "Resource": "*"
    }
  ]
}

Click Review policy, name it something sensible, then Create policy.

1.4 Configure the Trust Relationship

On your role’s Trust relationships tab, click Edit trust policy.

Replace with:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Principal": {
        "Federated": "arn:aws:iam::************:oidc-provider/app.terraform.io"
      },
      "Condition": {
        "StringEquals": {
          "app.terraform.io:aud": "aws.workload.identity"
        },
        "StringLike": {
          "app.terraform.io:sub": "organization:[your org name]:project::workspace:[your workspace name]:run_phase:*"
        }
      }
    }
  ]
}

Save and copy the Role ARN, e.g.: arn:aws:iam::************:role/my-thing-tf-staging-etc

2. Terraform Cloud: Environment-Variable Configuration

Instead of storing AWS keys, configure these Environment variables in your workspace:

Key Value Notes
TFC_AWS_PROVIDER_AUTH true Enables dynamic credential injection
TFC_AWS_RUN_ROLE_ARN arn:aws:iam::************:role/my-thing-tf-staging-etc The Role ARN you created above
  1. Go to Workspace → Settings → Variables.
  2. Add each as an Environment variable (not Terraform).
  3. Mark Sensitive as needed (so they won’t echo in logs).

3. Update Your AWS Provider Block

Remove any access_key/secret_key or assume_role blocks - you don’t need them. Use a minimal provider like:

provider "aws" {
  region = var.aws_region

  default_tags {
    tags = {
      Environment = var.environment
      ManagedBy   = "Terraform"
      Client      = "..."
    }
  }
}

Terraform Cloud will now handle the OIDC handshake and assume the role under the hood. And it works with both remote execution and remote state only, so, for example, you can run a development workspace locally, from your own laptop with need any AWS keys.

Why OIDC & Dynamic Credentials?

  • No static AWS keys in your Terraform Cloud workspace.
  • Least-privilege: your Terraform workspace can only assume a single, scoped IAM role.
  • Automatic rotation: AWS issues short-lived tokens per run.
  • Auditable: every assume-role call is recorded in CloudTrail (if you have CloudTrail set up).

Hope this helps.

Latest Posts

Developer with laptop and friendly robot assistant outdoors, coding with Spring Boot.

Responsible AI Coding: What I’ve Learned So Far

May 9, 2025

Computing

A whole new way of coding. When JetBrains offered me a trial of Junie, an AI assistant built into IntelliJ, I had to give it a go. I soon saw the potential, it’s kind of like an AI coding partner (albeit a little dumb at times), but that’s okay, if used responsibly. So here’s what I’ve learned about coding responsibly with AI—so far.

Continue reading
And abstract image of programming code surrounded by a light bulb.

Notes on Successfully Running Ubuntu in VirtualBox

January 26, 2024

Computing

As a Software Developer who likes to use Ubuntu for Development but can sometimes only run it as just another window in Windows, I would like to share some nuanced VirtualBox configurations I built up over the years.

Continue reading