Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 0 additions & 46 deletions .github/workflows/aws-auth-check.yml

This file was deleted.

75 changes: 75 additions & 0 deletions .github/workflows/iam_terraform-backend-role.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Apply Terraform Backend Role

# Terraform GitOps for the 00-remote_state/1-iam/ root: the org-wide S3 state
# access role is created/synchronised from remote. Plans on PR, applies on push
# to main. Credentials come from the identity/00-ci-trust CI role assumed via
# OIDC — no static AWS keys.

on:
push:
branches: [main]
paths:
- '00-remote_state/1-iam/**'
- '.github/workflows/iam_terraform-backend-role.yml'
pull_request:
paths:
- '00-remote_state/1-iam/**'
- '.github/workflows/iam_terraform-backend-role.yml'

permissions:
contents: read

jobs:
terraform:
runs-on: ubuntu-latest
permissions:
id-token: write # mint the OIDC token exchanged for temporary AWS creds
contents: read
steps:
# Fail fast and clearly if creds aren't set, instead of letting the
# tooling emit a confusing assume-role error.
- name: Assert credentials are present
env:
ROLE_ARN: ${{ vars.AWS_GITHUB_ACTIONS_ROLE_ARN }}
run: |
if [ -z "$ROLE_ARN" ]; then
echo "::error::vars.AWS_GITHUB_ACTIONS_ROLE_ARN is not set. Set it to the github-actions-terraform role ARN (provisioned by identity/00-ci-trust/)."
exit 1
fi

- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
# Keep in sync with mise.toml [tools] terraform version.
terraform_version: "1.14"

- name: Assume the CI role via OIDC
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1
with:
role-to-assume: ${{ vars.AWS_GITHUB_ACTIONS_ROLE_ARN }}
aws-region: eu-west-3

- name: Terraform plan
if: github.event_name == 'pull_request'
env:
TF_CHDIR: "00-remote_state/1-iam"
TF_ENVIRONMENT: "00-remote-state-iam.tfvars"
run: |
set -euo pipefail
terraform -chdir="${TF_CHDIR}" init -input=false
terraform -chdir="${TF_CHDIR}" workspace select "${TF_ENVIRONMENT%.*}"
terraform -chdir="${TF_CHDIR}" plan -input=false -var-file="env/${TF_ENVIRONMENT}"

- name: Terraform apply
if: github.event_name == 'push'
env:
TF_CHDIR: "00-remote_state/1-iam"
TF_ENVIRONMENT: "00-remote-state-iam.tfvars"
run: |
set -euo pipefail
terraform -chdir="${TF_CHDIR}" init -input=false
terraform -chdir="${TF_CHDIR}" workspace select "${TF_ENVIRONMENT%.*}"
terraform -chdir="${TF_CHDIR}" apply -auto-approve -input=false -var-file="env/${TF_ENVIRONMENT}"
50 changes: 0 additions & 50 deletions .github/workflows/s3-lister-role.yml

This file was deleted.

48 changes: 0 additions & 48 deletions .github/workflows/scaleway-auth-check.yml

This file was deleted.

46 changes: 31 additions & 15 deletions .github/workflows/scaleway-plan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ name: Scaleway Plan (via s3-lister role)
on:
pull_request:
paths:
- 'cluster/scaleway/**'
- 's3-lister-role/**'
- '02-cluster/scaleway/**'
- '01-iam/aws/**'
- '.github/workflows/scaleway-plan.yml'
workflow_dispatch:

permissions:
contents: read
id-token: write # This is required for requesting the JWT

jobs:
plan:
Expand All @@ -36,10 +37,8 @@ jobs:
id-token: write # mint the OIDC token exchanged for temporary AWS creds
contents: read
env:
# Select the existing remote workspace without `workspace new` (a write the
# read-only role can't perform). The default workspace would collide with
# the state bucket's own state at the bucket root.
TF_WORKSPACE: infrastructure
INFISICAL_MACHINE_IDENTITY_ID: ${{ vars.INFISICAL_MACHINE_IDENTITY_ID }}

# Scaleway provider creds for the refresh. Keys are secrets; the public
# org/project IDs are repo variables (not secrets).
SCW_ACCESS_KEY: ${{ secrets.SCW_ACCESS_KEY }}
Expand All @@ -48,10 +47,10 @@ jobs:
SCW_DEFAULT_PROJECT_ID: ${{ vars.SCW_DEFAULT_PROJECT_ID }}
# The infisical provider authenticates eagerly even with bootstrap_argocd
# off (the gated data source still pulls the provider into the graph), so
# it needs valid machine-identity creds just to configure. With
# bootstrap_argocd=false no secret is actually read.
TF_VAR_infisical_client_id: ${{ secrets.INFISICAL_CLIENT_ID }}
TF_VAR_infisical_client_secret: ${{ secrets.INFISICAL_CLIENT_SECRET }}
# it needs to configure successfully — here via keyless GitHub-OIDC (see
# the "Mint Infisical OIDC token" step). With bootstrap_argocd=false no
# secret is actually read.

