Move the Terraform workflows off static AWS credentials onto OIDC
@ale210 is already working on this.
Since Sep 6, 2026.
Assessment
This issue has not been assessed yet.
Description
Overview
We need terraform-plan.yaml and terraform-apply.yaml to authenticate to AWS by assuming a role through GitHub's OIDC provider instead of passing a static access key and secret, because devops-security is the repo that builds the org's GitHub Actions OIDC and is the last repo still using long-lived credentials to reach AWS.
Action Items
- Read the current position before changing anything. Both workflows pass
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}andaws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}toaws-actions/configure-aws-credentials@v4, and neither declarespermissions: id-token: writeat the job or workflow level. Line numbers drift — find the step by its name,Configure AWS Credentials. - Confirm what the secrets actually are. They are the access key of IAM user
devops-iam-github-action(keyAKIAQQWOSJEPUH74UTOJ, created 2024-02-19, never rotated, still in active use). Verify that nothing outside these two workflows uses that user before planning to delete it —aws iam get-access-key-last-usedand CloudTrail on the username are the two places to look. - Do not create the roles with
module "aws-gha-oidc-providers". That module creates anaws_iam_openid_connect_provider, and AWS permits exactly one provider per URL per account.arn:aws:iam::035866691871:oidc-provider/token.actions.githubusercontent.comalready exists — the incubator instantiation created it — so a second instantiation fails withEntityAlreadyExists. The module is the right shape to copy and the wrong thing to call. - Design two roles, not one. The plan/apply privilege split is the part of the pattern that static keys have no equivalent for, and it is a security win here alongside retiring the keys.
devops-security-tf-plan— read-only,subscoped torepo:hackforla/devops-security:ref:refs/heads/*andrepo:hackforla/devops-security:pull_request.devops-security-tf-apply—arn:aws:iam::aws:policy/AdministratorAccess,subscoped torepo:hackforla/devops-security:ref:refs/heads/mainonly. The branch scoping is what keeps this safe: a branch or pull request cannot assume this role at all.- Both trust
arn:aws:iam::035866691871:oidc-provider/token.actions.githubusercontent.comwithtoken.actions.githubusercontent.com:aud = sts.amazonaws.com. AdministratorAccesson the apply role is a decision, not a derivation, and is worth recording as one: the IAM user CI runs as today has a narrower set — managedTerraformIAM,TerraformDynamoDBAccess,DevopsSecurityTerraformBucketAccess,AmazonS3ReadOnlyAccess, plus inlineGitHubActionsOIDCThumbprintUpdatePolicy— so this widens the apply path rather than matching it one-for-one. It matchesincubator-tf-apply, which is the same shape one repo over. When hackforla/incubator#138 reports back on narrowing incubator's apply privileges, its conclusions apply here too.
- Work out what the plan role needs beyond
ReadOnlyAccessbefore creating it, because a plan is not a read-only operation against this backend.terraform/prod.backend.tfvarssetsdynamodb_table = "hfla_ops_terraform_table", so every plan acquires and releases a state lock, which needsdynamodb:PutItemanddynamodb:DeleteItem— neither of whichReadOnlyAccessgrants. The existingTerraformDynamoDBAccesspolicy grants exactly this set and can be attached to the plan role. Noteincubator-tf-plancarries onlyReadOnlyAccessplus a secrets-read policy and appears to lack lock-write permission entirely, so check how incubator's plan actually behaves rather than copying it — either it runs with locking disabled or something else is going on, and the answer decides whether this role needs the DynamoDB policy or the workflow needs-lock=false. - Settle the two tagging questions before creating the roles, and record the answers on this issue. A tag is hard to change once an audit depends on it.
- The exact tag key and value marking these roles as deliberately outside Terraform. No
immuneorexemptconvention exists in any of the three repos today. The only tagging precedent isuser_tagson IAM users interraform/aws-users.tf, which uses quoted title-case keys ("Project","Access Level") — match that shape or diverge on purpose. - Which audit the tag exempts the roles from, by name. Decision record "Use it or lose it policy for IAM User Accounts" is about IAM users and these are roles, so the tag may be pre-empting an audit that does not exist. If it cannot be named, the tag documents nothing.
- The exact tag key and value marking these roles as deliberately outside Terraform. No
- Create the two roles by hand in account
035866691871, and capture each role's trust policy, attached policies and tags before and after the change. This is an AWS write with no PR trail, so it follows the same discipline as the org-account work: one change at a time, verified after each. These roles are created manually on purpose — it dissolves the bootstrap problem where the Terraform that creates the role is run by the workflow that needs it — which is what makes the switch below a single PR. - Write down, in
terraform/aws-gha-oidc-providers.tfitself, that these two roles exist outside Terraform and why. A reader of that file will otherwise see incubator's roles declared and devops-security's absent and "fix" it, which re-creates the bootstrap problem this decision removed. The note belongs where they will be standing, not only in this issue. - Switch both workflows in one PR. Replace the two
secrets.AWS_*inputs withrole-to-assume/role-session-name/aws-region: us-west-2, and addpermissions: id-token: writealongside the existingcontents: read.hackforla/incubator's.github/workflows/terraform-plan.yamlis the working example to copy. - Verification does not happen by itself here, and this is the trap. Both workflows filter on
paths: ['**/*.tf'], so a PR that changes only workflow files triggers neither one — the switch would sit unexercised until someone else's Terraform change, possibly weeks later. Include a no-op.tfchange in the same PR (a comment line is enough) so the plan runs on the PR and the apply runs on merge. - After the PR merges, confirm the apply run assumed
devops-security-tf-applyand succeeded, and that the plan comment appeared on the PR before merge. Both are observable only after the fact, which is why they are listed here rather than as branch checks. - After both runs are confirmed green, retire the credentials: delete access key
AKIAQQWOSJEPUH74UTOJ, delete the repository secretsAWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY, and decide whether the IAM userdevops-iam-github-actionitself should go. Note the user is not currently declared in Terraform — verified after hackforla/devops-security#180 merged — so deleting it also removes an unmanaged resource; if it is kept instead, it needs to be brought intoterraform/aws-users.tf. Do not do any of this before the runs are green — it is the rollback path.
Resources/Instructions
.github/workflows/terraform-plan.yamland.github/workflows/terraform-apply.yaml— the two files that change. The credential step is identical in both.terraform/aws-gha-oidc-providers.tf— where the incubator roles are declared, and where the note about these roles living outside Terraform belongs.terraform/modules/aws-gha-oidc-providers/— the module to read for the trust-policy shape. Do not instantiate it; see the action item above.terraform/prod.backend.tfvars— the S3 backend and the DynamoDB lock table the plan role has to be able to write to.hackforla/incubator.github/workflows/terraform-plan.yamlandterraform-apply.yaml— the working OIDC example, assumingarn:aws:iam::035866691871:role/incubator-tf-plan.- hackforla/devops-security#170 — bumps
aws-actions/configure-aws-credentialsv4→v6 anddflook/terraform-*v1→v3 in these same two files, and rewrites the very step this issue rewrites. Whichever starts first, check the other: if this issue lands first, #170 should close itself as covered for theconfigure-aws-credentialshalf. - hackforla/devops-security#187 — stops the merge-triggered apply auto-approving itself, and adds a
workflow_dispatchtrigger toterraform-apply.yaml. It is blocked on this issue: that dispatch path is a deliberately unreviewed apply, and it is only safe oncedevops-security-tf-applyscopessubtorefs/heads/main, because the static keys it replaces carry no branch scoping at all. #187 also hits the samepaths: ['**/*.tf']verification trap described above. Nothing in this issue needs to change for it — but if the apply role here ends up not branch-scoped, #187 needs revisiting before it is worked. - hackforla/incubator#139 (Terraform plan via Identity Center) and hackforla/incubator#138 (restrict incubator apply privileges) — separate work with the same goal of retiring long-lived AWS credentials. #138 is researching the plan/apply privilege split this issue applies.
- AWS account
035866691871, regionus-west-2.
- Dominant language
- HCL
- Stars
- 1
- Forks
- 14
- Avg merge
- 1h 3m
- Merged PRs (30d)
- 21
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from hackforla/devops-security
-
complexity: medium feature: maintenance role: DevOps Engineer size: 2pt
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
hackforla/devops-security#170 ·
-
complexity: small Feature: Onboarding/Contributing.md good first issue role: DevOps Engineer size: 0.5pt
Difficulty 1/5 Under an hour Newbie friendliness 90/100
hackforla/devops-security#163 ·
-
complexity: small feature: AWS user request role: DevOps Engineer size: 1pt
hackforla/devops-security#198 · 1 assignee ·
-
complexity: prework Feature: Onboarding/Contributing.md role: DevOps Engineer role: missing size: 1pt
Difficulty 4/5 3-5 days Newbie friendliness 45/100
hackforla/devops-security#195 · 1 assignee ·
-
complexity: medium Feature: Onboarding/Contributing.md role: DevOps Engineer size: 3pt
Difficulty 4/5 3-5 days Newbie friendliness 45/100
hackforla/devops-security#171 ·