Skip to content

Hardening AWS OIDC

Why harden your trust policy?

The default AWS OIDC trust policy scopes access by repository, meaning any GitHub Actions workflow in that repository can mint an OIDC token and assume the role — not just the Terrateam workflow. A malicious pull request that adds a new workflow file or modifies the existing one could assume the same role and exfiltrate credentials.

This guide walks through progressively stronger controls to ensure only Terrateam-originated workflow runs can assume your AWS IAM role.

Layer 1: Restrict to the Terrateam Workflow

Section titled “Layer 1: Restrict to the Terrateam Workflow”

Use GitHub’s job_workflow_ref claim to ensure only the Terrateam workflow file can assume the role. Any other workflow file in the repository will be denied at the AWS level.

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:ORG/REPO:*",
"token.actions.githubusercontent.com:job_workflow_ref": "ORG/REPO/.github/workflows/terrateam.yml@*"
}
}
}
]
}

Layer 2: Restrict to Terrateam-Dispatched Runs

Section titled “Layer 2: Restrict to Terrateam-Dispatched Runs”

The GitHub OIDC token includes an actor claim — the user or app that triggered the workflow. When the Terrateam server dispatches your workflow, the actor is your Terrateam GitHub App bot (e.g. terrateam-action[bot]).

Add the actor condition to your trust policy so only workflows dispatched by the Terrateam app can assume the role:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:actor": "terrateam-action[bot]",
"token.actions.githubusercontent.com:repository": "ORG/REPO",
"token.actions.githubusercontent.com:workflow": "Terrateam Workflow"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:ORG/REPO:*"
}
}
}
]
}

If someone manually dispatches the workflow or adds a new one, the actor will be their GitHub username and AWS will reject the token.

Even with the trust policy locked down, someone could open a PR modifying terrateam.yml to add malicious steps. Enterprise ci_config_update prevents Terrateam from operating on any PR that modifies the workflow file unless the author is explicitly authorized:

access_control:
enabled: true
ci_config_update: ['team:infra-admins']
terrateam_config_update: ['team:infra-admins']
SettingProtectsEffect
ci_config_update.github/workflows/terrateam.ymlBlocks Terrateam operations on PRs that modify the workflow file unless the author matches the ruleset
terrateam_config_update.terrateam/config.ymlBlocks Terrateam operations on PRs that modify the Terrateam config unless the author matches the ruleset

Combine with file-level access control and GitHub branch protections for defense in depth:

access_control:
enabled: true
ci_config_update: ['team:infra-admins']
terrateam_config_update: ['team:infra-admins']
files:
'.github/workflows/': ['team:infra-admins']

Also consider adding a CODEOWNERS file and branch protection rules on .github/workflows/.

AWS only supports a subset of JWT claims in IAM trust policy conditions. Claims like event_name, runner_environment, and repository_visibility exist in the GitHub OIDC token but are not directly usable as IAM condition keys. See the AWS documentation for the full list of supported keys.

As a workaround, GitHub allows you to customize the sub claim to include additional values. This lets you enforce these claims through the sub condition in your trust policy.

  1. Use the GitHub CLI to add the desired claims to the sub field:

    Terminal window
    gh api \
    --method PUT \
    -H "Accept: application/vnd.github+json" \
    -H "X-GitHub-Api-Version: 2022-11-28" \
    /repos/ORG/REPO/actions/oidc/customization/sub \
    --input - <<< '{
    "use_default": false,
    "include_claim_keys": [
    "repo",
    "context",
    "event_name",
    "repository_visibility",
    "runner_environment"
    ]
    }'
  2. With the customized sub claim, you can now enforce all of these conditions in your trust policy:

    {
    "Version": "2012-10-17",
    "Statement": [
    {
    "Effect": "Allow",
    "Principal": {
    "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
    "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
    "token.actions.githubusercontent.com:actor": "terrateam-action[bot]",
    "token.actions.githubusercontent.com:workflow": "Terrateam Workflow",
    "token.actions.githubusercontent.com:repository": "ORG/REPO"
    },
    "StringLike": {
    "token.actions.githubusercontent.com:sub": "repo:ORG/REPO:ref:*:event_name:workflow_dispatch:repository_visibility:private:runner_environment:self-hosted"
    }
    }
    }
    ]
    }

    This ensures:

    • Only the Terrateam GitHub App can trigger the workflow (actor)
    • Only the named workflow can assume the role (workflow)
    • Only workflow_dispatch events are accepted (not manual or other triggers)
    • Only private repositories are accepted
    • Only self-hosted runners are accepted
LayerControlWhat It Prevents
1job_workflow_ref in trust policyOther workflow files assuming the role
2actor in trust policyManual or non-Terrateam workflow dispatches
3Enterprise ci_config_updateUnauthorized modification of the workflow file
4Custom sub claimRuns from non-dispatch events, public repos, or GitHub-hosted runners

For maximum security, combine all four layers. Layers 1 and 2 are available to all users. Layer 3 requires Enterprise. Layer 4 is available to all users and provides the strongest trust policy enforcement.