AWS Lesson 83 of 123

AWS Landing Zone: OU Structure & Account Baselines — Security/Infrastructure/Workloads/Sandbox OUs, Account Factory Baselines & Environment Separation

In a nutshell

If AWS Organizations is an apartment building, then every AWS account is a separate apartment: a fire in one can’t spread to the others (that is your blast radius), each has its own electricity meter (billing) and its own fuse rating (service quotas), and the locks are completely independent (IAM). An organizational unit (OU) is a floor of that building. A house rule you pin to a floor — a Service Control Policy (SCP) — automatically applies to every apartment on that floor and every floor beneath it, and, crucially, it can only ever forbid things, never hand out new privileges. This lesson is about drawing the floor plan: which apartments share a floor, what house rules hang on each floor, and what “starter furniture” (the baseline) every new apartment comes with on move-in day.

The one idea to carry through everything below: you group accounts by the guardrails they should share, not by which team or department rents them. Two production accounts that must obey the same rules belong on the same floor even if different teams own them; a team’s dev and prod accounts belong on different floors even though one team owns both. Teams reorganize constantly; guardrails and blast radius are stable. So the tree is built from policy and isolation, and ownership is carried by tags and by the account boundary itself.

Why should a beginner care? This OU tree is the load-bearing wall of the whole landing zone. Control Tower controls, Config conformance packs, IAM Identity Center permission sets, backup policies, and your billing rollups all attach to it. Design it well and governance “snaps on” at the right altitude with almost no per-account toil; design it badly and every later decision inherits the debt.

Level: Advanced · Time: ~40 min

Before you start, you should be comfortable with:

After this lesson you will be able to:

Where this fits

This is part 3 of AWS Landing Zone & Control Tower. Part 1 established the multi-account strategy and the management account; part 2 stood up the landing zone with AWS Control Tower, the Security and Log Archive accounts, and the baseline log/audit plumbing. This part decides the shape of the organization: the organizational unit (OU) hierarchy that Service Control Policies (SCPs), the AWS Control Tower controls (formerly “guardrails”), and centralized config hang off; the per-account baseline that every account inherits the moment it is vended; and the environment separation model that keeps dev, test, and prod in different blast-radius zones. OUs and account baselines are the load-bearing wall — Control Tower controls, Config conformance packs, IAM Identity Center permission sets, and your billing rollups all attach to this tree. Get the tree wrong and every later workstream inherits the debt; get it right and governance “snaps on” at the correct altitude with almost no per-account toil.

AWS Landing Zone & Control Tower — animated overview

The recommended OU structure

What an OU actually is

An organizational unit in AWS Organizations is a container inside the organization root that groups AWS accounts so you can apply policy to many accounts at once through inheritance. The policy types that flow down an OU branch are Service Control Policies (the IAM permission ceiling — they never grant, only constrain the maximum available permissions), Resource Control Policies (RCPs) (a maximum-permission ceiling on resources, e.g. forcing aws:SecureTransport on every S3 bucket and SQS queue org-wide), Tag Policies, Backup Policies, and Declarative Policies for EC2/EBS/VPC. AWS Control Tower additionally attaches its controls at the OU level — preventive controls are implemented as SCPs, detective controls as AWS Config rules, and proactive controls as AWS CloudFormation Hooks.

Three hard rules shape every design decision:

The single most important framing: an OU is a unit of policy, not a unit of ownership. The mistake juniors make is modeling the company’s departments as OUs. The mistake architects make is modeling blast radius and shared controls as OUs and letting tags, not the tree, carry ownership.

The reference hierarchy (AWS SRA / Control Tower aligned)

The AWS Security Reference Architecture (SRA) and the Control Tower defaults converge on an opinionated, flat-ish, function-first tree. Under the organization root you create:

