← Back to Index
↳ Project /32AWS · Platform · Governance

AWS Landing Zone Automator

An account vending machine for the gap between one shared account with a root login and a platform team running Control Tower. One apply stands up a SOC 2 ready multi-account foundation; after that a new account is one block in a tfvars file and it arrives with guardrails, logging, budgets, and SSO already applied.

Role
Cloud / Platform
Cloud
AWS
Scope
Organization-wide
Resources
58 (Terraform)
tfvars
account_requests
Organizations
OU tree · all features
SCPs
deny root · region allowlist
Identity Center
3 groups · per-account
Budgets
80% alarm
Vended Account
baseline · no default VPC
Log Archive
org trail · SSE-KMS · locked
One block in a tfvars file, and the account arrives inside guardrails with logging, budgets, and SSO already applied

/01Problem

Multi-account AWS is the recommendation everyone gives and almost nobody implements, because the first account separation is where the work actually is: organizational units, service control policies, centralized immutable audit logging, and single sign-on all have to exist before the second account is worth having.

The target was the middle of that gap. Startups getting SOC 2 ready need account separation, immutable audit logs, least-privilege SSO, and root controls, which is most of what an auditor asks about first. SaaS teams need dev, staging, and prod per product. MSPs need a client to land inside guardrails on day one. All three are the same recurring workflow, not a one-time script.

/02Approach

  • Organizations with all features enabled, and an OU tree of Security, Workloads/Prod, Workloads/NonProd, and Sandbox, so policy attaches to a boundary rather than to individual accounts.
  • Four service control policies as the guardrail layer: deny root user actions, deny leaving the organization, a region allowlist, and CloudTrail tamper protection.
  • An organization CloudTrail encrypted with SSE-KMS writing into a versioned, object-locked bucket in a dedicated log-archive account, so the audit trail is outside the accounts it is auditing and cannot be rewritten by them.
  • Vending itself is an `account_requests` map: each entry creates an account in the right OU with tags, a monthly budget alarm at 80 percent, and an in-account baseline that sets an IAM alias, a strict password policy, a smoke-test role, and removes the default VPC.

/03Architecture

The apply runs in two stages, and the reason is a real Terraform constraint rather than a workaround. Provider configurations are static and must resolve at plan time, but the aliased providers that assume roles into the log-archive and vended accounts need account IDs that do not exist until the first stage finishes. A helper script copies those IDs from stage one outputs into a gitignored tfvars file, and the second apply completes the cross-account wiring.

Nothing sensitive reaches the repository. Account emails, notification addresses, and account IDs live only in gitignored tfvars and remote state, outputs carrying account IDs are marked sensitive, the state bucket name is passed through a gitignored backend config, and CI authenticates with GitHub OIDC against the committed example file only.

/04Outcome

Deployed live against a real organization and validated on six independent checks: an SCP explicitly denying a disallowed region, per-account organization CloudTrail delivery, least-privilege SSO assignment, budget alarms, tag and OU placement, and zero default VPCs in the vended accounts.

Teardown taught more than the build. Closed accounts block OU deletion until they are moved back to the root, the account-close waiter finishes a minute or so before AWS actually settles, and the SSO assignment loop needs the account request map emptied before the final destroy pass will complete. All three are documented in the repo, because the teardown path is the part of a landing zone nobody writes down.

↳ Run Receipt/32
Provision58 resources against a real organization: OUs, 4 SCPs, Identity Center, org CloudTrail, vended accounts
Validate6 of 6 checks passed, including an SCP-denied region and zero default VPCs in vended accounts
DestroyTorn down the same night, state at zero, only the default FullAWSAccess SCP remaining
ResidualTrail KMS key in its mandatory 7-day deletion window, unbilled
Costa few cents for the full deploy-demo-destroy cycle
AWS OrganizationsService Control PoliciesIAM Identity CenterCloudTrailKMSS3 Object LockAWS BudgetsTerraform
StatusCheckingDeployed2026-08-03 18:28 UTCVisitsOriginPrivate S3 + CloudFront OACPipelineGitHub Actions OIDCCounterAPI Gateway + Lambda + DynamoDB