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
17 changes: 9 additions & 8 deletions .github/workflows/iam_terraform-backend-role.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
name: Apply Terraform Backend Role

# Terraform GitOps for the 00-remote_state/1-iam/ root: the org-wide state-access
# role is created/synchronised from remote via the .github/actions/terraform
# composite action. 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.
# Terraform GitOps for the 01-iam/ci-managed/aws-state-access/ root: the org-wide
# state-access role is created/synchronised from remote via the
# .github/actions/terraform composite action. Plans on PR, applies on push to main.
# Credentials come from the 01-iam/bootstrap/aws CI role assumed via OIDC — no
# static AWS keys.

on:
push:
branches: [main]
paths:
- '00-remote_state/1-iam/**'
- '01-iam/ci-managed/aws-state-access/**'
- '.github/actions/terraform/**'
- '.github/workflows/iam_terraform-backend-role.yml'
pull_request:
paths:
- '00-remote_state/1-iam/**'
- '01-iam/ci-managed/aws-state-access/**'
- '.github/actions/terraform/**'
- '.github/workflows/iam_terraform-backend-role.yml'

Expand All @@ -38,7 +39,7 @@ jobs:
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 (provisioned by identity/00-ci-trust/)."
echo "::error::vars.AWS_GITHUB_ACTIONS_ROLE_ARN is not set (provisioned by 01-iam/bootstrap/aws/)."
exit 1
fi

Expand All @@ -49,7 +50,7 @@ jobs:

- uses: ./.github/actions/terraform
with:
root: 00-remote_state/1-iam
root: 01-iam/ci-managed/aws-state-access
tfvars-file: 00-remote-state-iam.tfvars
aws-role-arn: ${{ vars.AWS_GITHUB_ACTIONS_ROLE_ARN }}
command: ${{ github.event_name == 'push' && 'apply' || 'plan' }}
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/scaleway.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ name: Terraform — Scaleway cluster
# - workflow_dispatch → up/down on demand (plan | apply | destroy)
#
# State R/W + lock uses the org state role (vars.AWS_TF_STATE_ROLE_ARN —
# AmazonS3FullAccess, see 00-remote_state/1-iam). Scaleway cluster lifecycle uses
# the CI API key from the `scaleway` environment (Kubernetes/VPC/PrivateNetwork
# FullAccess, see 01-iam/scaleway). Provider creds are set as job env (read by the
# AmazonS3FullAccess, see 01-iam/ci-managed/aws-state-access). Scaleway cluster
# lifecycle uses the CI API key from the `scaleway` environment (Kubernetes/VPC/
# PrivateNetwork FullAccess, see 01-iam/bootstrap/scaleway). Provider creds are set as job env (read by the
# action) — never passed as plain action inputs. No static AWS keys.

on:
Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
ROLE_ARN: ${{ vars.AWS_TF_STATE_ROLE_ARN }}
run: |
if [ -z "$ROLE_ARN" ]; then
echo "::error::vars.AWS_TF_STATE_ROLE_ARN is not set (provisioned by 00-remote_state/1-iam)."
echo "::error::vars.AWS_TF_STATE_ROLE_ARN is not set (provisioned by 01-iam/ci-managed/aws-state-access)."
exit 1
fi

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/terraform-lock.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ jobs:
fail-fast: false
matrix:
root:
- 00-remote_state/0-backend
- 00-remote_state/1-iam
- 01-iam/aws
- 01-iam/scaleway
- 00-remote_state
- 01-iam/bootstrap/aws
- 01-iam/bootstrap/scaleway
- 01-iam/ci-managed/aws-state-access
- 02-cluster/scaleway
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# state/00-backend — Terraform state bucket
# 00-remote_state — 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/`, `ci/10-scaleway/`, and future ones) points
its `backend "s3"` at this bucket.
(`02-cluster/local/`, `02-cluster/scaleway/`, `01-iam/bootstrap/scaleway/`, and
future ones) points its `backend "s3"` at this bucket.

It is the shared substrate every other root depends on — strata `00` of the
`state/` domain, applied by an admin.
It is the shared substrate every other root depends on — the `00-remote_state`
domain (a single-root domain, so it is flattened: the domain folder *is* the
root), applied by an admin.

## What it creates