Root  (AWS Organizations management account governs this)
├── Security                 (Control Tower mandatory; Audit + Log Archive live here)
│   ├── [Audit account]      (cross-account security read/respond; delegated admin)
│   └── [Log Archive account](immutable CloudTrail + Config S3, central log bucket)
├── Infrastructure           (shared platform services every workload consumes)
│   ├── [Network account]    (Transit Gateway, central VPCs, Route 53 Resolver, NFW)
│   └── [Shared Services]    (CI/CD, AMI/golden images, central ECR, IPAM, directory)
├── Workloads                (where application teams' accounts live)
│   ├── Prod                 (production accounts, tightest controls)
│   ├── Test                 (pre-prod / staging accounts)
│   └── Dev                  (development / SDLC accounts)
├── Sandbox                  (loose guardrails, detached from the network, hard $ caps)
├── Policy Staging           (an empty OU to safely test new SCPs/controls before rollout)
└── Suspended                (quarantine: cancelled or compromised accounts, deny-all)

Why each top-level branch exists, and the representative guardrails that land there:

OU Purpose Representative controls / SCPs assigned here
Security Centralized security tooling and the immutable audit trail. Control Tower requires this OU and puts the Audit and Log Archive accounts in it. Protect CloudTrail/Config from disablement; deny deletion of the log bucket; deny leaving the org; enforce GuardDuty/Security Hub delegated admin
Infrastructure Shared platform plane the whole org consumes — networking, CI/CD, golden AMIs, central ECR/IPAM/DNS. Tighter change control than workloads. Region deny; deny public S3; restrict who can modify Transit Gateway/RAM shares; require IMDSv2; protect shared route tables
Workloads The home for application accounts, sub-divided by environment (Prod/Test/Dev). The bulk of accounts live here. Baseline app guardrails: deny root usage, require encryption-at-rest, deny unencrypted EBS, region allow-list, mandatory tags
Workloads/Prod Production accounts — the tightest, most stable control set; changes go through change control. Deny *:Delete* on logging/backup; deny RDS/EBS without encryption; deny IAM user creation; stricter region list; deny disabling backup vault lock
Workloads/Test Staging / pre-prod, close to prod but allows some break-glass for validation. Prod controls minus a few break-glass denies; smaller instance-family allow-list for cost
Workloads/Dev Developer SDLC accounts; productive but never internet-trusted with prod data. Deny production data access; budget actions; restrict expensive instance families; deny public RDS
Sandbox Free experimentation disconnected from the corporate network, with hard spend caps and no access to prod data. Network isolation (deny VPC peering/TGW attach to corp), deny on data exfil paths, AWS Budgets actions, region lock
Policy Staging A deliberately near-empty OU used to test a new SCP or Control Tower control on a throwaway account before promoting it to Workloads/Prod. Whatever policy is currently under test — this OU’s contents change constantly by design
Suspended (a.k.a. Quarantine) Quarantine pen for cancelled accounts awaiting closure and for compromised accounts moved here during incident response. A single deny-all SCP (Deny *) with a tiny break-glass allow for forensics; detaches all other inherited permissions instantly

Security OU

This is the only OU AWS Control Tower mandates and pre-populates: when you set up the landing zone it creates the Security OU and provisions the Audit and Log Archive accounts into it. The Audit account is your cross-account security-operations seat — it holds the read/respond roles and is the natural delegated administrator for GuardDuty, Security Hub, Macie, Detective, IAM Access Analyzer, and Inspector, so security tooling runs out of the management account (a hard AWS best practice). The Log Archive account holds the central S3 bucket that receives organization CloudTrail and Config snapshots, ideally with S3 Object Lock (WORM) and a bucket policy that even the org admin cannot weaken without leaving a trace.

The SCPs here are the “never touch the evidence” set: deny cloudtrail:StopLogging, cloudtrail:DeleteTrail, config:DeleteConfigurationRecorder, config:StopConfigurationRecorder, and any s3:DeleteBucket/s3:PutBucketPolicy against the log bucket. You also deny organizations:LeaveOrganization here so a security account can never be detached from governance.

Infrastructure OU

The Infrastructure OU holds the shared platform plane — the things every workload account consumes but should never own. In a hub-and-spoke landing zone the Network account owns the Transit Gateway, the centralized inspection/egress VPC (often with AWS Network Firewall), Route 53 Resolver rules and inbound/outbound endpoints, and the RAM (Resource Access Manager) shares that hand subnets out to workload accounts via VPC sharing. A separate Shared Services account owns the CI/CD tooling, the central Amazon ECR registries, golden AMI/EC2 Image Builder pipelines, VPC IPAM pools, and any AWS Directory Service / AD Connector.

Why split networking out at all? Because the Transit Gateway and central route tables are the component whose misconfiguration takes down every connected account at once — it deserves its own account, its own change control, and its own tighter SCP set (deny modifying TGW route tables outside a pipeline role, deny RAM share deletion, require IMDSv2, region-lock).

Workloads OU and its environment sub-OUs

This is where the actual applications live, and it is the only branch you sub-divide by environment. The recommended pattern is Workloads → {Prod, Test, Dev}, with each application getting separate accounts per environment (more on why under “Environment separation”). Some large orgs add a second axis — e.g. Workloads/Prod/Regulated vs Workloads/Prod/Standard, or Workloads/Prod/SOX — to layer compliance-specific controls (PCI, HIPAA, SOX) on top of the environment baseline. Resist going deeper than that: two meaningful axes (environment × compliance class) is almost always enough, and each extra level is an extra SCP intersection to reason about.

Sandbox OU

The Sandbox OU exists so engineers can experiment without punching holes in production guardrails and without touching the corporate network. The defining characteristic is isolation: SCPs deny creating VPC peering/TGW attachments to corp networks, deny actions that could move prod data in, and AWS Budgets actions apply a hard spend cap that can auto-attach a restrictive policy when a threshold is breached. Sandbox accounts are deliberately cattle, not pets — vended quickly, recycled aggressively, and never trusted with anything that matters.

Policy Staging OU

The Policy Staging OU is the one most teams forget, and it is what separates a mature platform from a fragile one. Because an SCP Deny is absolute and applies instantly to every account in the branch, a bad SCP can lock out an entire production OU in one click. The Policy Staging OU is a near-empty OU containing one or two throwaway accounts that mirror the access patterns of a real workload account. You attach the candidate SCP or new Control Tower control here first, run a smoke test (deploy a representative stack, assume the app role, exercise the deny paths), confirm nothing legitimate breaks, then promote the policy to Workloads/Prod. It turns “we changed an SCP and took down prod” into a non-event.

Suspended OU

The Suspended (or Quarantine) OU is a two-job container. Job one: cancelled accounts awaiting the 90-day closure window sit here, locked down so nobody spins new resources up in them. Job two — the one that earns its keep — is incident response: when an account is compromised, the fastest containment is to move it into Suspended, where a single Deny * SCP (with a narrow break-glass allow for forensics roles) strips every inherited permission instantly without you having to unwind individual IAM changes the attacker may have made. Because OU membership re-evaluates policy immediately, this is the cleanest “big red button” AWS Organizations gives you.

Why not model by org chart, region, or business unit?

Tempting model Why it fails What to do instead
One OU per business unit / team Teams reorg constantly; SCPs don’t map to teams; you get dozens of shallow OUs with near-identical controls Group by shared guardrails (env, compliance); carry ownership in tags + a separate AWS account
One OU per AWS Region Regions are a control concern, not a grouping concern — solve with a region-deny SCP, not topology Use a Region allow-list SCP at the root/Infrastructure level; let accounts be multi-region
Deep nesting to mirror the company hierarchy Five-level limit; every level is another SCP intersection; Control Tower drift gets unreadable Stay flat: two meaningful axes (environment × compliance) is the ceiling for almost everyone
One giant OU for “everything non-prod” Loses the dev-vs-test distinction you need for promotion gates and cost controls Split Dev and Test even if controls are similar today — you will want the seam later

Worked example: how an SCP travels down the tree

The single most important mechanic to internalize is how a permission survives — or doesn’t — as it passes through every OU between the root and an account. Get this right and the whole tree stops feeling like guesswork.

The two-part rule. For any single API action (say s3:DeleteBucket) to be permitted by the SCP layer on an account, both of these must be true at the same time:

  1. Every level from the organization root down to the account contains at least one SCP that allows the action. The allow-lists intersect — a level that fails to allow it removes it, no matter what the levels above said.
  2. No level anywhere on that path denies the action. A single explicit Deny beats every Allow, at any depth.

AWS attaches a default policy called FullAWSAccess ("Effect": "Allow", "Action": "*") to the root, to every OU, and to every account. While that default is in place, rule 1 is satisfied automatically for everything — so in a normal Control Tower organization only your Deny statements do any work. That is the deny-list strategy, and it is what almost everyone runs.

Let’s trace a real branch. Suppose an account orders-prod lives at Root → Workloads → Prod. You attach this SCP at the Workloads OU (it should apply to dev, test, and prod alike):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyRootUserActions",
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "StringLike": { "aws:PrincipalArn": "arn:aws:iam::*:root" }
      }
    },
    {
      "Sid": "RegionLock",
      "Effect": "Deny",
      "NotAction": [
        "iam:*", "organizations:*", "sts:*",
        "route53:*", "cloudfront:*", "support:*", "waf:*", "wafv2:*"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": ["us-east-1", "us-west-2"]
        }
      }
    }
  ]
}

