There is one hard isolation boundary in AWS, and it is not the VPC, the tag, or the IAM policy. It is the account. Service quotas are per account. Billing rolls up per account. A resource name collision, a runaway script, a compromised credential — all of them stop at the account edge unless something explicitly bridges it.
Which makes “how many accounts, and which workload goes where” one of the highest-leverage decisions in AWS, and one that is painful to change later.
Topic 1: The Organization
An organization has one management account (formerly “payer”) and any number of member accounts, arranged in organizational units. Three capabilities matter operationally:
- Consolidated billing. One bill, and — importantly — usage aggregates for volume discounts and for Reserved Instance / Savings Plan sharing across the whole organization. Two accounts each running half a workload get the same tiering as one account running all of it.
- Service control policies. Guardrails that cap what any principal in an account may do.
- Trusted access and delegated administration. CloudTrail organization trails, Config aggregators, Security Hub, GuardDuty and IAM Access Analyzer can each be run from one delegated account across every member.
Two rules about the management account that people learn the hard way:
- Do not run workloads in it. SCPs do not apply to the management account. Anything you build there sits outside every guardrail you wrote.
- Lock it down hard — MFA on root, no access keys on root, alarm on any console login. It can create accounts, remove SCPs and read every bill; there is no more privileged position in your estate.
Topic 2: What an SCP Actually Does
An SCP grants nothing. It is a filter on what IAM in that account is permitted to grant. Effective permission = SCP ∩ identity policy (∩ boundary ∩ resource policy, as covered in the evaluation order).
That single sentence resolves nearly every SCP misunderstanding:
- An SCP that “allows S3” does not give anyone S3 access. It merely does not block it.
- Removing an action from an SCP
Allowlist denies it, even to an account administrator. - An SCP cannot grant a permission the account’s IAM does not grant, and cannot make root do something.
FullAWSAccess is attached by default at every level and allows everything, which is what makes the deny-list style work. Two styles:
// DENY LIST (recommended): keep FullAWSAccess, add targeted Denies
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "DenyRegionsOutsideEurope",
"Effect": "Deny",
"NotAction": [
"iam:*", "sts:*", "organizations:*", "route53:*",
"cloudfront:*", "waf:*", "support:*", "budgets:*", "ce:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:RequestedRegion": ["eu-west-1", "eu-central-1"] }
}
}]
}
The NotAction list is not optional decoration: global services are reached through a regional endpoint (usually us-east-1), so a naive region deny breaks IAM, Route 53 and CloudFront across the whole account. Missing that exemption is the single most common way a region-restriction SCP causes an outage on the day it is attached.
An allow-list style SCP (replace FullAWSAccess with an explicit Allow of only permitted services) is stricter and much higher maintenance — every new service adoption becomes an SCP change. Use it for sandbox and dead-end accounts, not for the accounts where your engineers work.
The five guardrails worth having on day one:
1. Deny leaving the organization organizations:LeaveOrganization
2. Deny disabling security telemetry cloudtrail:StopLogging, guardduty:Delete*,
config:DeleteConfigurationRecorder
3. Deny deleting or altering the audit s3:DeleteBucket / PutBucketPolicy on the
log bucket log archive bucket
4. Deny regions you do not use aws:RequestedRegion (with the exemptions above)
5. Deny root user actions in member aws:PrincipalArn StringLike ":root"
accounts
Each is a Deny, so none of them can be argued around by a local administrator — which is the entire point.
Two limits that shape design: 5 SCPs per OU or account, and 5,120 characters per policy. Deep OU trees with a policy at every level run out fast. Plan for a shallow tree and consolidated policies.
Topic 3: Account Layout
The layout that has become standard, and the reasoning:
Root
├── Security OU
│ ├── log-archive ← CloudTrail + Config, write-only from others
│ └── security-tooling ← GuardDuty, Security Hub, delegated admin
├── Infrastructure OU
│ ├── network ← Transit Gateway, DX, shared VPCs (RAM)
│ └── shared-services ← CI runners, container registries, AMI pipeline
├── Workloads OU
│ ├── Prod OU ← one account per workload, or per team
│ └── NonProd OU ← same shape, weaker quotas, cheaper guardrails
├── Sandbox OU ← time-boxed, budget-capped, aggressive SCPs
└── Suspended OU ← SCP denying everything; where accounts go to be closed
The rule for splitting accounts: split on blast radius and ownership, not on org chart aesthetics. Production and non-production always split — sharing an account means a load test can exhaust a quota that production needs. Anything with distinct compliance scope splits, because auditing a subset of an account is far more expensive than auditing a whole one.
What you pay for more accounts: more places to configure, more roles to wire, more base infrastructure. Which is precisely why the account baseline should be code from the start — Control Tower, Account Factory for Terraform, or your own module. The first three accounts are easy by hand and the tenth is not.
Topic 4: Sharing Across Accounts Without Undoing the Boundary
You will need to share things. Do it with the mechanisms designed for it:
| Need | Mechanism | Note |
|---|---|---|
| One VPC used by several accounts | Resource Access Manager shares subnets | The owner keeps the VPC; participants launch into it |
| Central network hub | Transit Gateway shared via RAM | Route tables per environment keep dev out of prod |
| A private service consumed by others | PrivateLink / VPC endpoint service | No route table changes, no CIDR coordination |
| Read a bucket from another account | Bucket policy + identity policy | Both sides required; consider s3:ResourceAccount conditions |
| Assume a role from CI | Cross-account role + sts:ExternalId | Session name in CloudTrail identifies the caller |
| Encrypt shared data | Customer-managed KMS key with a key policy | AWS-managed keys cannot be shared cross-account |
The condition key that closes the widest hole: aws:PrincipalOrgID. It lets you write a resource policy that permits your entire organization without listing account IDs — and, more importantly, denies everyone outside it even if an ARN leaks.
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::internal-artifacts", "arn:aws:s3:::internal-artifacts/*"],
"Condition": { "StringNotEquals": { "aws:PrincipalOrgID": "o-abc123example" } }
}
Topic 5: Billing, Budgets and the Blast Radius of Cost
Cost is an availability concern: a runaway spend gets an account frozen, and a frozen account is an outage.
- Cost allocation tags must be activated in the management account before they appear in reports. Tags applied before activation are not retroactive, which is why tagging discipline pays interest.
- AWS Budgets with actions can do more than email — apply a deny SCP or stop instances at a threshold. In a sandbox OU that is the difference between a lesson and an invoice.
- Cost Anomaly Detection catches the shape of the problem you did not predict, which is most of them.
- Set a budget on every account at creation time, even a generous one. An account with no budget has no smoke detector.
This module deliberately stops there: the full treatment — the layer model, tagging strategy, right-sizing, commitments and Karpenter economics — is the Cloud Cost Optimization path, and duplicating it here would let the two drift apart.
Topic 6: Verifying Your Guardrails
A guardrail nobody tested is a comment.
# What policies actually apply to this account, including inherited ones?
aws organizations list-policies-for-target \
--target-id 111122223333 --filter SERVICE_CONTROL_POLICY
# Walk the tree upward — inheritance is where surprises live
aws organizations list-parents --child-id 111122223333
# Does this specific call survive the SCPs? Test from inside the account.
aws ec2 describe-instances --region ap-southeast-2
# expect: AccessDenied ... with an explicit deny in a service control policy
That last error string is the one you want to see. When an SCP denial happens, the message says service control policy explicitly — which is why the reason clause in an AccessDenied is worth reading rather than skipping.
Try it yourself: attach the deny-regions SCP to a sandbox account, then try to create a resource in a blocked region. Then check that IAM, Route 53 and Support still work. If they do not, your NotAction list is incomplete — and you have just found the bug in a controlled way instead of during a production change window.
Common mistake: attaching a region-deny SCP to an OU containing accounts that already have resources in those regions. The SCP does not delete anything; it makes those resources unmanageable — you cannot describe them, modify them, or delete them, and they keep billing. Inventory first, migrate or delete, then attach. In that order, always.