Expand Down Expand Up @@ -57,13 +58,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=state/00-backend init
terraform -chdir=state/00-backend apply # creates the bucket (billable)
terraform -chdir=00-remote_state init
terraform -chdir=00-remote_state 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=state/00-backend init -migrate-state
terraform -chdir=00-remote_state init -migrate-state
```

After that, this root's own state lives at `state-backend/terraform.tfstate`
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
20 changes: 11 additions & 9 deletions 01-iam/aws/README.md → 01-iam/bootstrap/aws/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# identity/00-ci-trust — CI identity & governance
# 01-iam/bootstrap/aws — CI identity & governance

A standalone Terraform root that provisions **keyless GitHub-OIDC → AWS** access
for this repo's CI: an IAM OIDC identity provider, plus an IAM role GitHub
Actions assumes (via short-lived OIDC tokens) to read/write the Terraform
**remote state on AWS S3** — **no static AWS keys in GitHub secrets**.

This is **not** under `cluster/` — it provisions no cluster. It's a CI-platform
concern, kept as its own root so its state and blast radius stay small.
This is **not** under `02-cluster/` — it provisions no cluster. It's the trust
anchor of the `01-iam` domain (`bootstrap/` = human-applied), kept as its own
root so its state and blast radius stay small.

## Why OIDC here (and why Scaleway still uses a static key)

Expand All @@ -18,7 +19,7 @@ GitHub secrets.

Scaleway resources (Kapsule, VPC) are a different story: **Scaleway IAM is not an
OIDC relying party** ([feature request](https://feature-request.scaleway.com/posts/761/oidc-provider-for-external-ci-cd)),
so those keep using a scoped, static Scaleway API key (see `ci/10-scaleway/`).
so those keep using a scoped, static Scaleway API key (see `01-iam/bootstrap/scaleway/`).
OIDC here covers **only** the AWS/S3 side. Revisit Scaleway OIDC
if/when Scaleway ships it.

Expand Down Expand Up @@ -79,8 +80,8 @@ inline in [`iam-ci.tf`](./iam-ci.tf)):
Verify the guardrails with the IAM policy simulator, e.g.:

```bash
ROLE=$(terraform -chdir=identity/00-ci-trust output -raw role_arn)
B=$(terraform -chdir=identity/00-ci-trust output -raw permissions_boundary_arn)
ROLE=$(terraform -chdir=01-iam/bootstrap/aws output -raw role_arn)
B=$(terraform -chdir=01-iam/bootstrap/aws output -raw permissions_boundary_arn)
# CreateRole without our boundary -> explicitDeny
aws iam simulate-principal-policy --policy-source-arn "$ROLE" \
--action-names iam:CreateRole \
Expand All @@ -99,8 +100,9 @@ aws iam simulate-principal-policy --policy-source-arn "$ROLE" \
## Apply

```bash
mise run ci-trust-plan # terraform init && plan — review first
mise run ci-trust-apply # terraform apply (creates IAM resources)
terraform -chdir=01-iam/bootstrap/aws init
terraform -chdir=01-iam/bootstrap/aws plan # review first
terraform -chdir=01-iam/bootstrap/aws apply # creates IAM resources
```

> Never `terraform apply`/`destroy` here without explicit approval.
Expand All @@ -113,7 +115,7 @@ variable (an ARN is a public identifier, not a secret). Set it once:
```bash
gh variable set AWS_GITHUB_ACTIONS_ROLE_ARN \
--repo IntegratedDynamic/infrastructure \
--body "$(terraform -chdir=identity/00-ci-trust output -raw role_arn)"
--body "$(terraform -chdir=01-iam/bootstrap/aws output -raw role_arn)"
```

Workflows then assume the role with `aws-actions/configure-aws-credentials`:
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# 01-iam/infisical — GitHub Actions → Infisical (OIDC)
# 01-iam/bootstrap/infisical — GitHub Actions → Infisical (OIDC)

A standalone Terraform root that sets up **keyless GitHub-OIDC → Infisical**: a
dedicated Infisical machine identity that GitHub Actions authenticates as by
presenting its short-lived OIDC token — no long-lived Infisical secret stored in
CI.