Then, at the narrower Workloads/Prod OU, you attach a second SCP that only production should feel:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "NoIamUsersInProd",
      "Effect": "Deny",
      "Action": ["iam:CreateUser", "iam:CreateAccessKey"],
      "Resource": "*"
    },
    {
      "Sid": "ProtectBackupsAndLogs",
      "Effect": "Deny",
      "Action": [
        "backup:DeleteBackupVault",
        "backup:DeleteRecoveryPoint",
        "cloudtrail:StopLogging",
        "cloudtrail:DeleteTrail",
        "config:StopConfigurationRecorder"
      ],
      "Resource": "*"
    }
  ]
}

Now compute what orders-prod can do. Start from FullAWSAccess (allow everything) inherited at the root. Subtract, in order:

Everything else remains available at the SCP layer. Two things about that word “available” trip people up. First, the SCP is only a ceiling. orders-prod can create an S3 bucket as far as SCPs are concerned, but a principal in the account still needs an IAM policy that actually grants s3:CreateBucket. SCP says “the most you may do”; IAM says “what you may do.” A permission has to clear both gates. Second, the same account sitting one floor up in Workloads/Dev would keep IAM-user creation and backup deletion, because the second SCP is attached only to Prod — the tree position, not the account, decides the ceiling.

The allow-list variant. Some regulated orgs run the opposite strategy: they detach FullAWSAccess from an OU and attach an SCP that explicitly Allows only an approved set of services (Allow ec2:*, s3:*, rds:*, …). This is stricter — the default is now “deny everything not on the list” — but it is far higher-maintenance, because rule 1 now bites: every child OU beneath it must also allow each service, or accounts there silently lose it. A common failure mode is enabling a brand-new AWS service that nobody added to the allow-list, and spending an afternoon confused about why the console throws AccessDenied even for an admin. Reach for allow-lists only where a compliance regime demands a closed service catalog; keep deny-lists everywhere else. Authoring, testing, and version-controlling these policies at scale is its own discipline — see AWS Landing Zone: Guardrails (SCPs & Controls).

