Layer 2 - platform: The Landing Zone

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:

BASH
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):

HCL
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.

Adesh Tamrakar
SOFTWARE ENGINEER · VAULT

Notes, insights and random discoveries from a working engineer's vault - written for future me, published for you.