OIDC Provider Module

Reference documentation for the GitHub Actions OIDC provider module.


Overview

The OIDC provider module (modules/oidc-provider/) creates a GitHub Actions OpenID Connect provider and IAM role, enabling keyless authentication from GitHub workflows to AWS.

Benefits:

  • No AWS credentials stored in GitHub Secrets
  • Temporary credentials with automatic expiration
  • Repository-scoped access control
  • Full audit trail via CloudTrail

Module files

modules/oidc-provider/
├── main.tf           # OIDC provider and IAM role resources
├── variables.tf      # Input variables
├── outputs.tf        # Module outputs
├── versions.tf       # Provider requirements
└── README.md         # Module documentation

Usage

Basic usage

module "oidc_provider" {
  source = "../../modules/oidc-provider"

  github_repo = "towardsthecloud/my-repo"
}

With managed policies

module "oidc_provider" {
  source = "../../modules/oidc-provider"

  github_repo = "towardsthecloud/my-repo"
  role_name   = "GitHubActionsServiceRole-Terraform-staging"

  managed_policy_arns = [
    "arn:aws:iam::aws:policy/PowerUserAccess"
  ]
}

With inline policies

module "oidc_provider" {
  source = "../../modules/oidc-provider"

  github_repo = "towardsthecloud/my-repo"

  inline_policies = {
    "S3Access" = jsonencode({
      Version = "2012-10-17"
      Statement = [
        {
          Effect = "Allow"
          Action = [
            "s3:GetObject",
            "s3:PutObject"
          ]
          Resource = "arn:aws:s3:::my-bucket/*"
        }
      ]
    })
  }
}

Reusing existing OIDC provider

When deploying multiple environments in the same AWS account:

module "oidc_provider" {
  source = "../../modules/oidc-provider"

  github_repo                = "towardsthecloud/my-repo"
  use_existing_oidc_provider = true
  role_name                  = "GitHubActionsServiceRole-Terraform-staging"
}

Variables

NameTypeDefaultRequiredDescription
github_repostring-yesGitHub repository in format owner/repo
use_existing_oidc_providerboolfalsenoUse existing OIDC provider instead of creating new one
github_thumbprintstring6938fd4d...noGitHub OIDC thumbprint
audience_listlist(string)["sts.amazonaws.com"]noAllowed audiences for OIDC token
role_namestringGitHubActionsServiceRole-TerraformnoName for the IAM role
pathstring/noIAM path for the role
managed_policy_arnslist(string)[]noList of managed policy ARNs to attach to role
inline_policiesmap(string){}noMap of inline policy names to JSON policy documents
max_session_durationnumber3600noMaximum session duration in seconds (3600-43200)
tagsmap(string){}noTags to apply to resources

Outputs

NameTypeDescription
oidc_provider_arnstringARN of the OIDC provider
oidc_provider_urlstringURL of the OIDC provider
role_arnstringARN of the GitHub Actions IAM role
role_namestringName of the GitHub Actions IAM role
role_idstringID of the GitHub Actions IAM role
role_unique_idstringUnique ID of the GitHub Actions IAM role

How it works

OIDC provider management

The module automatically detects and reuses existing OIDC providers:

data "aws_iam_openid_connect_provider" "github_actions" {
  count = var.use_existing_oidc_provider ? 1 : 0
  url   = "https://token.actions.githubusercontent.com"
}

resource "aws_iam_openid_connect_provider" "github_actions" {
  count = var.use_existing_oidc_provider ? 0 : 1
  url   = "https://token.actions.githubusercontent.com"

  client_id_list  = var.audience_list
  thumbprint_list = [var.github_thumbprint]

  tags = merge(
    {
      Name        = "GitHubActionsOIDCProvider"
      Description = "OIDC provider for GitHub Actions"
      Repository  = var.github_repo
    },
    var.tags
  )
}

Why this matters:

  • AWS allows only one OIDC provider per issuer URL per account
  • Setting use_existing_oidc_provider = true prevents errors when deploying multiple environments
  • The module uses a data source to find the existing provider instead of creating a new one

Trust policy

The IAM role trusts GitHub Actions for your specific repository:

resource "aws_iam_role" "github_actions" {
  name        = var.role_name
  path        = var.path
  description = "IAM role for GitHub Actions OIDC authentication"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = local.oidc_provider_arn
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringLike = {
            "token.actions.githubusercontent.com:sub" = "repo:${var.github_repo}:*"
          }
          StringEquals = {
            "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
          }
        }
      }
    ]
  })

  max_session_duration = var.max_session_duration

  tags = merge(
    {
      Name        = var.role_name
      Description = "GitHub Actions service role"
      Repository  = var.github_repo
    },
    var.tags
  )
}

Security features:

  • Repository-scoped: Only workflows from your repository can assume the role
  • Audience validation: Ensures token is intended for AWS STS
  • Wildcard subject: Allows all branches and environments (repo:owner/repo:*)

Policy attachments

The module supports both managed and inline policies:

# Attach managed policies
resource "aws_iam_role_policy_attachment" "github_actions" {
  for_each = toset(var.managed_policy_arns)

  role       = aws_iam_role.github_actions.name
  policy_arn = each.value
}

# Attach custom inline policies
resource "aws_iam_role_policy" "github_actions_inline" {
  for_each = var.inline_policies

  name   = each.key
  role   = aws_iam_role.github_actions.id
  policy = each.value
}

Requirements

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.0"
    }
  }
}

Next steps