Per-account baselines

What a baseline is

A per-account baseline is the set of resources, configuration, and guardrails that every account receives automatically at vend time and continuously thereafter, so that no account is ever “naked.” There are two complementary layers:

  1. The Control Tower baseline applied at enrollment — what AWS sets up the moment the account joins an enrolled OU.
  2. Your custom baseline — the org-specific resources and config you bolt on via Account Factory customizations, blueprints, or a post-provisioning pipeline (the Account Factory for Terraform / AFT pattern, or Customizations for Control Tower / CfCT using CloudFormation StackSets).

What Control Tower bakes in automatically

When an account is provisioned through Account Factory (Control Tower’s account-vending mechanism, surfaced through AWS Service Catalog) and enrolled into a governed OU, it inherits:

Baseline element What it does Mechanism
Org CloudTrail delivery All management events land in the central Log Archive bucket Organization trail managed by Control Tower
AWS Config recorder + delivery Config records resource state and ships snapshots to Log Archive Config recorder + aggregator
IAM Identity Center access The account is reachable via SSO permission sets, not long-lived IAM users IAM Identity Center (formerly AWS SSO)
Mandatory preventive controls Core “you can’t disable the audit plane” SCPs SCPs attached at the OU
Detective controls Config rules flag drift (e.g. public S3, unencrypted volumes) AWS Config managed/custom rules
Baseline IAM roles The Control Tower execution role + audit cross-account roles CloudFormation StackSet (AWSControlTowerExecution)
Default encryption / log config KMS key wiring for the trail/Config where configured Control Tower managed resources

Crucially, you never create these by hand per account — they ride along with enrollment. The architect’s job is to (a) ensure new accounts are always vended into a governed OU, and (b) define the custom layer below.

The custom baseline you add

Beyond what Control Tower gives you, almost every enterprise layers on a standard set. The decision is how to deliver it consistently:

Custom baseline item Why every account needs it Typical delivery
VPC topology (or no VPC) A right-sized VPC sharing the corp CIDR via RAM, or explicitly none for serverless-only accounts AFT/CfCT StackSet; delete the default VPC in every Region
GuardDuty / Security Hub / Inspector / Macie enrollment Threat detection and posture must be on from minute zero Org-level auto-enable via delegated admin (Audit account)
EBS encryption-by-default + KMS No unencrypted volume should ever exist ec2:EnableEbsEncryptionByDefault set per Region via StackSet
S3 Block Public Access (account-level) Closes the most common data-leak vector at the account boundary Account-level BPA set on baseline
IMDSv2 enforcement Mitigates SSRF credential theft Declarative Policy / SCP + launch templates
Default IAM roles & break-glass A read-only auditor role, a break-glass admin role, the CI/CD deploy role Identity Center permission sets + StackSet roles
Budgets + cost anomaly detection Every account must have a spend ceiling and anomaly alerts AWS Budgets + Cost Anomaly Detection via StackSet
Centralized logging wiring VPC Flow Logs, CloudWatch → central, DNS query logging StackSet to the central log destination
Tagging at birth Environment, CostCenter, Owner, DataClassification enforced by Tag Policy Account Factory parameters + Tag Policy

Account Factory, AFT, and Customizations for Control Tower

There are three account-vending postures, and choosing among them is a real architectural decision:

Approach What it is When to choose it
Account Factory (console / Service Catalog) Click-ops or Service Catalog product to vend a single account with basic parameters Small orgs; <~30 accounts; low change rate
Account Factory for Terraform (AFT) A Terraform pipeline (GitOps) that vends accounts, runs global and account-specific customizations, and re-applies on every account Terraform shops; you want account requests as pull requests and drift-corrected baselines
Customizations for Control Tower (CfCT) A CloudFormation/StackSet pipeline triggered by lifecycle events to apply CFN templates to OUs/accounts CloudFormation shops; you want to layer StackSets onto Control Tower’s lifecycle

The non-negotiable principle across all three: the baseline is code, applied idempotently, and re-converged. Drift is detected (Config + Control Tower drift detection) and corrected (re-run AFT / re-deploy the StackSet), so an account that someone hand-edits snaps back to baseline. A baseline that exists only as a runbook is not a baseline.

Account-vending request as an artifact

The concrete artifact of this discipline is the account request itself — in AFT this is a small Terraform module instance (or in CfCT, an entry in a manifest) capturing: account name and email (a unique, deliverable address, usually a +-suffixed alias on a shared mailbox), the target OU, the owner/cost-center/environment tags, the SSO access (which permission sets, which groups), and any account-specific customization toggles (e.g. “this account gets a shared VPC subnet,” “this is a serverless-only account, no VPC”). That request is reviewed via pull request, which gives you an auditable history of why every account exists.

