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
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
github_repo | string | - | yes | GitHub repository in format owner/repo |
use_existing_oidc_provider | bool | false | no | Use existing OIDC provider instead of creating new one |
github_thumbprint | string | 6938fd4d... | no | GitHub OIDC thumbprint |
audience_list | list(string) | ["sts.amazonaws.com"] | no | Allowed audiences for OIDC token |
role_name | string | GitHubActionsServiceRole-Terraform | no | Name for the IAM role |
path | string | / | no | IAM path for the role |
managed_policy_arns | list(string) | [] | no | List of managed policy ARNs to attach to role |
inline_policies | map(string) | {} | no | Map of inline policy names to JSON policy documents |
max_session_duration | number | 3600 | no | Maximum session duration in seconds (3600-43200) |
tags | map(string) | {} | no | Tags to apply to resources |
Outputs
| Name | Type | Description |
|---|---|---|
oidc_provider_arn | string | ARN of the OIDC provider |
oidc_provider_url | string | URL of the OIDC provider |
role_arn | string | ARN of the GitHub Actions IAM role |
role_name | string | Name of the GitHub Actions IAM role |
role_id | string | ID of the GitHub Actions IAM role |
role_unique_id | string | Unique 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 = trueprevents 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
- Learn how to create custom modules
- Understand the CI/CD Workflow that uses this module
- Review Environments for multi-environment setup