Skip to main content

AWS Organizations — Multi-Account Strategy Done Right

· 6 min read
Goel Academy
DevOps & Cloud Learning Hub

A startup begins with one AWS account. The founder's personal email is the root user. Production, staging, development, and CI/CD all run in the same account. IAM users multiply. Someone accidentally deletes a production DynamoDB table while testing in what they thought was dev. Sound familiar? The single-account model works until it catastrophically doesn't. AWS Organizations exists because account isolation is the strongest security boundary AWS provides.

Why Multi-Account?

A single AWS account has several problems at scale:

  • No blast radius isolation. A misconfigured IAM policy or deleted resource affects everything.
  • Quota limits are shared. Your dev workloads consume the same service limits as production.
  • No cost separation. You can't tell which team or project is spending what.
  • Security audit nightmare. Every IAM user and role in one account means a massive attack surface.
  • Compliance complexity. PCI DSS and HIPAA workloads mixed with sandbox experiments.

Separate accounts provide hard boundaries. An IAM admin in the development account cannot access production resources. Period. No IAM policy mistake can bridge that gap (unless you explicitly create cross-account access).

Organizations Structure

AWS Organizations uses a hierarchy of root, Organizational Units (OUs), and accounts:

Root (Management Account)
├── Security OU
│ ├── Log Archive Account (centralized CloudTrail, Config)
│ └── Security Tooling Account (GuardDuty, Security Hub)
├── Infrastructure OU
│ ├── Networking Account (Transit Gateway, DNS)
│ └── Shared Services Account (CI/CD, container registry)
├── Workloads OU
│ ├── Production OU
│ │ ├── App-A Production Account
│ │ └── App-B Production Account
│ └── Non-Production OU
│ ├── Staging Account
│ └── Development Account
└── Sandbox OU
├── Developer-1 Sandbox Account
└── Developer-2 Sandbox Account
# Create the organization (from the management account)
aws organizations create-organization --feature-set ALL

# Create OUs
SECURITY_OU=$(aws organizations create-organizational-unit \
--parent-id r-abc1 \
--name "Security" \
--query 'OrganizationalUnit.Id' --output text)

INFRA_OU=$(aws organizations create-organizational-unit \
--parent-id r-abc1 \
--name "Infrastructure" \
--query 'OrganizationalUnit.Id' --output text)

WORKLOADS_OU=$(aws organizations create-organizational-unit \
--parent-id r-abc1 \
--name "Workloads" \
--query 'OrganizationalUnit.Id' --output text)

SANDBOX_OU=$(aws organizations create-organizational-unit \
--parent-id r-abc1 \
--name "Sandbox" \
--query 'OrganizationalUnit.Id' --output text)

# Create a new account
aws organizations create-account \
--email "app-a-prod@company.com" \
--account-name "App-A Production"

# Move account to the correct OU
aws organizations move-account \
--account-id 111111111111 \
--source-parent-id r-abc1 \
--destination-parent-id $WORKLOADS_OU

Service Control Policies (SCPs)

SCPs are the enforcement layer. They define the maximum permissions available in an account. Even if an IAM policy grants Allow *, an SCP can deny it. SCPs don't grant permissions — they set the outer boundary.

SCP: Restrict to Approved Regions Only

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnapprovedRegions",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": [
"us-east-1",
"us-west-2",
"eu-west-1"
]
},
"ArnNotLike": {
"aws:PrincipalARN": "arn:aws:iam::*:role/OrganizationAdmin"
}
}
}
]
}

SCP: Require Tags on Resource Creation

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyEC2WithoutTags",
"Effect": "Deny",
"Action": [
"ec2:RunInstances"
],
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"Null": {
"aws:RequestTag/Environment": "true",
"aws:RequestTag/Team": "true"
}
}
}
]
}

SCP: Prevent Root Account Login

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRootActions",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringLike": {
"aws:PrincipalArn": "arn:aws:iam::*:root"
}
}
}
]
}

SCP: Prevent Leaving the Organization

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyLeaveOrg",
"Effect": "Deny",
"Action": [
"organizations:LeaveOrganization"
],
"Resource": "*"
}
]
}

Apply SCPs to OUs:

# Attach the region restriction to Workloads OU
aws organizations attach-policy \
--policy-id p-abc123 \
--target-id $WORKLOADS_OU

# List policies on an OU
aws organizations list-policies-for-target \
--target-id $WORKLOADS_OU \
--filter SERVICE_CONTROL_POLICY

Consolidated Billing

All accounts in an organization share a single bill. This gives you:

  • Volume discounts: Combined usage across accounts qualifies for tier pricing
  • RI/Savings Plan sharing: A Reserved Instance purchased in one account applies to matching usage in any account
  • Cost Allocation Tags: Tag resources across accounts and get unified cost reporting
# View cost by linked account
aws ce get-cost-and-usage \
--time-period Start=2025-10-01,End=2025-11-01 \
--granularity MONTHLY \
--metrics BlendedCost \
--group-by Type=DIMENSION,Key=LINKED_ACCOUNT

Common OU Structures

OUPurposeSCP Focus
SecurityLog archive, security tooling, auditPrevent log deletion, restrict access
InfrastructureNetworking, shared services, CI/CDProtect Transit Gateway, limit changes
ProductionLive customer workloadsRegion lock, require encryption, deny risky actions
Non-ProductionStaging, QA, testingLess restrictive, but still tag-enforced
SandboxDeveloper experimentationBudget limits, auto-cleanup, deny expensive services
SuspendedAccounts being decommissionedDeny all actions except billing

Cross-Account Access With IAM Roles

Accounts communicate through IAM roles, not IAM users. An engineer in Account A assumes a role in Account B to perform actions:

# In Account B (target): Create a cross-account role
aws iam create-role \
--role-name CrossAccountDeployRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111111111111:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "deploy-2025"
}
}
}]
}'

# Attach permissions to the role
aws iam attach-role-policy \
--role-name CrossAccountDeployRole \
--policy-arn arn:aws:iam::aws:policy/AmazonECS_FullAccess

# From Account A (source): Assume the role
CREDS=$(aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/CrossAccountDeployRole \
--role-session-name deploy-session \
--external-id deploy-2025 \
--query 'Credentials')

# Use the temporary credentials
export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r '.AccessKeyId')
export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r '.SecretAccessKey')
export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r '.SessionToken')

# Now all commands run in Account B's context
aws ecs list-clusters

Control Tower — Account Factory Preview

AWS Control Tower automates the setup of a multi-account environment with pre-configured guardrails. It creates the landing zone (log archive, audit account, SSO), sets up guardrails (mandatory and elective), and provides Account Factory for creating new accounts with a standard baseline:

# List Control Tower guardrails
aws controltower list-enabled-controls \
--target-identifier "arn:aws:organizations::123456789012:ou/o-abc123/ou-xyz789"

# Account Factory creates accounts with:
# - CloudTrail logging to centralized S3
# - Config enabled with organization-wide rules
# - VPC with standard CIDR range
# - SSO access configured
# - Guardrails automatically applied

Control Tower is opinionated — it makes choices for you about structure and guardrails. For most organizations starting fresh, it's the fastest path to a well-governed multi-account setup.

Best Practices Summary

  1. One workload per account — isolate production, staging, and development
  2. Never use the management account for workloads — it's for billing and organization management only
  3. SCPs on OUs, not individual accounts — manage at the organizational level
  4. Centralize logging — CloudTrail, Config, and VPC Flow Logs to the log archive account
  5. Use SSO, not IAM users — AWS IAM Identity Center for human access across all accounts
  6. Automate account creation — Account Factory or custom automation, never manual console setup
  7. Tag everything — enforce with SCPs so cost allocation works from day one

What's Next

With your multi-account strategy in place, you need CI/CD pipelines that work across accounts. In the next post, we'll build a complete CI/CD pipeline on AWS using CodePipeline, CodeBuild, and CodeDeploy — including cross-account deployments from a central pipeline account.