Worked example: what happens the second an account is vended

A baseline is easiest to understand as a timeline. Follow one account, orders-prod, from a pull request to a governed, furnished account:

  1. A request as code. An engineer opens a PR adding one small module instance to the account-request repo (below). It names the account, its email, its target OU, its tags, and any per-account overlay. Review of that PR is the auditable answer to “why does this account exist?”
  2. Merge triggers the pipeline. With Account Factory for Terraform (AFT), the merge kicks a pipeline that calls Account Factory (a Service Catalog product Control Tower owns). Organizations creates the account and moves it straight into Workloads/Prod — it is never parked in the root.
  3. Control Tower enrollment fires. The moment the account lands in a governed OU, the Control Tower baseline attaches: the org CloudTrail begins delivering to Log Archive, an AWS Config recorder starts and ships to the aggregator, IAM Identity Center is wired so access is via permission sets (not IAM users), the mandatory preventive SCPs apply, and the AWSControlTowerExecution role is created for future StackSet runs.
  4. Global customizations run. AFT then applies the layer every account gets: delete the default VPC in every Region, turn on EBS encryption-by-default, set account-level S3 Block Public Access, enforce IMDSv2, stamp an AWS Budgets ceiling with Cost Anomaly Detection, and auto-enable GuardDuty/Security Hub/Inspector/Macie through the Audit account as delegated admin.
  5. Account-specific customizations run. Finally the overlay named in the request applies — here, a shared-VPC subnet handed over by RAM; for a serverless-only account it might be no VPC at all.
  6. Drift is watched and re-converged. Config and Control Tower drift detection watch the account forever after. If someone hand-edits it, re-running AFT (or re-deploying the StackSet) snaps it back. A baseline that lives only in a runbook is not a baseline — it must be code that re-applies.

The request itself is deliberately small and declarative. This is the real shape of an AFT account-request module instance (identifiers are placeholders):

module "orders_prod" {
  source = "./modules/aft-account-request"

  control_tower_parameters = {
    AccountEmail              = "aws+orders-prod@example.com"   # unique, deliverable
    AccountName               = "orders-prod"
    ManagedOrganizationalUnit = "Workloads/Prod"                # the target OU path
    SSOUserEmail              = "cloud-platform@example.com"
    SSOUserFirstName          = "Orders"
    SSOUserLastName           = "Prod"
  }

  account_tags = {
    Environment        = "prod"
    Owner              = "team-orders"
    CostCenter         = "CC-4471"
    DataClassification = "confidential"
  }

  change_management_parameters = {
    change_requested_by = "jane.doe"
    change_reason       = "Vend prod account for Orders service (JIRA PLAT-1287)"
  }

  account_customizations_name = "shared-vpc-workload"   # which per-account overlay to apply
}

Notice there is no click-ops here and nowhere to fat-finger the OU: the ManagedOrganizationalUnit is text in a reviewed file. AFT’s mechanics — the request, global, and account customization repos and how they re-converge — are covered end to end in Account Factory for Terraform (AFT).

Naming and tagging conventions that keep the tree searchable

At three accounts you can hold everything in your head; at three hundred you cannot, and the only thing that saves you is a boring, mechanical convention decided before the first account is vended. Retro-fitting names and tags across a live org is miserable, so treat this as part of the OU design, not an afterthought.

Convention Pattern Example Why it matters
Account name <workload>-<env> orders-prod Human-scannable in the Organizations console and in billing
Account email aws+<workload>-<env>@<domain> aws+orders-prod@example.com Must be globally unique and deliverable; the +-alias routes to one shared, monitored mailbox
OU path Workloads/<Env>[/<ComplianceClass>] Workloads/Prod/PCI The path is the guardrail set the account inherits
Mandatory tags Environment, Owner, CostCenter, DataClassification prod / team-orders / CC-4471 / confidential Cost allocation, automation targeting, and the OU-vs-tag drift check

Two rules make tags trustworthy rather than decorative. First, enforce the keys with a Tag Policy so a missing or misspelled key is caught, and activate them as cost allocation tags in the billing console or they never reach Cost Explorer. Second, the Environment tag and the OU placement must always agree — an account tagged prod sitting in Workloads/Dev is a governance bug, and a periodic Config rule (or an AFT post-check) that flags “Environment tag ≠ OU” catches exactly that drift before it hurts.

Environment separation

Why separate accounts per environment, not VPCs or tags

The single strongest isolation boundary AWS gives you is the account. Service quotas, IAM trust, billing, and — critically — blast radius all stop at the account line in a way they never do at a VPC or tag boundary. The principle the AWS multi-account strategy hammers is: isolate production workloads in their own accounts, and give each application its own set of accounts per environment. So a single app “Orders” gets orders-dev, orders-test, and orders-prod as three separate accounts, sitting in Workloads/Dev, Workloads/Test, and Workloads/Prod respectively.

