← Back to Tutorials
Tutorial 06 Intermediate

AWS Organizations: Multi-Account Security Strategy

Many growing companies start with a single AWS account for simplicity, but this creates significant security vulnerabilities as teams and environments expand. Learn how to implement proper account isolation with AWS Organizations, Service Control Policies, and cross-account access controls.

45 min implementation
12 min read
Multi-Account Governance

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

1

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.

2

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.

3

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.

4

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.

5

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
πŸ’‘
Key Advantage: Each account provides a natural security boundary. Even if one account is compromised, attackers cannot directly access resources in other accounts without additional authentication.
1

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
πŸ’‘
Important Choice: "Enable all features" allows you to use Service Control Policies (SCPs) for security governance. "Consolidated billing only" limits you to billing features without security controls.
Create Organization via CLI
# 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
Create Organizational Units
# 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
⚠️
Management Account Security: The management account has ultimate control over all member accounts. Limit access to a small security team and enable comprehensive logging.
βœ…
Organization Created! Your current account is now the management account with full administrative control over the organization structure.
2

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

Recommended Naming Pattern
# 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
⚠️
Email Management: Each AWS account requires a unique email address. Use email aliases or a dedicated email management system.
Create and Move Accounts via CLI
# 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
βœ…
Account Isolation Active! Each account now provides a natural security boundary. Resources in one account cannot directly access resources in another account without explicit cross-account permissions.
3

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.

πŸ’‘
SCP Key Concept: SCPs only restrict actions; they never grant permissions. Local IAM policies must still grant the actual permissions within the SCP boundaries.

Console Steps

3.1 Create Baseline Security SCP

  • Navigate to Organizations β†’ Policies β†’ Service control policies
  • Click Create policy
  • Policy name: BaselineSecurityControls
Baseline Security SCP
{
    "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:

Production Security SCP
{
    "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 Policies tab β†’ Attach
  • Attach both "BaselineSecurityControls" and "ProductionSecurityControls"
  • Attach "BaselineSecurityControls" to other OUs
Attach SCPs via CLI
# 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
⚠️
SCP Testing: Test SCPs in non-production environments first. An overly restrictive SCP can prevent legitimate operations. Always maintain emergency access through the management account.
βœ…
Security Guardrails Active! SCPs are now enforcing organization-wide security controls that cannot be bypassed by individual account administrators.
4

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:

Cross-Account Trust Policy
{
    "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 (run in member account)
# 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:

Role Assumption Policy
{
    "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
# 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
# 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
βœ…
Cross-Account Access & Billing Configured! Administrators can now securely access member accounts with MFA requirements, and you have centralized billing visibility with cost allocation.

Validate Your Configuration

Complete these checks to ensure your AWS Organizations security is properly configured:

Validation Script

organizations-validation.sh
#!/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.

References