Bird
Raised Fist0
Terraformcloud~10 mins

OIDC authentication for CI/CD in Terraform - Commands & Configuration

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Introduction
CI/CD pipelines need a safe way to access cloud resources without using long-lived passwords. OIDC authentication lets pipelines prove who they are using short-lived tokens, making access safer and easier to manage.
When your CI/CD pipeline needs to deploy infrastructure to a cloud provider securely.
When you want to avoid storing static cloud credentials in your pipeline configuration.
When you want to use your cloud provider's identity system to control pipeline permissions.
When you want to automatically rotate credentials without manual intervention.
When you want to improve security by using temporary tokens instead of permanent keys.
Config File - main.tf
main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
  required_version = ">= 1.3.0"
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_iam_openid_connect_provider" "github" {
  url = "https://token.actions.githubusercontent.com"
  client_id_list = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}

resource "aws_iam_role" "ci_cd_role" {
  name = "ci-cd-oidc-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          Federated = aws_iam_openid_connect_provider.github.arn
        },
        Action = "sts:AssumeRoleWithWebIdentity",
        Condition = {
          StringLike = {
            "token.actions.githubusercontent.com:sub" = "repo:example-org/example-repo:ref:refs/heads/main"
          }
        }
      }
    ]
  })
}

resource "aws_iam_policy" "ci_cd_policy" {
  name = "ci-cd-policy"
  description = "Policy for CI/CD pipeline to access S3 bucket"
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Action = ["s3:PutObject", "s3:GetObject"],
        Resource = "arn:aws:s3:::example-bucket/*"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "attach_policy" {
  role       = aws_iam_role.ci_cd_role.name
  policy_arn = aws_iam_policy.ci_cd_policy.arn
}

This Terraform file sets up OIDC authentication for a CI/CD pipeline using AWS.

terraform block defines the AWS provider and Terraform version.

aws_iam_openid_connect_provider registers GitHub's OIDC endpoint with AWS.

aws_iam_role creates a role that the CI/CD pipeline can assume using OIDC tokens from GitHub Actions, limited to the main branch of a specific repo.

aws_iam_policy defines permissions for the role, here allowing access to an S3 bucket.

aws_iam_role_policy_attachment attaches the policy to the role.

Commands
Initializes Terraform, downloads the AWS provider plugin, and prepares the working directory.
Terminal
terraform init
Expected OutputExpected
Initializing the backend... Initializing provider plugins... - Finding hashicorp/aws versions matching "~> 4.0"... - Installing hashicorp/aws v4.60.0... - Installed hashicorp/aws v4.60.0 (signed by HashiCorp) Terraform has been successfully initialized! You may now begin working with Terraform. Try running "terraform plan" to see any changes that are required for your infrastructure.
Shows the changes Terraform will make to create the OIDC provider, IAM role, policy, and attachment.
Terminal
terraform plan
Expected OutputExpected
Terraform used the selected providers to generate an execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_iam_openid_connect_provider.github will be created + resource "aws_iam_openid_connect_provider" "github" { + arn = (known after apply) + client_id_list = ["sts.amazonaws.com"] + id = (known after apply) + thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"] + url = "https://token.actions.githubusercontent.com" } # aws_iam_role.ci_cd_role will be created + resource "aws_iam_role" "ci_cd_role" { + arn = (known after apply) + assume_role_policy = jsonencode(...) + id = (known after apply) + name = "ci-cd-oidc-role" } # aws_iam_policy.ci_cd_policy will be created + resource "aws_iam_policy" "ci_cd_policy" { + arn = (known after apply) + description = "Policy for CI/CD pipeline to access S3 bucket" + id = (known after apply) + name = "ci-cd-policy" + policy = jsonencode(...) } # aws_iam_role_policy_attachment.attach_policy will be created + resource "aws_iam_role_policy_attachment" "attach_policy" { + id = (known after apply) + policy_arn = (known after apply) + role = "ci-cd-oidc-role" } Plan: 4 to add, 0 to change, 0 to destroy.
Applies the planned changes to create the OIDC provider, IAM role, policy, and attach the policy to the role.
Terminal
terraform apply -auto-approve
Expected OutputExpected
aws_iam_openid_connect_provider.github: Creating... aws_iam_role.ci_cd_role: Creating... aws_iam_policy.ci_cd_policy: Creating... aws_iam_openid_connect_provider.github: Creation complete after 2s [id=arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com] aws_iam_role.ci_cd_role: Creation complete after 3s [id=ci-cd-oidc-role] aws_iam_policy.ci_cd_policy: Creation complete after 2s [id=arn:aws:iam::123456789012:policy/ci-cd-policy] aws_iam_role_policy_attachment.attach_policy: Creating... aws_iam_role_policy_attachment.attach_policy: Creation complete after 1s [id=ci-cd-oidc-role/arn:aws:iam::123456789012:policy/ci-cd-policy] Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
-auto-approve - Automatically approves the apply without asking for confirmation.
Verifies the IAM role was created and shows its details.
Terminal
aws iam get-role --role-name ci-cd-oidc-role
Expected OutputExpected
{ "Role": { "Path": "/", "RoleName": "ci-cd-oidc-role", "RoleId": "AROAJEXAMPLEID", "Arn": "arn:aws:iam::123456789012:role/ci-cd-oidc-role", "CreateDate": "2024-06-01T12:00:00Z", "AssumeRolePolicyDocument": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"Federated\":\"arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com\"},\"Action\":\"sts:AssumeRoleWithWebIdentity\",\"Condition\":{\"StringLike\":{\"token.actions.githubusercontent.com:sub\":\"repo:example-org/example-repo:ref:refs/heads/main\"}}}]}" } }
--role-name - Specifies the exact IAM role to retrieve.
Key Concept

If you remember nothing else from this pattern, remember: OIDC lets your CI/CD pipeline securely prove its identity to the cloud without storing long-term secrets.

Common Mistakes
Using incorrect or missing thumbprint for the OIDC provider.
AWS rejects the OIDC provider registration if the thumbprint does not match the provider's SSL certificate, causing authentication failures.
Always verify and use the correct thumbprint for the OIDC provider URL before applying Terraform.
Not specifying the correct 'sub' condition in the IAM role trust policy.
Without the correct 'sub' condition, any token from the OIDC provider could assume the role, which is a security risk or the role might not be assumable by the intended pipeline.
Set the 'sub' condition to match your specific repository and branch to restrict role assumption.
Attaching overly broad IAM policies to the role.
This grants more permissions than needed, increasing security risks if the pipeline is compromised.
Grant only the minimum permissions the pipeline needs, following the principle of least privilege.
Summary
Initialize Terraform to prepare the working directory and download providers.
Plan the Terraform deployment to see what resources will be created.
Apply the Terraform configuration to create the OIDC provider, IAM role, and policies.
Verify the IAM role exists and has the correct trust policy for OIDC authentication.

Practice

(1/5)
1. What is the main benefit of using OIDC authentication in CI/CD pipelines with Terraform?
easy
A. It disables all access restrictions for faster builds.
B. It automatically deploys code without any manual approval.
C. It replaces Terraform with a different infrastructure tool.
D. It allows secure authentication without storing passwords in the pipeline.

Solution

  1. Step 1: Understand OIDC purpose in CI/CD

    OIDC provides a secure way for pipelines to prove identity without passwords.
  2. Step 2: Connect to Terraform usage

    Terraform can create roles that trust OIDC tokens, avoiding password storage.
  3. Final Answer:

    It allows secure authentication without storing passwords in the pipeline. -> Option D
  4. Quick Check:

    OIDC = passwordless secure authentication [OK]
Hint: OIDC means no passwords needed in CI/CD [OK]
Common Mistakes:
  • Thinking OIDC automates deployment without approval
  • Confusing OIDC with replacing Terraform
  • Assuming OIDC disables access controls
2. Which Terraform resource block correctly defines an IAM role trusting GitHub Actions OIDC provider?
easy
A. resource "aws_iam_role" "github_oidc_role" { assume_role_policy = jsonencode({ Version = "2012-10-17", Statement = [{ Effect = "Allow", Principal = { Federated = "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" }, Action = "sts:AssumeRoleWithWebIdentity" }] }) }
B. resource "aws_iam_role" "github_oidc_role" { assume_role_policy = jsonencode({ Version = "2012-10-17", Statement = [{ Effect = "Allow", Principal = { Service = "github.com" }, Action = "sts:AssumeRole" }] }) }
C. resource "aws_iam_role" "github_oidc_role" { assume_role_policy = jsonencode({ Version = "2012-10-17", Statement = [{ Effect = "Allow", Principal = { AWS = "arn:aws:iam::123456789012:root" }, Action = "sts:AssumeRoleWithWebIdentity" }] }) }
D. resource "aws_iam_role" "github_oidc_role" { assume_role_policy = jsonencode({ Version = "2012-10-17", Statement = [{ Effect = "Allow", Principal = { Federated = "arn:aws:iam::123456789012:oidc-provider/github.com" }, Action = "sts:AssumeRoleWithWebIdentity" }] }) }

Solution

  1. Step 1: Identify correct OIDC provider ARN format

    The OIDC provider ARN for GitHub Actions uses 'token.actions.githubusercontent.com'.
  2. Step 2: Check assume_role_policy structure

    The policy must allow 'sts:AssumeRoleWithWebIdentity' with Principal as Federated and correct ARN.
  3. Final Answer:

    The resource block with Federated principal using 'token.actions.githubusercontent.com' and 'sts:AssumeRoleWithWebIdentity' action. -> Option A
  4. Quick Check:

    Correct OIDC ARN + sts:AssumeRoleWithWebIdentity = resource "aws_iam_role" "github_oidc_role" { assume_role_policy = jsonencode({ Version = "2012-10-17", Statement = [{ Effect = "Allow", Principal = { Federated = "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" }, Action = "sts:AssumeRoleWithWebIdentity" }] }) } [OK]
Hint: OIDC provider ARN uses token.actions.githubusercontent.com [OK]
Common Mistakes:
  • Using Service principal instead of Federated
  • Wrong OIDC provider ARN format
  • Using sts:AssumeRole instead of sts:AssumeRoleWithWebIdentity
3. Given this Terraform snippet for an IAM role trust policy, what is the effect of the condition block?
condition = {
  StringEquals = {
    "token.actions.githubusercontent.com:sub" = "repo:myorg/myrepo:ref:refs/heads/main"
  }
}
medium
A. Allows any GitHub repo to assume the role.
B. Restricts role assumption to the main branch of myorg/myrepo only.
C. Blocks all role assumptions from GitHub Actions.
D. Allows only forks of myorg/myrepo to assume the role.

Solution

  1. Step 1: Understand the condition key

    The condition uses 'token.actions.githubusercontent.com:sub' to specify the GitHub repo and branch.
  2. Step 2: Interpret the value

    The value 'repo:myorg/myrepo:ref:refs/heads/main' restricts access to the main branch of that repo only.
  3. Final Answer:

    Restricts role assumption to the main branch of myorg/myrepo only. -> Option B
  4. Quick Check:

    Condition limits to specific repo and branch = Restricts role assumption to the main branch of myorg/myrepo only. [OK]
Hint: Condition with repo and ref limits branch access [OK]
Common Mistakes:
  • Ignoring the branch restriction in condition
  • Assuming condition allows all repos
  • Confusing forks with original repo access
4. You wrote this Terraform assume_role_policy but your CI/CD pipeline fails to authenticate:
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/github.com" },
    "Action": "sts:AssumeRoleWithWebIdentity"
  }]
}
What is the most likely error?
medium
A. The Principal should use 'Service' instead of 'Federated'.
B. The Action should be 'sts:AssumeRole' instead of 'sts:AssumeRoleWithWebIdentity'.
C. The OIDC provider ARN is incorrect; it should be 'token.actions.githubusercontent.com'.
D. The Version date is invalid and must be updated.