Isolation boundary Strength What still leaks across it
Tag (one account, env=prod/dev) Weakest IAM blast radius, service quotas, a fat-fingered * policy, a runaway bill
VPC (one account, multiple VPCs) Weak–medium IAM and the account-wide control plane; quota exhaustion; the root user
Account per environment Strongest Almost nothing — by design; cross-account is explicit and auditable

Separate accounts also make the promotion story clean: a CI/CD pipeline in Shared Services assumes a deploy role into orders-dev, then orders-test, then orders-prod, with a manual approval gate before prod. Each environment can carry a different SCP set purely by living in a different OU — dev can be allowed to spin up cheap experiments that prod denies, and prod can deny the IAM-user creation and backup-vault-tampering that dev tolerates.

How environment separation maps onto the OU tree

Environment is expressed twice, on purpose, and both must agree:

  1. Structurally, via OU placement: the account sits in Workloads/Prod vs /Test vs /Dev, which controls which SCPs and Control Tower controls it inherits.
  2. Descriptively, via a mandatory Environment tag enforced by a Tag Policy, which controls cost allocation, automation behavior, and queryability.

The two must never disagree — an account tagged prod sitting in the Dev OU is a governance bug. A periodic Config rule (or an AFT post-check) that flags “Environment tag != OU” catches exactly this drift.

The promotion and connectivity model

Environment separation is not just about blocking — it is about controlled flow:

The account-per-X decision: team, environment, or application

“Should this get its own account?” is the question you will answer hundreds of times, so it helps to have a rule instead of a mood. There are three axes people split on — team, environment, and application — and they are not equal. Environment is non-negotiable; application is common; team is usually a tag, not a boundary; Region is never a primary split.

Axis Give it its own account when… Keep it in a shared account when…
Environment (dev/test/prod) Always. Production is always its own account, in Workloads/Prod. This is the one split you never skip. Never — sharing prod with non-prod defeats the entire model.
Application / service It has a distinct compliance scope, materially different quota needs, or its own on-call and release cadence. Several microservices form one bounded context, owned by one team, deployed on one lifecycle — group them.
Team The team owns an independent budget and blast radius and ships on its own schedule. It is a small internal tool with no production data — carry the team in an Owner tag, not a new OU.
Region Never as a primary split. Regions are a control (a Region-lock SCP), not a grouping. Always — let one account be multi-Region and constrain Regions with policy.

The arithmetic falls out of the first two rows. A company with A independent applications, each needing dev/test/prod, lands near A × 3 workload accounts, plus the foundational handful (management, Audit, Log Archive, Network, Shared Services). Nine applications is roughly 9 × 3 = 27… but real orgs share some non-prod, split some prod by compliance, and add sandboxes, so the true number is a range you manage, not a target you hit. The discipline is not “few accounts” or “many accounts” — it is that every account’s existence is justified by a reviewed request, and its position in the tree is chosen by which guardrails it should inherit.

Going deeper

Why the account is the real boundary (quotas, billing, blast radius)

Everyone repeats “the account is the strongest isolation boundary,” but it is worth knowing precisely what stops at that line, because it is what makes the multi-account model non-negotiable rather than aesthetic:

The VPC and the tag simply do not offer any of these. A tag is a label an admin can ignore; a VPC still shares the account’s IAM, quotas, root user, and bill. That is the whole argument for accounts-per-environment in one paragraph.

The management account is special — keep it empty

The management (payer) account is not “account number one that happens to be in charge.” It is structurally different, and treating it like a normal account is one of the most dangerous mistakes in the whole design:

That last word is the mechanism. Delegated administration lets a security service be administered from another account while the management account stays clean. GuardDuty, Security Hub, Macie, Detective, IAM Access Analyzer, Inspector, the Config aggregator, Firewall Manager, and even CloudFormation StackSets all support a delegated administrator — and the canonical target is the Audit account in the Security OU. Running these from the management account is a documented anti-pattern: it widens the blast radius of your single most sensitive account for no benefit.

How your tree maps onto Control Tower’s default OUs

A common source of confusion: the elegant SRA-aligned tree above is mostly something you build, not something Control Tower hands you. When you set up the Control Tower landing zone, it creates only a small, opinionated starting set; you add and register the rest.

OU in the recommended tree Who creates it Notes
Security Control Tower, mandatory Created at setup; Audit + Log Archive accounts are provisioned into it. You cannot skip it.
Sandbox Control Tower, by default Created as a default second OU (historically called “Custom”); you may rename or repurpose it.
Infrastructure You Create it, then register it with Control Tower so its accounts are governed.
Workloads (+ Prod/Test/Dev) You The environment sub-OUs are yours to design and register.
Policy Staging You Deliberately near-empty; often not fully enrolled so you can test controls against it.
Suspended You Register it, then attach the deny-all SCP.

The verbs that matter are register (tell Control Tower to govern an existing OU) and enroll (bring an existing account under governance). New accounts vended through Account Factory are enrolled automatically; older accounts you created before Control Tower must be enrolled explicitly. The management account, note, is never enrolled — it governs, it is not governed.

