Architecture¶
What the Module Creates¶
| Address | Type | Purpose |
|---|---|---|
aws_iam_role.github | resource | The role a GitHub Actions workflow assumes |
data.aws_iam_openid_connect_provider.github | data source | Looks up the account's GitHub OIDC provider |
data.aws_iam_policy_document.github-trust | data source | Builds the trust policy attached to the role |
That is the whole module. It intentionally creates no permission policies, no identity provider, and no GitHub-side resources.
How Keyless Authentication Works¶
sequenceDiagram
participant W as GitHub Actions workflow
participant G as GitHub OIDC issuer<br/>token.actions.githubusercontent.com
participant S as AWS STS
participant R as IAM role<br/>ih-tf-repo-github
W->>G: Request OIDC token (needs id-token: write)
G-->>W: Signed JWT (iss, aud, sub claims)
W->>S: AssumeRoleWithWebIdentity(role_arn, JWT)
S->>G: Fetch signing keys, verify JWT signature
S->>R: Evaluate trust policy against JWT claims
R-->>S: Allowed if aud and sub match
S-->>W: Temporary credentials (up to max_session_duration) Nothing static is exchanged: the JWT is minted per job, lives for minutes, and is only accepted by AWS because the account trusts the GitHub issuer and the role's trust policy matches the token's claims.
The Trust Policy¶
The module renders a trust policy equivalent to this JSON:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRoleWithWebIdentity",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": [
"repo:infrahouse/aws-control:*",
"repo:infrahouse@*/aws-control@*:*"
]
}
}
}
]
}
Three things must line up for the assume-role call to succeed:
- Federated principal — the ARN of the account's GitHub OIDC provider. The module reads it from
data.aws_iam_openid_connect_provider.githubusing the URLhttps://token.actions.githubusercontent.com, so the provider must already exist. audclaim —sts.amazonaws.com, the audienceaws-actions/configure-aws-credentialsrequests by default.subclaim — identifies the workflow. By default (subject_claims = ["*"]) the module matches any workflow of the given repository, regardless of branch, tag, or environment (the trailing:*).subject_claimsreplaces*with the suffixes you allow, such asref:refs/heads/main— see Configuration.
Subject Claim Formats¶
GitHub is migrating to immutable subject claims that embed numeric organization and repository IDs, so a rename no longer silently breaks (or, worse, silently re-grants) access. New and renamed repositories get the immutable format from 2026-07-15.
| Format | Example | Used by |
|---|---|---|
| Legacy | repo:infrahouse/aws-control:ref:refs/heads/main | Existing repositories |
| Immutable | repo:infrahouse@12345/aws-control@67890:ref:refs/heads/main | New and renamed repositories |
The module allows both patterns, so the same configuration works before and after GitHub flips a repository to the immutable format. Because the numeric IDs are matched with @*, the module does not need to know them at plan time.
Note
Matching repo:<org>@*/<repo>@*:* trusts any organization/repository ID pair whose names match. If a repository is deleted and its name later claimed elsewhere in the same organization, the new repository would also match. Pinning the numeric IDs requires a custom trust policy outside this module.
Role Naming and Tags¶
The default name (ih-tf-<repo_name>-github) makes roles easy to find and audit across accounts. The role carries two provenance tags:
created_by_module=infrahouse/github-role/awsmodule_version= the module version that created it
Providers commonly add more tags through default_tags (for example created_by), and those merge with the module's tags.
Session Duration¶
max_session_duration (default 3600 seconds) caps how long the credentials issued to the workflow remain valid. aws-actions/configure-aws-credentials requests one hour by default; ask for more with its role-duration-seconds input only after raising this variable to at least that value.
Boundaries¶
The module does not:
- create the GitHub OIDC identity provider — use terraform-aws-gh-identity-provider once per account
- attach any permissions — see Examples and Security
- restrict access by branch, tag, or environment — a workflow on any ref of the repository can assume the role
- configure anything on the GitHub side, such as repository secrets or variables