A landing zone is DevOps jargon for "the baseline, org-wide AWS setup you stand up before any application infrastructure" - the accounts, the security guardrails, the audit logging, the login system, and the budgets. It runs in the management account (the root of the AWS Organization).
platform/main.tf is almost entirely a set of module calls -
it composes six building blocks rather than defining resources itself:
| Module call | What it sets up | AWS concepts |
|---|---|---|
module.org_foundation |
The AWS Organization, OUs, and the member accounts (dev/staging/prod/security). | Organizations, OUs, accounts |
module.guardrails |
Service Control Policies - e.g. region lock to Canada, baseline protections. | SCPs |
module.org_cloudtrail |
One org-wide audit trail writing to a hardened, separate log-archive bucket, with a break-glass alarm. | CloudTrail, S3, CloudWatch |
module.identity_center |
Human login: permission sets and group assignments, federated to Microsoft Entra. | IAM Identity Center |
module.budgets |
Org-wide and per-account spending budgets and alerts. | Budgets, SNS |
Two beginner-relevant patterns appear here.
Two-phase apply
The README and SETUP describe platform as a two-phase apply, and the code is built for it. Phase 1 creates the accounts; only after the accounts exist (and their IDs are known) can phase 2 configure things inside them. You target phase 1 explicitly:
tofu -chdir=platform apply -target=module.org_foundation # phase 1: accounts
tofu -chdir=platform output # copy the new account IDs
# ...wire up Entra login...
tofu -chdir=platform apply # phase 2: everything else-target=... limits an apply to one part of the graph - useful here, but generally
something you use sparingly.
Placeholders so validate works before real IDs exist
Before phase 1 runs, the real account IDs don't exist yet. To keep make validate
working anyway, the code substitutes placeholder IDs using the coalesce function
(platform/main.tf):
account_ids = {
dev = coalesce(var.dev_account_id, "000000000000")
# coalesce returns the first non-null argument: the real ID if set, else the placeholder
...
}coalesce(a, b) returns a if it isn't null, otherwise b. So before you've
filled in the real IDs, validation still passes against the dummy 000000000000.
Why a separate Security account and a separate log-archive bucket? Separation of duties. If an attacker compromises a workload account, the audit logs live somewhere they can't reach and tamper with. This is a standard landing-zone security pattern.