4. AWS organisation
What it is
Instead of one AWS account for everything, we create a small organisation of accounts: one management account that only handles billing and sign-in, and one member account per environment (dev, staging, prod) plus a couple of support accounts. People sign in once through IAM Identity Center (AWS's single sign-on) and pick the account and role they need.
The design reasons are in AWS accounts and environments. This page is the exact setup.
Why we do it
- Blast radius. A mistake in dev cannot delete prod, because prod is a different account.
- Permissions. A new developer can have full access to dev and read-only access to prod.
- Cost. Each account's bill is separate, so you see what dev costs without tagging.
- Handover. Each member account can be moved into the client's own organisation later.
Before you start
- Team mailboxes, for example
aws-root@example.com. Most mail systems allowaws-root+dev@example.comstyle aliases; each AWS account needs a unique address. - A company card for the management account (it pays for all accounts).
- The main region, agreed with the project lead. For India this is usually
ap-south-1(Mumbai). Some global services also needus-east-1. - AWS CLI v2:
brew install awscli.
How it fits together
An OU (organisational unit) is a folder of accounts. Rules attached to an OU apply to every account in it.
Steps
1. Create the management account
- Go to
aws.amazon.comand choose Create an AWS Account. - Root email:
aws-root@example.com. Account name:example-management. - Add the company card. Choose the Basic support plan for now.
- Sign in as root. Open the account menu (top right), Security credentials, Assign MFA device. Use an authenticator app. Store the root password and the MFA recovery in the team vault.
- In the account menu, Account, turn on IAM user and role access to Billing information, so SSO admins can see the bill.
The root user is now put away. After step 4 you only use it for the few tasks that require it.
2. Set a budget alarm
Billing and Cost Management, Budgets, Create budget, Monthly cost budget. Set an amount agreed with the project lead and email alerts at 80% and 100% to the team mailbox. A forgotten resource then shows up in days, not at month end.
3. Turn on Organizations and create OUs
- Open AWS Organizations, Create an organization. All features are on by default; keep them.
- Under AWS accounts, select Root, Actions, Create new, and create three
OUs:
Workloads,Shared,Sandbox.
4. Create the member accounts
AWS accounts, Add an AWS account, Create an AWS account, once per account:
| Account name | OU | |
|---|---|---|
example-dev | aws-root+dev@example.com | Workloads |
example-staging | aws-root+staging@example.com | Workloads |
example-prod | aws-root+prod@example.com | Workloads |
example-shared | aws-root+shared@example.com | Shared |
example-sandbox | aws-root+sandbox@example.com | Sandbox |
Or with the CLI, signed in to the management account:
aws organizations create-account \
--email aws-root+dev@example.com --account-name example-dev
aws organizations list-accounts --query 'Accounts[].[Name,Id,Status]' --output table
aws organizations move-account --account-id <dev-id> \
--source-parent-id <root-id> --destination-parent-id <workloads-ou-id>
Leave the role name as the default, OrganizationAccountAccessRole.
5. Set up single sign-on with IAM Identity Center
-
Open IAM Identity Center in the main region and choose Enable. Pick the region carefully; moving it later means setting everything up again.
-
Settings, Identity source: keep the Identity Center directory, or connect the client's provider (Entra ID, Google Workspace) if they want staff to use company logins. If you connect one, check its plan lets you assign apps to groups, or you will assign every person by hand.
-
Settings, Authentication: require MFA for every sign-in.
-
Groups: create
admins,developers,prod-operators,read-only. -
Permission sets, Create permission set, Predefined:
Permission set Based on AdminAdministratorAccess DeveloperPowerUserAccess ProdOperatorViewOnlyAccess, plus CloudWatch Logs read ReadOnlyReadOnlyAccess -
AWS accounts: select an account, Assign users or groups, choose the group and permission set:
Group dev staging prod admins Admin Admin Admin developers Developer Developer none prod-operators ReadOnly ReadOnly ProdOperator read-only ReadOnly ReadOnly ReadOnly -
Users: add each person (or they arrive from the client's provider) and put them in groups.
Each person then sets up their laptop once:
aws configure sso # paste the AWS access portal URL from the Identity Center dashboard
aws sso login --profile example-dev
aws sts get-caller-identity --profile example-dev
6. Turn on the guardrails
| Guardrail | How | Why |
|---|---|---|
| Organisation CloudTrail | CloudTrail, Create trail, tick Enable for all accounts in my organization | One audit log of every action in every account |
| GuardDuty | GuardDuty in the management account, delegate admin to example-shared, enable for all members, in the main region and us-east-1 | Detects stolen keys and unusual activity |
| S3 Block Public Access | In each account: S3, Block Public Access settings for this account, all on | No bucket goes public by accident |
| Service control policies | Organizations, Policies, Service control policies, enable, create, attach to the Workloads OU | Rules even an admin inside the account cannot break |
Two SCPs to start with. Deny leaving the organisation:
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Deny", "Action": "organizations:LeaveOrganization", "Resource": "*" }
]
}
Deny regions you do not use (global services are excepted):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Deny",
"NotAction": ["iam:*", "organizations:*", "sts:*", "route53:*", "cloudfront:*",
"support:*", "budgets:*", "ce:*", "health:*", "waf:*", "wafv2:*"],
"Resource": "*",
"Condition": { "StringNotEquals": { "aws:RequestedRegion": ["ap-south-1", "us-east-1"] } }
}]
}
Attach SCPs to the Workloads OU first and test in dev. A too-broad deny breaks things in ways that are hard to trace.
7. Create a Terraform state bucket in each account
Terraform keeps a state file describing what it created. It lives in an S3 bucket in the same account.
P=example-dev
aws s3api create-bucket --bucket $P-tfstate --region ap-south-1 \
--create-bucket-configuration LocationConstraint=ap-south-1 --profile $P
aws s3api put-bucket-versioning --bucket $P-tfstate \
--versioning-configuration Status=Enabled --profile $P
aws s3api put-public-access-block --bucket $P-tfstate --profile $P \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
terraform {
backend "s3" {
bucket = "example-dev-tfstate"
key = "envs/dev/terraform.tfstate"
region = "ap-south-1"
encrypt = true
use_lockfile = true # S3-native locking, Terraform 1.10 and later
}
}
8. Request quota increases
New accounts start with low limits. In Service Quotas in each workload account, request what the project needs before launch week, for example Fargate vCPUs and SES sending (see Email).
Check it worked
aws organizations list-accounts --output table # five accounts, ACTIVE
aws sso login --profile example-prod
aws sts get-caller-identity --profile example-prod # your SSO role, not a user
aws cloudtrail describe-trails --profile example-prod # the organisation trail
Ask a developer to sign in: they must reach dev and staging, and be refused in prod.
If it goes wrong
| You see | It means |
|---|---|
| "Email address is already associated with an AWS account" | Each account needs a unique email. Use the +alias |
| A person sees no accounts in the access portal | They are in no group, or the group has no assignment |
| "Access denied" in staging after attaching an SCP | The region SCP is missing a global service or region. Test SCPs in dev first |
terraform init says the bucket does not exist | Wrong profile or region in the backend block |
Rules
Nothing runs in the management account except Organizations, billing and Identity Center. If it is compromised, every account is.
No IAM users for people, and no long-lived access keys on laptops. Everyone uses SSO. Once people have IAM users, moving them to SSO becomes a project of its own.
Code chooses its environment from an explicit setting, never from "which account am I in".
Mistakes we made
- Production had seven IAM users and none had MFA, because people were added before SSO existed.
- The client's identity provider plan could not assign apps to groups, so people had to be assigned one by one.
- No CloudTrail or GuardDuty for months in the first production account.
Done when
- Management account with root MFA, billing access for IAM, budget alarm.
- Organizations with Workloads, Shared, Sandbox OUs and five member accounts.
- Identity Center with MFA, groups, permission sets, assignments.
- Every team member has signed in with
aws sso login. - Organisation CloudTrail, GuardDuty, S3 Block Public Access, SCPs.
- Terraform state bucket in each account.
- Quota requests sent.