Automates customer AWS account vending under the workloads OU.
Adding a customer to customers.tfvars produces:
- 1 OU under
workloads/(ou-oti7-vk4layff) named after the customer - 2 member accounts in that OU:
<customer>-dev,<customer>-prod - 1 IAM Identity Center group per role per customer:
<customer>-admin,<customer>-developer, each assigned (via AWS-managed permission sets)AdministratorAccess/PowerUserAccessin both the dev and prod accounts - 1 Identity Center user per entry in
customer_users, added to the matching group by username prefix (a--> admin,d--> developer) - 1 GitHub OIDC provider +
AdministratorAccessIAM role (github-actions-deploy) in each of the dev/prod accounts, trustingrepo:BookingSystemAdvancedOrg/*:*-- i.e. any repo in that GitHub org can assume the role in any customer's dev/prod account, but no other org can
organizations + identity-center are management-account-only resources —
Organizations and IAM Identity Center are always managed from the account
where they're enabled, regardless of which account they target. They live in
the repo root and share one state file.
The GitHub OIDC provider and IAM role, however, are IAM resources that must
physically exist inside each member account. Terraform provider blocks
cannot be generated dynamically per for_each item, so this can't be one
terraform apply. Instead account-baseline/ is a second, tiny root module
applied once per account (its own state file, keyed by
customers/<customer>/<env>/account-baseline.tfstate), with its AWS provider
assuming OrganizationAccountAccessRole into whichever target_account_id
you pass in. .github/workflows/cicd.yml drives this automatically: stage 1
applies the root module and emits an account_matrix output, stage 2 fans
that out into one job per account.
config.tf, variables.tf, main.tf, outputs.tf # root: organizations + identity-center
customers.tfvars # the only file you edit day-to-day
modules/organizations/ # OUs + accounts
modules/identity-center/ # groups, users, permission sets, assignments
modules/github-oidc/ # OIDC provider + deploy role (single account)
account-baseline/ # per-account root that calls modules/github-oidc
- Create (or reuse) an S3 bucket in the management account for Terraform
state -- bucket name is hardcoded in
config.tf/account-baseline/config.tf(388343452097-tfstate-bucket), so nothing to set as a repo variable for this. Locking is native S3 conditional-write locking (use_lockfile = true, requires Terraform >=1.11 -- this repo pinsTF_VERSIONwell above that incicd.yml); no DynamoDB table. - Create a GitHub OIDC provider + deploy role in the management account
by hand (bootstrap chicken-and-egg — this repo's own pipeline can't
provision its own credentials). Set its ARN as the
MANAGEMENT_DEPLOY_ROLE_ARNrepo variable. Scope its trust policy torepo:BookingSystemAdvancedOrg/aws-accounts:*. - Local plans:
terraform initat the repo root needs no flags (backend is fully hardcoded). Inaccount-baseline/,keystill can't be hardcoded (it varies per customer/env), so run:terraform init -backend-config="key=customers/<customer>/<env>/account-baseline.tfstate"
- Add the customer name to
customersincustomers.tfvars. - Add its users to
customer_users(usernames prefixeda-ord-). - Open a PR — CI plans the org/identity-center stage. Merge to
mainto apply it and fan outaccount-baselineinto the two new accounts.
- The GitHub deploy role gets
AdministratorAccess, per the requirement — it's the broadest possible grant to a CI role. Worth narrowing to a deploy-scoped policy once each customer's actual IaC footprint is known. - The deploy role's trust is org-wide (
repo:BookingSystemAdvancedOrg/*:*), not scoped to one repo per customer, per the requirement. This means any repo in the org can deploy withAdministratorAccessinto every customer's dev and prod accounts -- a compromised or careless workflow in one repo can reach every other customer's account.allowed_ref(module variable, defaults to*) is available to narrow this by branch/tag later without restructuring, if that tradeoff needs revisiting. aws_organizations_accounthasprevent_destroy = true— removing a customer fromcustomers.tfvarswill NOT delete its accounts; that has to be a deliberate, separate action (AWS account deletion is effectively irreversible).- Permission sets (
AdministratorAccess/PowerUserAccess) are created once and shared across all customers; only the group -> account assignment is per customer, so there's no permission-set sprawl as customers grow.