steps:
# Fail fast and clearly if creds/vars aren't set, instead of letting the
# tooling emit a confusing assume-role or provider auth error.
Expand All @@ -60,11 +59,11 @@ jobs:
ROLE_ARN: ${{ vars.AWS_S3_LISTER_ROLE_ARN }}
run: |
if [ -z "$ROLE_ARN" ]; then
echo "::error::vars.AWS_S3_LISTER_ROLE_ARN is not set. Set it to the s3-lister role ARN (see s3-lister-role/README.md)."
echo "::error::vars.AWS_S3_LISTER_ROLE_ARN is not set. Set it to the s3-lister role ARN (provisioned by state/10-access)."
exit 1
fi
if [ -z "$SCW_ACCESS_KEY" ] || [ -z "$SCW_SECRET_KEY" ]; then
echo "::error::SCW_ACCESS_KEY and/or SCW_SECRET_KEY are not set (see github-ci/README.md)."
echo "::error::SCW_ACCESS_KEY and/or SCW_SECRET_KEY are not set (see ci/10-scaleway/README.md)."
exit 1
fi

Expand All @@ -85,8 +84,25 @@ jobs:
role-to-assume: ${{ vars.AWS_S3_LISTER_ROLE_ARN }}
aws-region: eu-west-3

# Mint a GitHub OIDC JWT for the Infisical machine identity and stash it in
# $INFISICAL_AUTH_JWT (the provider's default token env var). audience must
# match the identity's boundAudiences. This is the keyless counterpart to
# the universal-auth client_id/secret used locally — no static Infisical
# secret in CI. core.getIDToken is the official @actions/core way to mint
# the token (it masks the value automatically).
- name: Mint Infisical OIDC token
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const jwt = await core.getIDToken('https://github.com/IntegratedDynamic')
core.exportVariable('INFISICAL_AUTH_JWT', jwt)

- name: Terraform plan (read-only role -> no lock, no apply)
env:
TF_CHDIR: "02-cluster/scaleway/"
TF_ENVIRONMENT: "02-cluster-staging.tfvars"
run: |
terraform -chdir=cluster/scaleway init -input=false
terraform -chdir=cluster/scaleway plan \
-lock=false -input=false -var bootstrap_argocd=false
set -euo pipefail
terraform -chdir="${TF_CHDIR}" init -input=false
terraform -chdir="${TF_CHDIR}" workspace select "${TF_ENVIRONMENT%.*}"
terraform -chdir="${TF_CHDIR}" destroy -auto-approve -input=false -var-file="env/${TF_ENVIRONMENT}"
11 changes: 5 additions & 6 deletions .github/workflows/terraform-lock.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ jobs:
fail-fast: false
matrix:
root:
- terraform-state-bucket
- cluster/local
- cluster/scaleway
- github-ci
- aws-github-oidc
- s3-lister-role
- 00-remote_state/0-backend
- 00-remote_state/1-iam
- 01-iam/aws
- 01-iam/scaleway
- 02-cluster/scaleway
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# terraform-state-bucket
# state/00-backend — Terraform state bucket

A standalone Terraform root that provisions a single **AWS S3 bucket** to hold
the Terraform remote state for the **whole org**. Every other root
(`cluster/local/`, `cluster/scaleway/`, `github-ci/`, and future ones) points
(`cluster/local/`, `cluster/scaleway/`, `ci/10-scaleway/`, and future ones) points
its `backend "s3"` at this bucket.

This is intentionally **not** under `cluster/` because it provisions no cluster;
it is the shared substrate the other roots depend on. It lives at the repo root
as its own root module.
It is the shared substrate every other root depends on — strata `00` of the
`state/` domain, applied by an admin.

## What it creates

Expand Down Expand Up @@ -58,13 +57,13 @@ This root creates the very bucket it then stores its state in. Bootstrap order:
2. Apply once with **local state** — temporarily comment out the `backend "s3"`
block in `version.tf` so the bucket gets created:
```bash
terraform -chdir=terraform-state-bucket init
terraform -chdir=terraform-state-bucket apply # creates the bucket (billable)
terraform -chdir=state/00-backend init
terraform -chdir=state/00-backend apply # creates the bucket (billable)
```
3. Re-add the `backend "s3"` block and migrate the local state into the bucket
it now manages:
```bash
terraform -chdir=terraform-state-bucket init -migrate-state
terraform -chdir=state/00-backend init -migrate-state
```

After that, this root's own state lives at `state-backend/terraform.tfstate`
Expand Down
3 changes: 3 additions & 0 deletions 00-remote_state/0-backend/env/00-remote-state-backend.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bucket_prefix = "id-terraform-state"
region = "eu-west-3"
noncurrent_version_expiration_days = 10
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ terraform {
backend "s3" {
bucket = "id-terraform-state20260612164136440800000001"
region = "eu-west-3"
# Prefix kept as "state-backend" (≠ this root's path state/00-backend/) on
# purpose: the state key is decoupled from the directory, so the repo
# restructure was a pure move with zero state migration.
workspace_key_prefix = "state-backend"
key = "terraform.tfstate"
encrypt = true
use_lockfile = true
key = "terraform.tfstate"
encrypt = true
use_lockfile = true
}

required_providers {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
region = "eu-west-3"
role_name = "s3-lister"

# Repo-scoped path + boundary required by the CI grant (aws-github-oidc/). Must
# Repo-scoped path + boundary required by the CI grant (identity/00-ci-trust/). Must
# match the grant's pins exactly or the apply is denied.
role_path = "/tf-managed/IntegratedDynamic/infrastructure/"
permissions_boundary_arn = "arn:aws:iam::503577850357:policy/tf-managed-boundary"
Expand Down
Loading
Loading