SCP evaluation, precisely (and what SCPs never touch)

Beyond the deny-vs-allow-list model, a few sharp edges separate people who think an SCP is applied from people who know it is:

Hard limits you design around

The tree has physical limits, and a good design stays comfortably inside them rather than discovering them in an outage:

Operating the tree at scale — moves, closures, and drift

Three operational realities finish the picture. Moving an account between OUs re-evaluates its entire inherited policy set instantly — this is not a slow migration, it is an atomic change, and it is the engine behind both promotion (nothing moves at promotion — you deploy into the standing prod account) and the quarantine pattern (move a compromised account into Suspended and its Deny * strips every permission in seconds). Closure follows the 90-day rule above and should route through the Suspended OU so nobody spins up new resources in an account on its way out. And drift is the quiet killer: an account someone hand-edited slowly diverges from its baseline until it is the one account that behaves differently in an incident. The whole point of delivering baselines as code (AFT/CfCT StackSets) plus Config and Control Tower drift detection is that drift is detected and re-converged, not discovered at 3 a.m.

Real-world enterprise scenario

Context. Meridian Cargo, a fictional mid-size logistics SaaS, runs a fleet-tracking and freight-booking platform. They have 9 product teams, roughly 60 microservices, a hard PCI-DSS obligation (they store cardholder data for freight payments), and a board mandate to “move to a governed multi-account model in one quarter.” They start from a sprawling two-account mess (one “everything-dev,” one “everything-prod”) with shared IAM, no central logging, and a $90k/month bill nobody can attribute.

The decisions, per sub-component.

OU structure. Their platform team stands up AWS Control Tower, accepting the mandated Security OU (Audit + Log Archive). They then create Infrastructure (Network + Shared Services accounts), Workloads with Prod / Test / Dev children, a Sandbox OU, a Policy Staging OU, and a Suspended OU. Because PCI is in scope, they split Workloads/Prod into Prod/PCI and Prod/Standard — a second axis — so the PCI accounts inherit an extra conformance pack and a tighter region allow-list (us-east-1, us-west-2 only) on top of the prod baseline. Final tree: 7 top-level OUs, one two-level split under Prod — never deeper. They explicitly reject the team-per-OU model their first draft had (it produced 9 near-identical OUs); ownership moves to a mandatory Owner tag plus the account itself.

Per-account baselines. They adopt Account Factory for Terraform (AFT) because they are already a Terraform shop. Every account request becomes a pull request. The global customization layer (applied to all accounts) deletes the default VPC in every Region, sets EBS encryption-by-default, turns on account-level S3 Block Public Access, enforces IMDSv2 via a Declarative Policy, wires VPC Flow Logs + CloudTrail to the central Log Archive bucket, auto-enables GuardDuty, Security Hub, Inspector, and Macie through the Audit account as delegated admin, and stamps an AWS Budgets ceiling with Cost Anomaly Detection. The account-specific layer attaches a shared-VPC subnet (via RAM) for accounts that need one, or no VPC at all for the 12 serverless-only accounts. PCI accounts additionally receive an AWS Config conformance pack mapped to the PCI-DSS controls and a backup plan with AWS Backup Vault Lock enabled.

Environment separation. Each of the 9 product teams gets three accounts per product (dev/test/prod), so “freight-booking” becomes freight-dev, freight-test, freight-prod. They end up with ~60 workload accounts plus the 4 foundational ones (management, audit, log-archive, network) and shared-services — about 66 accounts total. Dev attaches to a non-prod TGW route domain; prod to a prod-only domain; the two cannot route to each other. A single AWS CodePipeline in Shared Services promotes builds dev → test → prod, assuming an environment-specific deploy role at each stage, with a manual approval and a change ticket required before the prod stage. A Config rule flags any account whose Environment tag disagrees with its OU.

The artifacts they produce. An OU design diagram and decision record; an AFT repository with the global + per-account customization modules; ~66 account-request Terraform module instances under version control; the SCP set per OU (with the candidate-SCP workflow routed through Policy Staging); the PCI conformance pack and Tag Policy; and an Identity Center permission-set matrix mapping AD groups → permission sets → OUs.

The measurable outcome. Within the quarter: 100% of accounts enrolled in a governed OU with the baseline applied automatically; central CloudTrail/Config with zero accounts able to disable the audit plane (verified by attempting it from Policy Staging); PCI scope reduced to the 9 Prod/PCI accounts only, shrinking the audit surface; cost now attributable to a team and environment for every dollar (cost-allocation tags enforced by Tag Policy); and a live drill where a deliberately “compromised” test account was contained in under two minutes by moving it to the Suspended OU — the Deny * SCP stripped every permission instantly.

Deliverables & checklist

By the end of this phase you should have produced:

Common pitfalls

Practice challenges

Work these in order — they escalate from “place the accounts” to “design and contain.” Try each before opening the solution.