Solution

  1. Step 1: Check the OIDC provider ARN

    The ARN 'arn:aws:iam::123456789012:oidc-provider/github.com' is incorrect for GitHub Actions.
  2. Step 2: Identify correct ARN for GitHub Actions

    The correct ARN uses 'token.actions.githubusercontent.com' as the OIDC provider host.
  3. Final Answer:

    The OIDC provider ARN is incorrect; it should be 'token.actions.githubusercontent.com'. -> Option C
  4. Quick Check:

    Wrong OIDC ARN causes auth failure = The OIDC provider ARN is incorrect; it should be 'token.actions.githubusercontent.com'. [OK]
Hint: Check OIDC ARN spelling carefully for GitHub Actions [OK]
Common Mistakes:
  • Using 'sts:AssumeRole' instead of 'sts:AssumeRoleWithWebIdentity'
  • Confusing Federated with Service principal
  • Ignoring ARN format for OIDC provider
5. You want to create a Terraform IAM role for GitHub Actions that only allows workflows from the 'release' branch of 'myorg/myrepo' to assume it. Which condition block correctly enforces this restriction?
hard
A. { "StringEquals": { "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/release" } }
B. { "StringLike": { "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/*" } }
C. { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" } }
D. { "StringNotEquals": { "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/main" } }

Solution

  1. Step 1: Understand the condition key and value

    To restrict to the 'release' branch, the condition must match 'repo:myorg/myrepo:ref:refs/heads/release'.
  2. Step 2: Choose correct operator

    'StringEquals' ensures exact match, so only 'release' branch is allowed.
  3. Final Answer:

    The condition block with StringEquals matching 'repo:myorg/myrepo:ref:refs/heads/release'. -> Option A
  4. Quick Check:

    Exact branch match uses StringEquals with correct ref [OK]
Hint: Use StringEquals with exact repo and branch ref [OK]
Common Mistakes:
  • Using StringLike with wildcard allowing all branches
  • Checking audience (aud) instead of subject (sub)
  • Using StringNotEquals which excludes main but allows others