This is the keyless counterpart to [`01-iam/scaleway`](../scaleway) (which has to
This is the keyless counterpart to [`01-iam/bootstrap/scaleway`](../scaleway) (which has to
use a static API key because Scaleway IAM isn't an OIDC relying party). Infisical
**is**, so we get the real keyless flow here.

Expand Down Expand Up @@ -48,9 +48,9 @@ with it instead of any static secret.
## Apply

```bash
terraform -chdir=01-iam/infisical init
terraform -chdir=01-iam/infisical plan -var-file=env/01-iam-infisical.tfvars
terraform -chdir=01-iam/infisical apply -var-file=env/01-iam-infisical.tfvars
terraform -chdir=01-iam/bootstrap/infisical init
terraform -chdir=01-iam/bootstrap/infisical plan -var-file=env/01-iam-infisical.tfvars
terraform -chdir=01-iam/bootstrap/infisical apply -var-file=env/01-iam-infisical.tfvars
```

> Never `apply`/`destroy` here without explicit approval.
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
20 changes: 11 additions & 9 deletions 01-iam/scaleway/README.md → 01-iam/bootstrap/scaleway/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# ci/10-scaleway — Scaleway CI identity
# 01-iam/bootstrap/scaleway — Scaleway CI identity

A standalone Terraform root that provisions the **Scaleway identity GitHub
Actions uses to authenticate to Scaleway**. First real consumer: a smoke-test
workflow that lists Object Storage buckets; the Terraform CI/CD pipeline itself
is a separate, later concern.

This is **not** under `cluster/` — it provisions no cluster. It's a CI-platform
concern, kept as its own root so its state and blast radius stay small.
This is **not** under `02-cluster/` — it provisions no cluster. It's a
`01-iam/bootstrap/` trust anchor (human-applied), kept as its own root so its
state and blast radius stay small.

## Why a static key and not OIDC

Expand Down Expand Up @@ -58,8 +59,9 @@ Same as the other roots:
## Apply

```bash
mise run scaleway-ci-plan # terraform init && plan — review first
mise run scaleway-ci-apply # terraform apply (billable: creates an IAM key)
terraform -chdir=01-iam/bootstrap/scaleway init
terraform -chdir=01-iam/bootstrap/scaleway plan # review first
terraform -chdir=01-iam/bootstrap/scaleway apply # billable: creates an IAM key
```

> Never `terraform apply`/`destroy` here without explicit approval.
Expand All @@ -77,12 +79,12 @@ them into your shell history or echo them**:
# SCW_ACCESS_KEY is a public identifier, exposed as a Terraform output:
gh secret set SCW_ACCESS_KEY \
--repo IntegratedDynamic/infrastructure \
--body "$(terraform -chdir=ci/10-scaleway output -raw access_key)"
--body "$(terraform -chdir=01-iam/bootstrap/scaleway output -raw access_key)"

# SCW_SECRET_KEY is sensitive — pipe it from the API key resource without printing:
gh secret set SCW_SECRET_KEY \
--repo IntegratedDynamic/infrastructure \
--body "$(terraform -chdir=ci/10-scaleway state show -no-color scaleway_iam_api_key.github_ci \
--body "$(terraform -chdir=01-iam/bootstrap/scaleway state show -no-color scaleway_iam_api_key.github_ci \
| awk '/secret_key/ {print $3; exit}' | tr -d '\"')"
```

Expand All @@ -94,7 +96,7 @@ from stdin.)

The smoke-test workflow (`.github/workflows/scaleway-auth-check.yml`) runs
`scw object bucket list region=fr-par` against the key. It triggers on any PR
that touches `ci/10-scaleway/**` or the workflow itself, so you can validate before
that touches `01-iam/bootstrap/scaleway/**` or the workflow itself, so you can validate before
merge.

It needs two **repo variables** (public identifiers, not secrets — the scw CLI
Expand Down Expand Up @@ -122,7 +124,7 @@ The API key lives entirely in this root's state.
- **On demand** — force it early with:

```bash
terraform -chdir=ci/10-scaleway apply -replace=scaleway_iam_api_key.github_ci
terraform -chdir=01-iam/bootstrap/scaleway apply -replace=scaleway_iam_api_key.github_ci
```

Either way the key material changes, so **re-run the `gh secret set` steps above**
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading
Loading