Account Strategy
AWS Intermediate/Advanced
Chapter 1 · Multi-Account Foundations
AWS Fundamentals taught you to build in one account. That is the right way to learn, and the wrong way to run anything that matters. The first decision a team makes, before any server or database, is how many AWS accounts it will have and who may do what in each. This chapter covers the tools for that decision: AWS Organizations (the structure), service control policies (the guardrails), IAM Identity Center (how people sign in), Control Tower (a ready-made version of all of it), and tags (how you find out who is spending what). It ends with an account layout for a small team that you can check with a script.
- Run here (level 1): Python 3.14.0 and cfn-lint 1.57.2 on Windows 11, in a scratch folder outside this site. Output is pasted from the real run.
- Needs an AWS account (level 2): none in this chapter. Nothing here creates, changes or costs anything.
- From the documentation, not run (level 3): everything about how AWS itself behaves. I read the AWS Organizations, IAM Identity Center, Control Tower and Billing pages on 2026-10-12 through a page-fetch tool that summarises pages, so quotes below are as that tool returned them. AWS changes limits and defaults often; check the current page before you rely on a number.
Why One Account Is Not Enough
An AWS account is a boundary. Everything inside it shares one bill, one set of service quotas, one root user and one blast radius: a mistake or a stolen key anywhere in the account can reach everything else in it. Putting production, testing, experiments and your audit logs in one account means a developer experimenting with a test database sits one wrong click from your customers' data, and the bill is one number nobody can split by team. Separate accounts are the strongest dividing line AWS gives you. The reasons teams give, which are reasoning rather than measurements:
| Concern | One account | Several accounts |
|---|---|---|
| Blast radius | A bad deploy, a leaked key or a deleted stack can touch every environment | Damage stops at the account boundary unless you built a path across it |
| Access | Policies must keep prod and dev apart inside one set of users and roles | Different people hold different access per account; production can be very few people |
| Billing | One bill; splitting it relies on every resource being tagged | Each account is a line on the bill without any tagging at all |
| Limits | Service quotas are shared, so a load test can use up production's headroom | Quotas are generally per account, so environments do not compete |
| Audit and compliance | Logs live next to the things being logged, and can be deleted by the same admins | Logs go to a separate account that workload admins cannot reach |
The price is administration: ten accounts means ten places to set up the same baseline. That is exactly what the rest of this chapter removes.
AWS Organizations
AWS Organizations lets you create and group accounts and apply policy to the groups. The vocabulary:
| Term | Meaning |
|---|---|
| Organization | The whole structure, with exactly one root at the top |
| Management account | The account that created the organization. It pays the bills and manages the structure |
| Member account | Every other account in the organization |
| Organizational unit (OU) | A folder of accounts and other OUs. Policies attached to an OU apply to everything beneath it |
| Policy | A document attached to the root, an OU or an account. Service control policies are the type this chapter covers |
All features, not just billing. An organization can be set up for consolidated billing only, or with all features. The documentation is explicit that SCPs "are available only in an organization that has all features enabled" and "aren't available if your organization has enabled only the consolidated billing features". Use all features.
Limits to design around
These are the default quotas from the Organizations quotas page (read 2026-10-12). Several are adjustable through Service Quotas; the hard ones are marked.
| Limit | Value on 2026-10-12 | Adjustable? |
|---|---|---|
| Accounts in an organization | 10 by default (newly created organizations may start lower) | Yes, on request from the management account, up to 50,000 depending on qualification |
| Roots | 1 | No |
| OUs in an organization | 2,000 | Not stated |
| OU nesting | Five levels deep under the root | Not stated |
| SCP size | 10,240 characters | Not stated |
| SCPs attached directly to one root, OU or account | 10, "all policy limits are hard limits"; every entity must keep at least one | No |
| Tags on a root, OU or account | 50 | Not stated |
| Member accounts created at once | 5 | Not stated |
The 10-account default matters more than it looks: a closed account keeps counting until it is permanently closed, and an invitation you send counts too, according to the same page. A layout with eleven accounts needs a quota increase requested before you create the eleventh. The layout checker in Exercise 3 warns about this.
Keep the management account empty
The Organizations best-practice page says to use the management account "only for tasks that require the management account" and to "store all of your AWS resources in other AWS accounts". One reason it gives is the next section's subject: SCPs "do not work to restrict any users or roles in the management account", so anything you run there is outside your guardrails. The same page recommends limiting who can reach it, reviewing that list quarterly or monthly, and delegating services such as CloudFormation StackSets to a member account where the service supports it.
Service Control Policies
A service control policy (SCP) sets the maximum permissions for the users and roles in the accounts it covers. The documentation is clear about the part people get wrong: "SCPs do not grant permissions". An SCP is a ceiling. Nobody gets anything from an SCP; they get it from an identity-based or resource-based policy, and only if the SCP ceiling also allows it.
| SCPs do | SCPs do not |
|---|---|
| Restrict IAM users and roles in member accounts, including each member account's root user | Affect anything in the management account |
| Apply to member accounts that are delegated administrators | Affect service-linked roles |
| Combine with permission boundaries: the boundary, the SCP and the identity policy must all allow the action | Directly affect resource-based policies, or users and roles from accounts outside the organization |
| Apply to everything beneath the OU or root they are attached to | Restrict a short list of tasks, such as registering for the Enterprise support plan as the root user |
Allow lists and deny lists
When SCPs are enabled, AWS attaches an AWS-managed policy called FullAWSAccess to every root, OU and account. It allows everything, so by default nothing changes. You then choose a strategy:
- Deny list. Keep FullAWSAccess and attach SCPs that
Denyspecific things (leaving the organization, turning off logging, unapproved regions). Everything not denied stays allowed. This is the easier one to run. - Allow list. Detach FullAWSAccess and attach SCPs that
Allowonly the services you approve. Everything else is denied, including new AWS services until you add them. This is tighter and more work. The documentation warns: "You should not remove the FullAWSAccess policy unless you modify or replace it with a separate policy with allowed actions, otherwise all AWS actions from member accounts will fail".
Two examples straight from the documentation's own pages (the first is the one AWS now attaches by default to organizations created in the console, see below):
The second is a deny list in a clever disguise: Deny with NotAction and StringNotEquals reads as "deny everything except these global services, when the region is not one of these two".
The excepted services (iam, cloudfront, route53, support) are the ones the documentation's example leaves out of the region lock. Reading double negatives
slowly is a core SCP skill.
The syntax rules, and a checker for them
SCP syntax is IAM policy syntax with restrictions. These rules are stated on the SCP syntax page; the checker below applies them (my implementation of the documentation, not AWS's validator):
| Rule | What the docs say |
|---|---|
| Version | Must be "2012-10-17" |
| Effect | Allow or Deny |
| Action / NotAction | service:Action strings; * and ? wildcards |
| Resource | Required (or NotResource). In an Allow statement only "*" is accepted; a Deny may name ARNs |
| Condition | Allowed in both Allow and Deny |
| Principal, NotPrincipal | Not supported |
| Size | Every character counts against 10,240; the console strips extra whitespace, the CLI and SDKs save the text exactly as given |
Run here (level 1). I wrote 17 SCP files: five taken from or modelled on the documentation, nine with one deliberate mistake each, and three for the size and duplicate-key edge cases. The checker's verdicts:
Two lines are worth reading closely. size_fits_only_compact.json is 18,932 characters as written (indented) but 10,222 without whitespace: it fits the 10,240 limit only if the whitespace is stripped, which the
console does and the CLI does not. That is a practical trap: a policy that saves fine from the console can be refused when the same file is sent with aws organizations create-policy. And the duplicate
Effect key passes with a warning, because the documentation does not say what AWS does with it (Node, Python and jq all keep the last, see Working with JSON 11). I did not guess.
Linting the template that deploys an SCP
You will usually deploy SCPs with infrastructure as code (AWS Intermediate/Advanced 4 covers CloudFormation). AWS::Organizations::Policy carries the SCP in a Content property. This template is the default-controls
policy, and cfn-lint passes it:
Run here (level 1). I linted that template, then six broken copies. cfn-lint 1.57.2 said:
cfn-lint caught the misspelt policy type and the missing Name, and only hinted at the removed TargetIds (it noticed the parameter was no longer used, which is not the same as knowing a target is missing). It
missed all three mistakes inside Content: a string instead of a policy, a Principal, and Effect: Denied all gave exit status 0. For this version, the template linter checks the
shape of the CloudFormation resource but not the policy language inside it. That is why the SCP checker above is a separate step, and why neither tool replaces testing on one account first (see below). Not run: other cfn-lint versions.
How SCPs combine
The evaluation page gives the rules, and they are worth stating exactly:
- Allow needs a yes at every level. "There must be an explicit
Allowstatement at every level from the root through each OU in the direct path to the account (including the target account itself)." - Deny needs a no at any level. "Any SCP from the root through each OU in the direct path to the account (including the target account itself) can deny that permission." A deny at the root beats an allow below it.
- Several SCPs at the same level add together. Scenario 7 on the page has two allow lists at the root, one for IAM and EC2 and one for S3 and CloudWatch, and the accounts get all four services.
I turned the page's scenarios into a small model and ran them. The "docs say" column is my reading of each scenario's prose (the figures are images I could not see, so for scenarios 1 and 4 I assumed the accounts not
mentioned as having extra SCPs have only FullAWSAccess). The model is the evaluator code below; it handles Action, NotAction, and multiple SCPs per level, and refuses to guess about Condition.
Run here (level 1). The model against the documentation's scenarios (a set of five probe actions: ec2:RunInstances, s3:GetObject, iam:CreateRole, cloudwatch:PutMetricData, lambda:InvokeFunction):
All ten account results match what the documentation says. The control line shows the comparison can fail: with one expectation flipped on purpose, exactly one mismatch is reported. The rest of the run shows the two ideas people get wrong:
An account with no identity policy can do nothing even though the SCP allows everything (the ceiling is not a grant); an administrator with * still cannot run EC2 once an SCP denies it; and the same deny does not touch the
management account. This is a model of the documented rules. It says nothing about how real requests are evaluated once conditions, resource policies, boundaries or session policies join in: AWS Intermediate/Advanced 2 takes
the full evaluation order.
Signing In: IAM Identity Center
In AWS Fundamentals 2 you met IAM users, with passwords and access keys. With many accounts that does not scale: a user per person per account, each with their own long-lived keys. AWS IAM Identity Center is the replacement for people (workforce users). The documentation describes it as the way to "manage centrally your workforce access to AWS accounts":
- One sign-in point. Users sign in at the AWS access portal and see the accounts and roles assigned to them. You can connect an existing identity provider (it supports SAML 2.0) or create users in Identity Center itself.
- Permission sets. A permission set is a reusable bundle of permissions for a job function such as administrator or read-only. You provision permission sets across accounts and assign them to users and groups. The documentation says this feature "is available only for organization instances".
- Organization instance. The documentation calls this "the best practice": it is deployed in the management account, and it is the only kind of instance that manages access to AWS accounts. An account instance is bound to one account and is for isolated application use.
- Old name. It was AWS Single Sign-On until 2022-07-26. The CLI commands and API namespaces still use
sso(for exampleaws configure sso), so older tutorials still apply.
The usual reason for this design is that people work with short-lived credentials from the portal rather than permanent access keys. That is how it is commonly described and how I understand it; the Identity Center pages I read
did not contain that sentence, so I am not quoting it. Level 3 for everything here: setting it up needs an Organizations management account, and I have none. On a machine with the CLI you would run aws configure sso and answer its prompts; I did not run it and did not check
its current wording.
AWS Control Tower
Everything above can be done by hand. AWS Control Tower does a recommended version of it for you. The documentation says it "orchestrates the capabilities of several other AWS services, including AWS Organizations, AWS Service Catalog, and AWS IAM Identity Center, to build a landing zone in less than an hour". A landing zone is the multi-account environment it builds: OUs, accounts and baseline settings. The other pieces:
- Controls (guardrails). High-level rules, in three kinds: preventive, detective and proactive. The page I read does not say which mechanism each kind uses, so I do not claim one.
- Account Factory. A template for creating new accounts that already match your standards.
- Dashboard. Shows accounts, enabled controls and non-compliant resources.
The documentation's own advice: "If you are hosting more than a handful of accounts, it's beneficial to have an orchestration layer". For a team of three with four accounts, building the structure by hand from this chapter is reasonable and teaches more; for a company with dozens, use Control Tower. Level 3: not run. I did not check what the service costs beyond the resources it creates, so check the pricing page before enabling it.
Tags and Cost Allocation
Accounts split the bill by team and environment for free. Tags split it inside an account and across accounts (for example "everything for the shop product"). A tag is a key and a value on a resource. Billing's rules, from the Cost allocation tags page:
- A tag does nothing for billing until you activate it as a cost allocation tag in the Billing and Cost Management console. User-defined and AWS-generated tags are activated separately. User-defined tags appear with a
user:prefix, AWS-generated ones withaws:. - "All tags can take up to 24 hours to appear in the Billing and Cost Management console."
- In an organization, only the management account (or a standalone account) can reach the cost allocation tags manager.
- The cost allocation report "includes both tagged and untagged resources", and its total reconciles with the Bills page. Untagged spend does not vanish; it is a line you can measure.
- "As a best practice, don't include sensitive information in tags."
Tag policies (a policy type in Organizations, up to 10 attached per entity) can require tag keys and values to follow a standard. I did not read their syntax page, so I do not show one.
Measuring tag coverage
The point of tagging is to find the gaps. Here is the whole technique on invented data: a fixed-seed generator makes a 48-row file shaped like a cost report (account, service, tag columns, cost). The dollar amounts are not AWS prices; only the method is real. The script groups the cost by tag with an explicit "(untagged)" bucket, checks that every grouping adds up to the file total, and lists the biggest untagged rows.
About a fifth of the rows had lost their owner tag by design, and they hold 22.9% of the spend. The unrelated groupings (by account, by environment) are unaffected, which is the argument for doing both: accounts
give you a total that cannot be mistagged, tags give you the cross-cutting view. The last line is a control: dropping one row changes the total, and the reconciliation catches it.
A Layout for a Small Team
A good first layout is boring. Separate the things that must never be touched by workload admins (logs, security tools), the things shared by everyone (network, pipelines), the workloads by environment, and a place to experiment. Eight accounts for one product:
| OU | Why it exists | SCPs in this plan |
|---|---|---|
| Security | Log archive and security tooling. Workload admins get no access, so the evidence cannot be altered by the people it is evidence about | leave/close denied; a deny on turning off logging |
| Infrastructure | Shared services: networking, CI/CD runners, shared images | leave/close denied |
| Workloads / Prod | Production, with few people and the strictest guardrails | leave/close and region lock (inherited), plus protection of backups |
| Workloads / NonProd | Staging and development, where people may try things | inherits from Workloads |
| Sandbox | Throw-away experiments, with budget alerts and a block on expensive services | leave/close, region lock, deny expensive services |
Notice what the nesting buys. An SCP attached to Workloads reaches Prod and NonProd without being attached to either, and the "Number of policies you can attach" limit only counts policies attached directly, not inherited ones. The
management account sits at the root and holds no workload. The plan names five SCPs; this chapter wrote two of them as files (the leave/close one and the region lock), so the rest are names in a plan, not tested policies.
Run here (level 1). The checker passes this layout and rejects the five broken copies I made (a missing tag, a workload in the management account, a shared e-mail address, an eleventh account, OUs nested six deep):
The 11-account copy passes with a warning rather than failing, because the quota is a default that can be raised. Two of the checker's rules are mine rather than AWS's: the required tags are a team convention, and the check for duplicate e-mail addresses is a sanity check (I did not find an e-mail rule on the pages I read, so I do not claim one).
Cost and Clean-Up
Nothing in this chapter creates anything in AWS, so there is nothing to delete and nothing to pay. When you do build this for real, start with a budget alert (AWS Fundamentals 9) on the management account, create the OUs and accounts, and test each SCP on the sandbox account before the root. I did not check what AWS Organizations, Identity Center or Control Tower charge for themselves (some of it may be free, and the resources they create may not be): read the pricing pages before enabling them.
aws organizations commands, the access portal, Control Tower, tag activation, and any other version of the tools used.
Practice
Write a checker for SCP files that applies the documented syntax rules (version, effect, action format, resource rules for Allow, no Principal, size both as written and with whitespace removed). Run it on five good policies and ten with one mistake each, and report the size trap. Then lint a CloudFormation template that deploys an SCP, along with six broken copies, and record which mistakes cfn-lint catches and which it misses.
Model the documented SCP evaluation rules (an allow at every level, a deny at any level, union of SCPs at one level, management account exempt) and run the documentation's scenarios as tests. Write your expected results first, count mismatches, and add a control that flips one expectation. Show that an SCP grants nothing and that the model refuses to guess about conditions.
📄 View solutionDescribe an account layout for a small team as JSON and write a checker (OU depth, account count against the default quota, no workload in the management account, required tags, distinct names, unknown OUs and SCP names). Show it rejecting five broken layouts. Then group an invented cost file by tag, measure the untagged share, and prove the totals reconcile.
📄 View solutionChapter 1 Quick Reference
- One account is one blast radius, one bill and one set of quotas; separate accounts are the strongest boundary and the bill splits itself
- Organizations: one root, a management account, member accounts, OUs (five levels deep at most); use all features, because SCPs do not exist in consolidated-billing-only mode
- Default quotas (2026-10-12): 10 accounts (adjustable), 10,240-character SCPs, 10 SCPs attached per root, OU or account; closed accounts count until permanently closed
- Keep workloads out of the management account: SCPs do not apply to it
- An SCP is a ceiling, never a grant. Allowed = an Allow at every level from root to account, no Deny anywhere, and an identity or resource policy that grants it
- SCPs apply to member-account roots too; they do not touch service-linked roles or the management account; Allow statements accept only
Resource: "*"; noPrincipal - Deny list = keep FullAWSAccess, deny specifics (easy). Allow list = replace FullAWSAccess with approved services (tight). Never remove FullAWSAccess without a replacement
- Whitespace counts toward the size limit unless the console strips it, so a policy can save from the console and be refused by the CLI
- Console-created organizations after July 10, 2026 get a leave/close deny at the root; older or CLI/CloudFormation-created ones need it attached by hand
- IAM Identity Center (organization instance in the management account) gives people portal sign-in and permission sets per account; CLI namespace is still
sso - Tags only reach the bill after activation, and take up to 24 hours; measure the untagged share and make buckets reconcile
- cfn-lint 1.57.2 checks an SCP resource's shape but not the policy inside
Content; lint the policy separately and test on one account first