AWS credentials for planning
terraform plan refreshes state and resolves data sources, so it needs to read your AWS account. The safest way to allow that from GitHub Actions is a role assumed through OIDC. GitHub signs a token for the workflow run, AWS exchanges it for short-lived credentials, and no access key is ever stored.
Infragr.am never sees these credentials. They exist only inside your workflow, for the plan step.
Create the role
Section titled “Create the role”Run these commands with the AWS CLI, signed in to the account the role belongs in. Replace acme/infra and the numeric IDs with your repository’s. The dashboard’s setup dialog fills them in for you.
-
Register GitHub as an identity provider. You need this once per account. Skip it if another repository already uses OIDC in this account.
Terminal window aws iam create-open-id-connect-provider \--url https://token.actions.githubusercontent.com \--client-id-list sts.amazonaws.com \--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1 -
Create a role only this repository can assume.
Terminal window ACCOUNT=$(aws sts get-caller-identity --query Account --output text)aws iam create-role --role-name infragram-plan \--assume-role-policy-document "$(cat <<JSON{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": {"Federated": "arn:aws:iam::${ACCOUNT}: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:acme/infra:*","repo:acme@1234567/infra@89012345:*"]}}}]}JSON)" -
Give it read-only access. The workflow only plans. It never applies.
Terminal window aws iam attach-role-policy --role-name infragram-plan \--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess -
Use it in the workflow.
- uses: aws-actions/configure-aws-credentials@v6with:role-to-assume: arn:aws:iam::123456789012:role/infragram-planaws-region: eu-west-1
Why two sub entries?
Section titled “Why two sub entries?”GitHub can issue the token’s subject in two forms:
repo:acme/infra:pull_request, the classic form, by name;repo:acme@1234567/infra@89012345:pull_request, qualified with the numeric IDs of the owner and the repository.
The ID form means a repository that is renamed or transferred can’t inherit trust granted under its old name. Repositories don’t all use the same form yet. A policy that lists only one will fail against repositories using the other, with an error that looks like a permissions problem:
Not authorized to perform sts:AssumeRoleWithWebIdentityFind your numeric IDs with gh api repos/acme/infra --jq '.owner.id, .id'.
Tighter than ReadOnlyAccess
Section titled “Tighter than ReadOnlyAccess”ReadOnlyAccess is the easy default. To narrow it, the role needs whatever your terraform plan reads: the state backend (for example s3:GetObject on the state key, and DynamoDB for locking if you use it), plus Describe*, List* and Get* for the services your configuration and its data sources touch. To find the exact set, run the plan once with CloudTrail open.
No credentials at all
Section titled “No credentials at all”If another workflow already plans your Terraform, you can skip cloud credentials entirely and reuse its plan.