feat: AWS GitHub Actions OIDC for Terraform state access#31
Merged
Conversation
Add a standalone `aws-github-oidc/` root that lets GitHub Actions assume
an AWS IAM role via OIDC to read/write the Terraform S3 remote state, so
no static AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY are needed in GitHub
secrets for the S3 backend.
- aws-github-oidc/: AWS-only root (S3 backend, key aws-github-oidc/
terraform.tfstate). Creates the GitHub Actions OIDC provider, a role
(github-actions-terraform) whose trust policy is scoped to
repo:IntegratedDynamic/infrastructure:*, and a least-privilege inline
policy granting S3 access to exactly the state bucket. Exposes role_arn.
- mise.toml: aws-github-oidc-{init,plan,apply} tasks; root added to `lock`.
- terraform-lock.yml: aws-github-oidc added to the lock matrix.
Scaleway IAM is not an OIDC relying party, so Scaleway resources keep
using the static API key (github-ci/ / terraform-ci/); OIDC covers only
the AWS/S3 side.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the hand-written OIDC provider/role/policy with the terraform-aws-modules/iam modules (iam-oidc-provider + iam-role with enable_github_oidc) — more concise and consistent. Trust stays scoped to repo:<org>/<repo>:*; S3 access is account-wide read for now (AmazonS3ReadOnlyAccess). - fix outputs.role_arn to reference module.iam_role_github_oidc.arn (was the whole module object) - order the role after the provider via depends_on (trust policy needs the provider ARN to exist first) - relock providers for darwin_arm64 + linux_amd64 (adds hashicorp/tls) Add .github/workflows/aws-auth-check.yml: a PR smoke test that assumes the role via GitHub OIDC (no static keys) and runs `aws s3 ls`. Wired to the new repo variable AWS_GITHUB_ACTIONS_ROLE_ARN. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nbrieussel
marked this pull request as ready for review
June 13, 2026 14:00
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Terraform state backend was migrated from Scaleway Object Storage to real AWS S3 (
eu-west-3, Paris — see #27). Without this, CI workflows would need staticAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYin GitHub secrets to reach the state bucket. AWS IAM is an OIDC relying party, so we can replace those static keys with GitHub Actions OIDC → AWS IAM role assumption — no long-lived AWS credentials in GitHub.Scaleway IAM is not an OIDC relying party (feature request), so Scaleway resources (Kapsule, VPC) keep using the static, scoped Scaleway API key from
github-ci//terraform-ci/. OIDC here covers only the AWS/S3 side — Scaleway OIDC remains a non-goal, blocked upstream.Changes
aws-github-oidc/root (AWS provider only; S3 backend, keyaws-github-oidc/terraform.tfstate, same bucket as every other root):aws_iam_openid_connect_provider.github_actions— GitHub Actions OIDC provider (one per account; import if it already exists).aws_iam_role.github_actions(github-actions-terraform) — trust policy scoped torepo:IntegratedDynamic/infrastructure:*with audiencests.amazonaws.com.aws_iam_role_policy.github_actions_s3(terraform-state-s3) — least privilege:ListBucket+GetBucketVersioningon the bucket, objectGet/Put/Deleteon its keys (theuse_lockfile = trueS3-native lock rides on object R/W — no DynamoDB).outputs.tf→role_arn;README.md; gitignorednico.auto.tfvarsplaceholder (no secrets — AWS creds come from the local AWS CLI/SSO session)..terraform.lock.hclfordarwin_arm64+linux_amd64.mise.toml—aws-github-oidc-{init,plan,apply}tasks; root added tolock.terraform-lock.yml—aws-github-oidcadded to the lock matrix.Verify
fmt,validate,providers lock(both platforms) andactionlintall pass locally.terraform planwas not run here — the local AWS SSO token was expired and I won't trigger an auth flow; runmise run aws-github-oidc-planafteraws sso loginand review before apply.Post-apply manual steps (operator)
mise run aws-github-oidc-apply(creates the OIDC provider + role).aws-actions/configure-aws-credentialswithrole-to-assume: ${{ vars.AWS_GITHUB_ACTIONS_ROLE_ARN }}andpermissions: id-token: write.Deferred — depends on the open #30 (
terraform-ci/)This branch is cut from
main, where neitherterraform-ci/nor thelist-state-bucket-terraform-cijob exist yet — they live in the still-open #30. To avoid cross-PR conflicts (one issue = one PR), the following are intentionally not in this PR and should land in a follow-up on top of #30 once it merges:scaleway-auth-check.yml→list-state-bucket-terraform-cito use OIDC instead of staticTF_SCW_*AWS keys: addpermissions: id-token: write, anaws-actions/configure-aws-credentialsstep (pinned SHA) withrole-to-assume: ${{ vars.AWS_GITHUB_ACTIONS_ROLE_ARN }}, runaws s3 ls s3://id-terraform-state20260612164136440800000001, and drop theenvironment: scalewaygate (OIDC needs no secret environment).terraform-ci/main.tfcleanup — dropObjectStorageReadWrite(state is now on AWS S3, not Scaleway), keeping onlyKubernetesFullAccess+VPCFullAccess.If preferred, I can rebase this branch onto #30's branch and fold those two items in — but that stacks PRs, so I left it as a documented follow-up by default.
🤖 Generated with Claude Code