This Terraform module creates an AWS IAM role that a GitHub Actions workflow can assume through OpenID Connect (OIDC). Workflows in the repository you name exchange their short-lived OIDC token for temporary AWS credentials — no access keys stored as repository secrets, nothing to rotate.
The module creates the role and its trust policy only. It attaches no permission policies; you attach exactly what your workflow needs to the returned role.
Writing the trust policy by hand is where GitHub OIDC setups usually go wrong: the federated principal, the
aud condition, and the sub pattern all have to be exactly right, and a single typo produces an opaque
Not authorized to perform sts:AssumeRoleWithWebIdentity. This module:
- Gets the trust policy right — federated principal, audience, and repository subject in one place
- Handles GitHub's immutable subject claims — matches both
repo:org/repo:*and the newerrepo:org@<org_id>/repo@<repo_id>:*format that new and renamed repositories receive from 2026-07-15 - Narrows who can assume the role —
subject_claimslimits it to, say, the default branch or one environment, in both claim formats - Stays unopinionated about permissions — no bundled policies means no accidental over-privilege
- Names roles predictably —
ih-tf-<repo_name>-githubby default, so roles are easy to audit across accounts - Tags what it creates —
created_by_moduleandmodule_versiontags make provenance obvious
- IAM role with a GitHub Actions OIDC trust policy, scoped to one
org/repo - Support for legacy and immutable GitHub subject claims
- Optional subject-claim narrowing (
subject_claims; any workflow in the repository by default) - Optional custom role name (
role_name) - Configurable session length (
max_session_duration, 1 hour by default) - Outputs the role name and ARN for policy attachment and workflow configuration
- Works with AWS provider 5.11+ and 6.x
module "github_role" {
source = "registry.infrahouse.com/infrahouse/github-role/aws"
version = "1.6.0"
gh_org_name = "infrahouse"
repo_name = "aws-control"
}
# The role has no permissions until you attach a policy.
resource "aws_iam_role_policy_attachment" "github_role_s3" {
role = module.github_role.github_role_name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
}
output "github_role_arn" {
value = module.github_role.github_role_arn
}The module looks up an existing GitHub OIDC identity provider in the account
(https://token.actions.githubusercontent.com). Create it once per AWS account with
terraform-aws-gh-identity-provider:
module "github_identity_provider" {
source = "registry.infrahouse.com/infrahouse/gh-identity-provider/aws"
version = "1.1.1"
}Then use the role in a workflow — id-token: write is what lets the runner request an OIDC token:
name: Deploy to AWS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v5
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: us-west-2
- name: Verify access
run: aws sts get-caller-identityFull documentation is available at infrahouse.github.io/terraform-aws-github-role.
- Getting Started — prerequisites and first deployment
- Architecture — how the OIDC trust relationship works
- Configuration — variable reference
- Examples — common use cases
- Security — least privilege and hardening
- Troubleshooting — common issues and solutions
⚠️ Important: do not grantAdministratorAccessto a GitHub Actions role. Anyone who can run a workflow in the repository then effectively owns the AWS account.
- Least privilege — attach resource-scoped policies for the exact actions the workflow performs
- One role per repository and environment — separate
stagingandproduction, ideally in separate accounts - Protect privileged workflows — the role trusts every branch of the repository, so gate deployments with GitHub environments and required reviewers
- Short sessions — keep
max_session_durationas low as the job allows - Audit regularly —
AssumeRoleWithWebIdentityevents in CloudTrail record which repository and ref used the role
More detail: Security.
| Name | Version |
|---|---|
| terraform | ~> 1.5 |
| aws | >= 5.11, < 7.0 |
| Name | Version |
|---|---|
| aws | >= 5.11, < 7.0 |
No modules.
| Name | Type |
|---|---|
| aws_iam_role.github | resource |
| aws_iam_openid_connect_provider.github | data source |
| aws_iam_policy_document.github-trust | data source |
| Name | Description | Type | Default | Required |
|---|---|---|---|---|
| gh_org_name | GitHub organization name. | string |
n/a | yes |
| max_session_duration | Maximum session duration in seconds for the IAM role. | number |
3600 |
no |
| repo_name | Repository name in GitHub. Without the organization part. | string |
n/a | yes |
| role_name | Name of the role. If left unset, the role name will be ih-tf-var.repo_name-github. |
string |
null |
no |
| subject_claims | Subject claim suffixes allowed to assume the role: the part of the OIDC sub claim after repo:<org>/<repo>:.The default ["*"] lets any workflow in the repository assume it. Narrow it for a role with productionaccess, e.g. ["ref:refs/heads/main"] or ["environment:production"]. A job that names an environment getsan environment:<name> subject, not a ref: one. The module matches each suffix in both the legacy and theimmutable repository-name format. |
list(string) |
[ |
no |
| Name | Description |
|---|---|
| github_role_arn | ARN of the IAM role created for GitHub Actions |
| github_role_name | Name of the IAM role created for GitHub Actions |
See the examples/ directory for complete working examples:
examples/basic— a role for one repository with a read-only policyexamples/least-privilege— a deployment role scoped to a single S3 bucket
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
This project is licensed under the Apache 2.0 License — see the LICENSE file for details.