1. Place the accounts (beginner). You have five accounts: log-archive, network-hub, payments-prod, payments-dev, and a throwaway scp-test-01. Put each in the right OU of the recommended tree.

<details> <summary>Solution</summary>

log-archiveSecurity; network-hubInfrastructure; payments-prodWorkloads/Prod; payments-devWorkloads/Dev; scp-test-01Policy Staging.

Why: each account is placed by the guardrail set it should inherit — audit-plane protection, shared-platform change control, tight prod controls, looser dev controls, and a mirror account for testing policies before they touch prod. </details>

2. Pick the delegated admin (beginner). Your security team wants GuardDuty, Security Hub, and Macie run centrally. Which account should be the delegated administrator, and which account must not run them?

<details> <summary>Solution</summary>

Delegate administration to the Audit account (in the Security OU). The management (payer) account must not run them — centralizing security tooling there is an AWS anti-pattern that widens the blast radius of your most sensitive account and is exempt from SCPs anyway.

Why: delegated administration is exactly the mechanism that keeps the management account empty while still giving you org-wide, single-pane security. </details>

3. Write the “protect the audit plane” SCP (intermediate). Write an SCP, suitable for the Security OU, that prevents anyone from stopping CloudTrail, deleting a trail, or detaching an account from the organization.

<details> <summary>Solution</summary>

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ProtectAuditPlane",
      "Effect": "Deny",
      "Action": [
        "cloudtrail:StopLogging",
        "cloudtrail:DeleteTrail",
        "config:StopConfigurationRecorder",
        "config:DeleteConfigurationRecorder",
        "organizations:LeaveOrganization"
      ],
      "Resource": "*"
    }
  ]
}

Why: an explicit Deny beats every Allow, so even a full administrator in the account cannot disable the evidence trail or escape governance — the guarantee you want on the Security OU. </details>

4. Compute effective permissions (intermediate). FullAWSAccess is attached everywhere. The Workloads OU denies all actions outside us-east-1/us-west-2 (with global services excepted). Workloads/Prod additionally denies iam:CreateUser. An admin role in payments-prod calls iam:CreateUser and, separately, ec2:RunInstances in eu-west-1. What happens to each call, at the SCP layer?

<details> <summary>Solution</summary>

iam:CreateUserdenied by the Prod-level SCP. ec2:RunInstances in eu-west-1denied by the Workloads-level Region lock (EC2 is regional and not in the global NotAction exception).

Why: a permission must clear every level’s ceiling; a Deny at any depth on the path removes it regardless of the admin’s IAM policy — and being an administrator does not lift an SCP. </details>

5. Design a tree with a compliance axis (advanced). A SaaS company has 12 applications (each needs dev/test/prod), a PCI-DSS obligation for 3 of them, and 8 serverless-only accounts that need no VPC. Sketch the OU tree and say roughly how many accounts result — without exceeding two meaningful axes.

<details> <summary>Solution</summary>

Top-level: Security, Infrastructure, Workloads, Sandbox, Policy Staging, Suspended. Under Workloads: Dev, Test, Prod, and split Prod into Prod/PCI and Prod/Standard (the second axis — environment × compliance — and no deeper). Roughly 12 × 3 = 36 workload accounts plus ~5 foundational (management, Audit, Log Archive, Network, Shared Services) ≈ ~41. The serverless-only accounts get the no-VPC account customization; they don’t need their own OU — the VPC decision is a baseline overlay, not a tree branch.

Why: compliance earns a sub-OU because PCI accounts inherit an extra conformance pack and tighter Region lock; “serverless” does not, because it is a per-account baseline choice, not a different guardrail set. </details>

6. Contain a compromised account (advanced). An attacker has valid credentials in orders-dev and is creating IAM users and exfiltrating from S3. Give the fastest containment that does not require you to unwind every IAM change by hand, and name one thing it will not stop.

<details> <summary>Solution</summary>

Move the account into the Suspended OU, which carries a Deny * SCP (with a narrow break-glass allow for a forensics role). OU membership re-evaluates policy instantly, so every inherited permission for your principals is stripped in seconds — no per-IAM-change cleanup needed. What it will not stop: service-linked roles are exempt from SCPs, so AWS-managed automation the attacker may have leaned on isn’t necessarily frozen, and any resource-based grant to an external principal still needs separate revocation.

Why: the quarantine OU is AWS Organizations’ “big red button” precisely because moving an account is an atomic policy re-evaluation, not a slow migration. </details>

Common beginner mistakes

These are conceptual traps — wrong mental models a newcomer forms before the mechanics click. (They are distinct from the architecture-level pitfalls listed earlier; those are things experienced teams still get wrong at scale.)

Glossary

What’s next

Part 4 of AWS Landing Zone & Control Tower moves from the account/OU skeleton to the policy plane itself — authoring and operating Service Control Policies, Resource Control Policies, and Control Tower controls as code, including the proactive/preventive/detective control mix and how to manage them at scale.

AWSLanding ZoneOU Structure & Account BaselinesEnterprise
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments