Why Single-Account AWS Architectures Create Security Risks
When all environments share a single AWS account, compromised development credentials can potentially access production resources. Attackers use privilege escalation techniques to move from low-privilege development access to high-privilege production systems.
The Five Critical Security Risks
Lateral Movement and Privilege Escalation
Compromised development credentials can access production resources. Attackers use IAM privilege escalation to move from low-privilege to high-privilege systems.
Overprivileged Access and Role Confusion
Teams often receive broader permissions than needed because it's easier to grant access across environments. This violates least privilege principles.
Resource Tagging and Cost Confusion
Without proper account isolation, it becomes difficult to track resource ownership, implement granular billing, and enforce cost controls per team or environment.
Compliance and Audit Challenges
Many compliance frameworks require clear separation between development and production environments. Single-account architectures make it difficult to demonstrate proper controls.
Blast Radius Amplification
Security incidents, misconfigurations, or service outages affect all environments simultaneously, maximizing business impact and recovery complexity.
Benefits of Multi-Account Strategy
- Account-Level Isolation: Each AWS account provides a hard security boundary that cannot be bypassed through IAM privilege escalation
- Granular Access Control: Service Control Policies (SCPs) enforce organization-wide security guardrails
- Simplified Compliance: Clear separation between environments supports audit requirements
- Centralized Billing: Consolidated billing with detailed cost allocation per account/team
- Reduced Blast Radius: Incidents are contained within individual accounts
Create AWS Organization & Design OU Structure
~12 minutes
AWS Organizations enables you to centrally manage multiple AWS accounts. Organizational Units (OUs) group accounts and apply policies consistently based on your security, compliance, and business requirements.
Prerequisites
- AWS account with administrative privileges (this becomes your management account)
- Root account MFA enabled (see Tutorial 01)
- Valid email addresses for each member account you plan to create
- Clear naming convention for accounts (e.g., company-prod-webapp, company-dev-sandbox)
Console Steps
1.1 Create Your Organization
- Sign in to AWS Console with administrative privileges
- Search for "Organizations" in the services search bar
- Click
Create an organization - Choose
Enable all features(recommended for full SCP support) - Click
Create organization - Verify the organization creation email
# Create organization with all features
aws organizations create-organization --feature-set ALL
# Verify organization creation
aws organizations describe-organization
# Get your organization ID and root ID
aws organizations list-roots
1.2 Design and Create Organizational Units
Create OUs based on environment type. Recommended structure for growing companies:
- Production OU: Production workloads, highest restrictions, minimal permissions
- Non-Production OU: Development, staging, testing, sandbox environments
- Security OU: Security tools, log archive, compliance audit accounts
# Get your root ID
ROOT_ID=$(aws organizations list-roots --query 'Roots[0].Id' --output text)
# Create Production OU
aws organizations create-organizational-unit \
--parent-id $ROOT_ID \
--name "Production"
# Create Non-Production OU
aws organizations create-organizational-unit \
--parent-id $ROOT_ID \
--name "Non-Production"
# Create Security OU
aws organizations create-organizational-unit \
--parent-id $ROOT_ID \
--name "Security"
# List OUs to verify
aws organizations list-organizational-units-for-parent \
--parent-id $ROOT_ID \
--query 'OrganizationalUnits[*].[Name,Id]' --output table
Create Member Accounts with Isolation
~10 minutes
Create separate AWS accounts for different environments and business functions. Each account provides natural isolation and independent security controls.
Account Naming Convention
# Naming pattern: [company]-[environment]-[purpose]
# Examples:
acme-prod-webapp
acme-prod-database
acme-staging-webapp
acme-dev-sandbox
acme-security-logging
2.1 Create Production Accounts
- In Organizations console, click
Add an AWS account - Select
Create an AWS account - Account name:
acme-prod-webapp - Email:
EMAIL ADDRESS - IAM role name:
OrganizationAccountAccessRole(default) - Click
Create AWS account
# Create production account
aws organizations create-account \
--account-name "acme-prod-webapp" \
--email "EMAIL ADDRESS" \
--role-name "OrganizationAccountAccessRole"
# Wait for account creation to complete, then get account ID
ACCOUNT_ID=$(aws organizations list-accounts \
--query 'Accounts[?Name==`acme-prod-webapp`].Id' \
--output text)
# Get Production OU ID
PROD_OU_ID=$(aws organizations list-organizational-units-for-parent \
--parent-id $ROOT_ID \
--query 'OrganizationalUnits[?Name==`Production`].Id' \
--output text)
# Move account to Production OU
aws organizations move-account \
--account-id $ACCOUNT_ID \
--source-parent-id $ROOT_ID \
--destination-parent-id $PROD_OU_ID
2.2 Typical Account Structure
Create accounts for each environment:
- Production OU: prod-webapp, prod-database, prod-analytics
- Non-Production OU: staging, development, testing, sandbox
- Security OU: security-tools, log-archive
Implement Service Control Policies (SCPs)
~15 minutes
Service Control Policies (SCPs) are organization-wide guardrails that define the maximum permissions for accounts and OUs. They act as a security boundary that cannot be circumvented by local IAM policies.
Console Steps
3.1 Create Baseline Security SCP
- Navigate to
Organizations β Policies β Service control policies - Click
Create policy - Policy name:
BaselineSecurityControls
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PreventCloudTrailDisable",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"cloudtrail:UpdateTrail"
],
"Resource": "*"
},
{
"Sid": "PreventConfigChanges",
"Effect": "Deny",
"Action": [
"config:DeleteConfigurationRecorder",
"config:DeleteDeliveryChannel",
"config:StopConfigurationRecorder"
],
"Resource": "*"
},
{
"Sid": "PreventRootAccountUsage",
"Effect": "Deny",
"NotAction": [
"iam:CreateVirtualMFADevice",
"iam:EnableMFADevice",
"iam:ListVirtualMFADevices",
"iam:ListMFADevices"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalType": "Root"
}
}
}
]
}
3.2 Create Production-Specific SCP
Apply stricter controls to production environments:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RestrictInstanceTypes",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"ForAnyValue:StringNotEquals": {
"ec2:InstanceType": [
"t3.micro", "t3.small", "t3.medium",
"m5.large", "m5.xlarge",
"c5.large", "c5.xlarge"
]
}
}
},
{
"Sid": "RequireIMDSv2",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringNotEquals": {
"ec2:MetadataHttpTokens": "required"
}
}
},
{
"Sid": "DenyLeavingOrganization",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
}
]
}
3.3 Attach SCPs to OUs
- Go to
Organizations β Organize accounts - Select the "Production" OU
- Click
Policiestab βAttach - Attach both "BaselineSecurityControls" and "ProductionSecurityControls"
- Attach "BaselineSecurityControls" to other OUs
# Get policy ID
BASELINE_POLICY_ID=$(aws organizations list-policies \
--filter SERVICE_CONTROL_POLICY \
--query 'Policies[?Name==`BaselineSecurityControls`].Id' \
--output text)
# Attach to Production OU
aws organizations attach-policy \
--policy-id $BASELINE_POLICY_ID \
--target-id $PROD_OU_ID
# Verify attachment
aws organizations list-policies-for-target \
--target-id $PROD_OU_ID \
--filter SERVICE_CONTROL_POLICY
Set Up Cross-Account Access & Billing Controls
~8 minutes
Cross-account IAM roles enable secure access between accounts without sharing long-term credentials. Combined with centralized billing, this provides visibility and control across your organization.
Cross-Account Access
4.1 Create Cross-Account Administrative Role
In each member account, create a role that can be assumed from the management account with MFA required:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::MANAGEMENT_ACCOUNT_ID:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
},
"NumericLessThan": {
"aws:MultiFactorAuthAge": "3600"
}
}
}
]
}
# Create cross-account role
aws iam create-role \
--role-name CrossAccountAdminRole \
--assume-role-policy-document file://trust-policy.json \
--description "Cross-account administrative access from management account"
# Attach appropriate policy (use PowerUserAccess for non-production)
aws iam attach-role-policy \
--role-name CrossAccountAdminRole \
--policy-arn arn:aws:iam::aws:policy/PowerUserAccess
4.2 Configure Access from Management Account
Create a policy allowing role assumption:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": [
"arn:aws:iam::PROD_ACCOUNT_ID:role/CrossAccountAdminRole",
"arn:aws:iam::STAGING_ACCOUNT_ID:role/CrossAccountAdminRole",
"arn:aws:iam::DEV_ACCOUNT_ID:role/CrossAccountAdminRole"
],
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
}
}
}
]
}
Centralized Billing Controls
4.3 Set Up Cost Allocation Tags
- Go to
Billing β Cost allocation tags - Activate AWS-generated tags:
aws:createdBy,aws:cloudformation:stack-name - Create user-defined tags for cost tracking
# Recommended cost allocation tags
Environment: production | staging | development
Team: engineering | security | devops | marketing
Project: webapp | analytics | mobile-app
CostCenter: engineering-001 | marketing-002
Owner: EMAIL ADDRESS
4.4 Create Billing Alerts
Set up CloudWatch billing alarms:
# Create billing alarm
aws cloudwatch put-metric-alarm \
--alarm-name "OrganizationHighCosts" \
--alarm-description "Alert when AWS charges exceed $1000" \
--metric-name EstimatedCharges \
--namespace AWS/Billing \
--statistic Maximum \
--period 86400 \
--threshold 1000 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=Currency,Value=USD \
--alarm-actions arn:aws:sns:us-east-1:ACCOUNT:billing-alerts \
--region us-east-1
Validate Your Configuration
Complete these checks to ensure your AWS Organizations security is properly configured:
Validation Script
#!/bin/bash
# AWS Organizations Security Validation Script
echo "=== AWS Organizations Validation ==="
echo ""
# Check organization structure
echo "Checking organization structure..."
ORG_INFO=$(aws organizations describe-organization 2>/dev/null)
if [ $? -eq 0 ]; then
echo "β Organization ID: $(echo $ORG_INFO | jq -r '.Organization.Id')"
echo " Feature Set: $(echo $ORG_INFO | jq -r '.Organization.FeatureSet')"
else
echo "β Not in an organization or no access"
exit 1
fi
echo ""
# List OUs
echo "Checking organizational units..."
ROOT_ID=$(aws organizations list-roots --query 'Roots[0].Id' --output text)
aws organizations list-organizational-units-for-parent \
--parent-id $ROOT_ID \
--query 'OrganizationalUnits[*].[Name,Id]' --output table
echo ""
# Check SCPs
echo "Checking Service Control Policies..."
SCP_COUNT=$(aws organizations list-policies \
--filter SERVICE_CONTROL_POLICY \
--query 'length(Policies)' --output text)
echo " Total SCPs: $SCP_COUNT"
aws organizations list-policies \
--filter SERVICE_CONTROL_POLICY \
--query 'Policies[*].[Name,Id]' --output table
echo ""
# Check accounts
echo "Checking member accounts..."
ACCOUNT_COUNT=$(aws organizations list-accounts \
--query 'length(Accounts)' --output text)
echo " Total accounts: $ACCOUNT_COUNT"
aws organizations list-accounts \
--query 'Accounts[*].[Name,Id,Status]' --output table
echo ""
# Test SCP enforcement (try to stop CloudTrail - should fail)
echo "Testing SCP enforcement..."
echo " Attempting to stop CloudTrail (should be denied by SCP)..."
aws cloudtrail stop-logging --name test-trail 2>&1 | grep -q "AccessDenied" && \
echo " β SCP correctly denying CloudTrail changes" || \
echo " β SCP may not be enforcing CloudTrail protection"
echo ""
echo "Organizations validation complete!"
Common Mistakes to Avoid
Over-restrictive SCPs. Test in non-production first. An overly restrictive SCP can prevent legitimate operations. Always maintain emergency access.
Inadequate network planning. Design your network architecture before creating accounts to avoid complex refactoring with VPC peering and Transit Gateway later.
Inconsistent tagging conventions. Establish and enforce naming and tagging standards early to enable effective cost management and automation.
Neglecting management account security. The management account has ultimate controlβtreat it like your most critical system with minimal access and maximum monitoring.
Creating too many accounts too quickly. Start with a simple structure and evolve based on actual needs rather than theoretical requirements.
Not setting up centralized logging. Enable organization-wide CloudTrail in the management account with cross-account log delivery to a dedicated security account.
Don't Build Multi-Account Security Alone
Multi-account architecture requires expertise in security, networking, compliance, and cost optimization. AWSight provides automated governance, continuous monitoring across all your accounts, and expert guidance to ensure your multi-account strategy succeeds.