Skip to content

feat: AWS GitHub Actions OIDC for Terraform state access#31

Merged
nbrieussel merged 2 commits into
mainfrom
feat/aws-github-oidc
Jun 13, 2026
Merged

feat: AWS GitHub Actions OIDC for Terraform state access#31
nbrieussel merged 2 commits into
mainfrom
feat/aws-github-oidc

Conversation

@nbrieussel

Copy link
Copy Markdown
Contributor

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 static AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY in 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

  • New aws-github-oidc/ root (AWS provider only; S3 backend, key aws-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 to repo:IntegratedDynamic/infrastructure:* with audience sts.amazonaws.com.
    • aws_iam_role_policy.github_actions_s3 (terraform-state-s3) — least privilege: ListBucket + GetBucketVersioning on the bucket, object Get/Put/Delete on its keys (the use_lockfile = true S3-native lock rides on object R/W — no DynamoDB).
    • outputs.tfrole_arn; README.md; gitignored nico.auto.tfvars placeholder (no secrets — AWS creds come from the local AWS CLI/SSO session).
    • .terraform.lock.hcl for darwin_arm64 + linux_amd64.
  • mise.tomlaws-github-oidc-{init,plan,apply} tasks; root added to lock.
  • terraform-lock.ymlaws-github-oidc added to the lock matrix.

Verify

terraform -chdir=aws-github-oidc fmt -check
terraform -chdir=aws-github-oidc validate          # passes offline
mise run aws-github-oidc-plan                        # needs `aws sso login` first
actionlint .github/workflows/*.yml

fmt, validate, providers lock (both platforms) and actionlint all pass locally. terraform plan was not run here — the local AWS SSO token was expired and I won't trigger an auth flow; run mise run aws-github-oidc-plan after aws sso login and review before apply.

Post-apply manual steps (operator)

  1. mise run aws-github-oidc-apply (creates the OIDC provider + role).
  2. Wire the role ARN into CI as a repo variable (an ARN is a public identifier, not a secret), since it's only known after apply:
    gh variable set AWS_GITHUB_ACTIONS_ROLE_ARN \
      --repo IntegratedDynamic/infrastructure \
      --body "$(terraform -chdir=aws-github-oidc output -raw role_arn)"
    Workflows then use aws-actions/configure-aws-credentials with role-to-assume: ${{ vars.AWS_GITHUB_ACTIONS_ROLE_ARN }} and permissions: id-token: write.

Deferred — depends on the open #30 (terraform-ci/)

This branch is cut from main, where neither terraform-ci/ nor the list-state-bucket-terraform-ci job 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:

  • Rework scaleway-auth-check.ymllist-state-bucket-terraform-ci to use OIDC instead of static TF_SCW_* AWS keys: add permissions: id-token: write, an aws-actions/configure-aws-credentials step (pinned SHA) with role-to-assume: ${{ vars.AWS_GITHUB_ACTIONS_ROLE_ARN }}, run aws s3 ls s3://id-terraform-state20260612164136440800000001, and drop the environment: scaleway gate (OIDC needs no secret environment).
  • terraform-ci/main.tf cleanup — drop ObjectStorageReadWrite (state is now on AWS S3, not Scaleway), keeping only KubernetesFullAccess + 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

Nicolas Brieussel and others added 2 commits June 13, 2026 15:01
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
nbrieussel marked this pull request as ready for review June 13, 2026 14:00
@nbrieussel
nbrieussel merged commit 30433d2 into main Jun 13, 2026
9 checks passed
@nbrieussel
nbrieussel deleted the feat/aws-github-oidc branch June 13, 2026 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant