Multi-Account AWS Strategie met AWS Organizations | Forrict Ga naar hoofdinhoud
Cloud Architectuur AWS Best Practices

Multi-Account AWS Strategie met AWS Organizations

Forrict Team
Multi-Account AWS Strategie met AWS Organizations
Een uitgebreide gids voor het ontwerpen en implementeren van een schaalbare multi-account AWS architectuur met AWS Organizations, Control Tower en Infrastructure as Code

Multi-Account AWS Strategie met AWS Organizations

Het bouwen van een veilige, schaalbare en beheerbare AWS basis via strategische multi-account architectuur

Introductie

Naarmate organisaties hun AWS-footprint uitbreiden, wordt het beheer van resources in één enkel AWS-account snel onbeheersbaar en riskant. Een goed ontworpen multi-account strategie met AWS Organizations is fundamenteel voor het bereiken van security, compliance en operationele excellentie op schaal.

Deze uitgebreide gids leidt je door het ontwerpen en implementeren van een robuuste multi-account architectuur, inclusief organizational unit (OU) ontwerp, Service Control Policies (SCPs), cross-account toegangspatronen en Infrastructure as Code (IaC) automatisering met AWS CDK en Terraform.

In deze gids leer je:

  • Waarom multi-account strategieën essentieel zijn voor enterprise AWS deployments
  • Hoe je een effectieve organizational unit (OU) hiërarchie ontwerpt
  • Het implementeren van guardrails met Service Control Policies (SCPs)
  • Automatisering van account provisioning met AWS Control Tower en IaC
  • Best practices voor cross-account toegang en resource sharing
  • Praktische implementatiepatronen met code voorbeelden

Waarom Multi-Account Architectuur Essentieel Is

Het Single Account Anti-Pattern

Het draaien van alle workloads in één enkel AWS-account creëert aanzienlijke uitdagingen:

Security Risico’s:

  • Blast radius: Een security incident treft alle resources
  • Moeilijk om least privilege te implementeren over diverse workloads
  • Uitdagend om gevoelige data en compliance grenzen te isoleren
  • Geen duidelijke scheiding tussen omgevingen (dev, test, prod)

Operationele Complexiteit:

  • Resource limits gelden op account niveau (VPCs, Elastic IPs, etc.)
  • Moeilijk om kosten per business unit of project te tracken
  • Naamconflicten en resource organisatie uitdagingen
  • Complexe IAM policies die toegang proberen te scheiden

Compliance Problemen:

  • Lastig om duidelijke data boundaries aan te tonen voor auditors
  • Moeilijk om verschillende compliance controls per workload toe te passen
  • Uitdagend om audit trails per business unit te onderhouden

Voordelen van Multi-Account Strategie

1. Security Isolatie

  • Elk account biedt een harde security boundary
  • Beperkt de blast radius van security incidenten
  • Eenvoudigere implementatie van least privilege
  • Duidelijke scheiding van verantwoordelijkheden

2. Billing en Kostenbeheer

  • Heldere kostentoewijzing per account/business unit
  • Gescheiden budgetten en kostencentrales
  • Betere chargeback mechanismen
  • Makkelijker om kostenoptimalisatie kansen te identificeren

3. Compliance en Governance

  • Geïsoleerde compliance boundaries per workload
  • Verschillende security controls per account type
  • Duidelijke audit trails per business unit
  • Eenvoudiger om aan regelgeving te voldoen

4. Operationele Voordelen

  • Vermijd service limits in enkel account
  • Betere resource organisatie
  • Onafhankelijke deployment cycles
  • Verminderd risico bij changes over omgevingen

AWS Organizations Kernconcepten

Organizational Units (OUs)

Organizational Units zijn logische groeperingen van AWS accounts die gemeenschappelijke governance vereisten delen. OUs maken hiërarchische policy toepassing en account organisatie mogelijk.

Voorbeeld OU Hiërarchie:

Root
├── Security OU
│   ├── Log Archive Account
│   ├── Security Audit Account
│   └── Security Tooling Account
├── Infrastructure OU
│   ├── Network Account
│   ├── Shared Services Account
│   └── DNS Account
├── Workloads OU
│   ├── Productie OU
│   │   ├── Prod-App-A Account
│   │   └── Prod-App-B Account
│   ├── Acceptatie OU
│   │   └── Acc-App Accounts
│   └── Ontwikkeling OU
│       └── Dev-App Accounts
├── Sandbox OU
│   └── Individuele Developer Accounts
└── Suspended OU
    └── Uit gebruik genomen Accounts

Service Control Policies (SCPs)

SCPs zijn een type organisatie policy die je gebruikt om permissions te beheren over je organisatie. SCPs bieden centrale controle over de maximaal beschikbare permissions voor alle accounts.

Belangrijke Kenmerken:

  • SCPs verlenen geen permissions; ze stellen permission guardrails in
  • Ze definiëren de maximale permissions voor accounts
  • SCPs beïnvloeden alle users en roles in gekoppelde accounts, inclusief root
  • Beïnvloeden nooit het management account (voorheen master account)

Account Structuur Patronen

Op Omgeving:

Productie OU → Acceptatie OU → Ontwikkeling OU → Sandbox OU

Best voor: Organisaties met duidelijke omgevingsscheiding

Op Business Unit:

Sales OU → Marketing OU → Engineering OU → Finance OU

Best voor: Grote ondernemingen met onafhankelijke business units

Op Compliance Boundary:

PCI-DSS OU → AVG OU → Algemeen OU

Best voor: Organisaties met verschillende compliance eisen

Hybride Aanpak (Aanbevolen):

Root
├── Security OU (functie-gebaseerd)
├── Infrastructure OU (functie-gebaseerd)
├── Productie OU
│   ├── Finance Workloads (business-unit)
│   └── Klantgerichte Apps (business-unit)
└── Non-Productie OU
    ├── Ontwikkeling (omgeving)
    └── Acceptatie (omgeving)

Ontwerpen van je OU Structuur

Best Practices voor OU Ontwerp

1. Houd Het Simpel

  • Begin met 3-5 top-level OUs
  • Vermijd diepe nesting (maximaal 3 niveaus aanbevolen)
  • Makkelijk te begrijpen en uit te leggen aan stakeholders
  • Kan in de loop der tijd evolueren naar behoefte

2. Plan voor Schaal

Huidig: 10 accounts → Toekomst: 100+ accounts

Ontwerp je structuur om groei te accommoderen zonder grote reorganisatie.

3. Stem Af op Business Structuur

  • Spiegel organisatorische rapportagelijnen waar van toepassing
  • Overweeg kostencenter afstemming
  • Maak duidelijke ownership en accountability mogelijk

4. Security-First Aanpak

  • Scheid security en infrastructure accounts
  • Isoleer gevoelige workloads
  • Duidelijke productie vs. non-productie grenzen

Voorbeeld OU Structuur voor Middelgroot Bedrijf

# OU Ontwerp: Middelgroot SaaS Bedrijf
Root:
  Security_OU:
    doel: "Gecentraliseerde security en audit functies"
    accounts:
      - Log-Archive (alle CloudTrail, Config, Flow Logs)
      - Security-Audit (read-only toegang voor auditing)
      - Security-Tooling (GuardDuty, Security Hub, etc.)
    scps:
      - deny-region-outside-eu
      - require-encryption

  Infrastructure_OU:
    doel: "Gedeelde infrastructuur services"
    accounts:
      - Network (Transit Gateway, VPN, Direct Connect)
      - Shared-Services (CI/CD, Artifact Registry)
      - DNS (Route53 hosted zones)
    scps:
      - deny-region-outside-eu
      - restrict-instance-types

  Productie_OU:
    doel: "Productie workloads"
    accounts:
      - Prod-API-Backend
      - Prod-Frontend
      - Prod-Data-Platform
    scps:
      - deny-region-outside-eu
      - require-encryption
      - require-mfa
      - deny-root-user
      - require-backup-tags

  Non_Productie_OU:
    doel: "Ontwikkeling en testen"
    acceptatie_ou:
      accounts:
        - Acc-Full-Stack
    ontwikkeling_ou:
      accounts:
        - Dev-Engineering
    scps:
      - deny-region-outside-eu
      - cost-control

  Sandbox_OU:
    doel: "Individueel experimenteren"
    accounts:
      - Sandbox-Engineer-1
      - Sandbox-Engineer-2
    scps:
      - cost-limits-strict
      - auto-terminate-resources

Service Control Policies (SCPs)

SCP Evaluatie Logica

Effectieve Permissions = Identity Policy ∩ SCP ∩ Resource Policy

Zelfs als een IAM policy toegang verleent, kan een SCP het weigeren. SCPs fungeren als guardrails.

Essentiële SCPs voor Enterprise

1. Weiger Operaties Buiten Goedgekeurde Regio’s

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

Waarom dit belangrijk is: Data residency compliance (AVG, lokale regelgeving)

2. Vereist Encryptie voor S3 en EBS

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "WeigerOnversleuteldeS3Uploads",
      "Effect": "Deny",
      "Action": "s3:PutObject",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": [
            "AES256",
            "aws:kms"
          ]
        }
      }
    },
    {
      "Sid": "WeigerOnversleuteldeEBS",
      "Effect": "Deny",
      "Action": [
        "ec2:RunInstances",
        "ec2:CreateVolume"
      ],
      "Resource": "*",
      "Condition": {
        "Bool": {
          "ec2:Encrypted": "false"
        }
      }
    }
  ]
}

Waarom dit belangrijk is: Security baseline, compliance vereisten

3. Vereist MFA voor Gevoelige Operaties

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "WeigerActiesZonderMFA",
      "Effect": "Deny",
      "Action": [
        "ec2:StopInstances",
        "ec2:TerminateInstances",
        "rds:DeleteDBInstance",
        "s3:DeleteBucket"
      ],
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    }
  ]
}

Waarom dit belangrijk is: Voorkom onbedoelde verwijderingen, extra beveiligingslaag

4. Voorkom Root User Gebruik

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

Waarom dit belangrijk is: Root user moet nooit gebruikt worden voor dagelijkse operaties

5. Kostencontrole voor Sandbox Accounts

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "WeigerDureInstanceTypes",
      "Effect": "Deny",
      "Action": "ec2:RunInstances",
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringNotLike": {
          "ec2:InstanceType": [
            "t3.*",
            "t3a.*",
            "t4g.*"
          ]
        }
      }
    },
    {
      "Sid": "WeigerDureRDS",
      "Effect": "Deny",
      "Action": "rds:CreateDBInstance",
      "Resource": "*",
      "Condition": {
        "StringNotLike": {
          "rds:DatabaseClass": "db.t3.*"
        }
      }
    }
  ]
}

Waarom dit belangrijk is: Voorkom ongecontroleerde kosten in experimentele accounts

Implementeren met AWS Control Tower

Wat is AWS Control Tower?

AWS Control Tower automatiseert de setup van een goed ontworpen multi-account omgeving gebaseerd op AWS best practices. Het bouwt voort op AWS Organizations en voegt toe:

  • Landing Zone: Geautomatiseerde baseline omgeving
  • Guardrails: Vooraf geconfigureerde SCPs en Config rules
  • Account Factory: Self-service account provisioning
  • Dashboard: Gecentraliseerd compliance overzicht

Control Tower Setup

Vereisten:

  • Nieuwe of bestaande AWS Organization
  • Management account met geschikte permissions
  • E-mailadressen voor Log Archive en Audit accounts

Deployment Stappen:

# 1. Zorg dat je in het management account zit
aws sts get-caller-identity

# 2. Deploy Control Tower via AWS Console
# Navigeer naar: AWS Control Tower → Set up landing zone

# 3. Configureer regio's (selecteer je toegestane regio's)
# Home Regio: eu-west-1
# Aanvullende Regio's: eu-central-1

# 4. Configureer fundamentele OU structuur
# Standaard OUs aangemaakt:
# - Security (Log Archive + Audit)
# - Sandbox

# 5. Schakel guardrails in

Control Tower Guardrails

Verplichte Guardrails (Altijd Afgedwongen):

  • Sta geen publieke schrijftoegang toe voor S3 buckets
  • Sta geen publieke leestoegang toe voor S3 buckets
  • Schakel MFA in voor root user
  • Sta geen wijzigingen toe aan encryptie configuratie voor S3 buckets

Sterk Aanbevolen Guardrails:

  • Schakel encryptie in voor EBS volumes
  • Schakel encryptie at rest in voor RDS instances
  • Detecteer of MFA is ingeschakeld voor IAM users
  • Detecteer of publieke routes bestaan in route tables

Electieve Guardrails:

  • Sta geen internetverbinding toe via RDP
  • Sta geen VPCs toe met internet gateways in specifieke OUs
  • Sta geen specifieke instance types toe

Account Factory Automatisering

Account Factory maakt self-service account creatie mogelijk met gestandaardiseerde baselines.

Customize Account Factory met Service Catalog:

# lambda/account-vending.py
import boto3
import json

def lambda_handler(event, context):
    """
    Custom account vending machine
    Getriggerd nadat Account Factory nieuw account creëert
    """
    account_id = event['account_id']
    account_email = event['account_email']
    ou_name = event['organizational_unit']

    # Initialiseer clients
    organizations = boto3.client('organizations')

    # Pas tags toe
    organizations.tag_resource(
        ResourceId=account_id,
        Tags=[
            {'Key': 'ManagedBy', 'Value': 'AccountFactory'},
            {'Key': 'Environment', 'Value': ou_name},
            {'Key': 'CostCenter', 'Value': event.get('cost_center', 'Default')}
        ]
    )

    # Schakel security services in
    enable_security_services(account_id)

    # Creëer baseline VPC
    create_baseline_vpc(account_id, event.get('vpc_config', {}))

    # Setup cross-account roles
    create_cross_account_roles(account_id)

    return {
        'statusCode': 200,
        'body': json.dumps({
            'message': f'Account {account_id} succesvol geconfigureerd',
            'account_email': account_email
        })
    }

def enable_security_services(account_id):
    """Schakel GuardDuty, Security Hub, Config in"""
    # Assume role in nieuw account
    sts = boto3.client('sts')
    assumed_role = sts.assume_role(
        RoleArn=f'arn:aws:iam::{account_id}:role/AWSControlTowerExecution',
        RoleSessionName='SecuritySetup'
    )

    credentials = assumed_role['Credentials']

    # Schakel GuardDuty in
    guardduty = boto3.client(
        'guardduty',
        aws_access_key_id=credentials['AccessKeyId'],
        aws_secret_access_key=credentials['SecretAccessKey'],
        aws_session_token=credentials['SessionToken']
    )

    detector = guardduty.create_detector(Enable=True)

    # Schakel Security Hub in
    securityhub = boto3.client(
        'securityhub',
        aws_access_key_id=credentials['AccessKeyId'],
        aws_secret_access_key=credentials['SecretAccessKey'],
        aws_session_token=credentials['SessionToken']
    )

    securityhub.enable_security_hub()

    # Schakel AWS Config in
    config = boto3.client(
        'config',
        aws_access_key_id=credentials['AccessKeyId'],
        aws_secret_access_key=credentials['SecretAccessKey'],
        aws_session_token=credentials['SessionToken']
    )

    # Config recorder setup (vereenvoudigd)
    config.put_configuration_recorder(
        ConfigurationRecorder={
            'name': 'default',
            'roleARN': f'arn:aws:iam::{account_id}:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig',
            'recordingGroup': {
                'allSupported': True,
                'includeGlobalResourceTypes': True
            }
        }
    )

def create_baseline_vpc(account_id, vpc_config):
    """Creëer gestandaardiseerde VPC met CDK/CloudFormation"""
    # Implementatie zou VPC stack deployen
    pass

def create_cross_account_roles(account_id):
    """Creëer roles voor cross-account toegang"""
    # Implementatie zou IAM roles creëren
    pass

Infrastructure as Code: AWS Organizations met CDK

Complete CDK Voorbeeld: Organization Setup

// lib/organization-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as organizations from 'aws-cdk-lib/aws-organizations';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class OrganizationStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Creëer Organizational Units
    const securityOU = new organizations.CfnOrganizationalUnit(this, 'SecurityOU', {
      name: 'Security',
      parentId: 'r-xxxx', // Root ID van AWS Organizations
    });

    const infrastructureOU = new organizations.CfnOrganizationalUnit(this, 'InfrastructureOU', {
      name: 'Infrastructure',
      parentId: 'r-xxxx',
    });

    const productionOU = new organizations.CfnOrganizationalUnit(this, 'ProductionOU', {
      name: 'Productie',
      parentId: 'r-xxxx',
    });

    const nonProductionOU = new organizations.CfnOrganizationalUnit(this, 'NonProductionOU', {
      name: 'NonProductie',
      parentId: 'r-xxxx',
    });

    // Creëer Service Control Policies
    const denyOutsideEuSCP = new organizations.CfnPolicy(this, 'DenyOutsideEU', {
      name: 'WeigerRegioBuitenEU',
      description: 'Voorkom resource creatie buiten EU regio\'s',
      type: 'SERVICE_CONTROL_POLICY',
      content: JSON.stringify({
        Version: '2012-10-17',
        Statement: [
          {
            Sid: 'WeigerAllesBuitenEU',
            Effect: 'Deny',
            Action: '*',
            Resource: '*',
            Condition: {
              StringNotEquals: {
                'aws:RequestedRegion': ['eu-west-1', 'eu-central-1'],
              },
            },
          },
        ],
      }),
    });

    const requireEncryptionSCP = new organizations.CfnPolicy(this, 'RequireEncryption', {
      name: 'VereistEncryptie',
      description: 'Vereist encryptie voor S3 en EBS',
      type: 'SERVICE_CONTROL_POLICY',
      content: JSON.stringify({
        Version: '2012-10-17',
        Statement: [
          {
            Sid: 'WeigerOnversleuteldeS3',
            Effect: 'Deny',
            Action: 's3:PutObject',
            Resource: '*',
            Condition: {
              StringNotEquals: {
                's3:x-amz-server-side-encryption': ['AES256', 'aws:kms'],
              },
            },
          },
          {
            Sid: 'WeigerOnversleuteldeEBS',
            Effect: 'Deny',
            Action: ['ec2:RunInstances', 'ec2:CreateVolume'],
            Resource: '*',
            Condition: {
              Bool: {
                'ec2:Encrypted': 'false',
              },
            },
          },
        ],
      }),
    });

    const denyRootUserSCP = new organizations.CfnPolicy(this, 'DenyRootUser', {
      name: 'WeigerRootUser',
      description: 'Voorkom root user om acties uit te voeren',
      type: 'SERVICE_CONTROL_POLICY',
      content: JSON.stringify({
        Version: '2012-10-17',
        Statement: [
          {
            Sid: 'WeigerRootUser',
            Effect: 'Deny',
            Action: '*',
            Resource: '*',
            Condition: {
              StringLike: {
                'aws:PrincipalArn': 'arn:aws:iam::*:root',
              },
            },
          },
        ],
      }),
    });

    // Koppel SCPs aan OUs
    new organizations.CfnPolicyAttachment(this, 'SecurityOURegionPolicy', {
      policyId: denyOutsideEuSCP.ref,
      targetId: securityOU.ref,
    });

    new organizations.CfnPolicyAttachment(this, 'ProductionOURegionPolicy', {
      policyId: denyOutsideEuSCP.ref,
      targetId: productionOU.ref,
    });

    new organizations.CfnPolicyAttachment(this, 'ProductionOUEncryptionPolicy', {
      policyId: requireEncryptionSCP.ref,
      targetId: productionOU.ref,
    });

    new organizations.CfnPolicyAttachment(this, 'ProductionOURootPolicy', {
      policyId: denyRootUserSCP.ref,
      targetId: productionOU.ref,
    });

    // Outputs
    new cdk.CfnOutput(this, 'SecurityOUId', {
      value: securityOU.ref,
      description: 'Security OU ID',
    });

    new cdk.CfnOutput(this, 'ProductionOUId', {
      value: productionOU.ref,
      description: 'Productie OU ID',
    });
  }
}

// bin/organization-app.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { OrganizationStack } from '../lib/organization-stack';

const app = new cdk.App();

new OrganizationStack(app, 'OrganizationStack', {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: 'us-east-1', // Organizations API is in us-east-1
  },
  description: 'AWS Organizations multi-account structuur',
});

app.synth();

Terraform Alternatief

# terraform/organizations.tf
terraform {
  required_version = ">= 1.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
}

# Creëer Organizational Units
resource "aws_organizations_organizational_unit" "security" {
  name      = "Security"
  parent_id = aws_organizations_organization.main.roots[0].id
}

resource "aws_organizations_organizational_unit" "infrastructure" {
  name      = "Infrastructure"
  parent_id = aws_organizations_organization.main.roots[0].id
}

resource "aws_organizations_organizational_unit" "productie" {
  name      = "Productie"
  parent_id = aws_organizations_organization.main.roots[0].id
}

resource "aws_organizations_organizational_unit" "non_productie" {
  name      = "NonProductie"
  parent_id = aws_organizations_organization.main.roots[0].id
}

# Service Control Policy: Weiger buiten EU
resource "aws_organizations_policy" "deny_outside_eu" {
  name        = "WeigerRegioBuitenEU"
  description = "Voorkom resource creatie buiten EU regio's"
  type        = "SERVICE_CONTROL_POLICY"

  content = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "WeigerAllesBuitenEU"
        Effect = "Deny"
        Action = "*"
        Resource = "*"
        Condition = {
          StringNotEquals = {
            "aws:RequestedRegion" = [
              "eu-west-1",
              "eu-central-1"
            ]
          }
        }
      }
    ]
  })
}

# Service Control Policy: Vereist Encryptie
resource "aws_organizations_policy" "require_encryption" {
  name        = "VereistEncryptie"
  description = "Vereist encryptie voor S3 en EBS"
  type        = "SERVICE_CONTROL_POLICY"

  content = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "WeigerOnversleuteldeS3"
        Effect = "Deny"
        Action = "s3:PutObject"
        Resource = "*"
        Condition = {
          StringNotEquals = {
            "s3:x-amz-server-side-encryption" = ["AES256", "aws:kms"]
          }
        }
      },
      {
        Sid    = "WeigerOnversleuteldeEBS"
        Effect = "Deny"
        Action = [
          "ec2:RunInstances",
          "ec2:CreateVolume"
        ]
        Resource = "*"
        Condition = {
          Bool = {
            "ec2:Encrypted" = "false"
          }
        }
      }
    ]
  })
}

# Koppel policies aan OUs
resource "aws_organizations_policy_attachment" "security_region_policy" {
  policy_id = aws_organizations_policy.deny_outside_eu.id
  target_id = aws_organizations_organizational_unit.security.id
}

resource "aws_organizations_policy_attachment" "productie_region_policy" {
  policy_id = aws_organizations_policy.deny_outside_eu.id
  target_id = aws_organizations_organizational_unit.productie.id
}

resource "aws_organizations_policy_attachment" "productie_encryption_policy" {
  policy_id = aws_organizations_policy.require_encryption.id
  target_id = aws_organizations_organizational_unit.productie.id
}

# Outputs
output "security_ou_id" {
  description = "Security OU ID"
  value       = aws_organizations_organizational_unit.security.id
}

output "productie_ou_id" {
  description = "Productie OU ID"
  value       = aws_organizations_organizational_unit.productie.id
}

Cross-Account Toegangspatronen

Patroon 1: Cross-Account IAM Roles (Aanbevolen)

Scenario: Applicatie in Account A heeft toegang nodig tot S3 bucket in Account B

Account B (Resource Account):

// lib/resource-account-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class ResourceAccountStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // S3 bucket in Account B
    const dataBucket = new s3.Bucket(this, 'DataBucket', {
      bucketName: 'account-b-data-bucket',
      encryption: s3.BucketEncryption.S3_MANAGED,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
    });

    // Creëer cross-account role
    const crossAccountRole = new iam.Role(this, 'CrossAccountS3AccessRole', {
      roleName: 'CrossAccountS3Access',
      assumedBy: new iam.AccountPrincipal('111122223333'), // Account A ID
      description: 'Role voor Account A om S3 bucket in Account B te benaderen',
    });

    // Verleen permissions
    dataBucket.grantRead(crossAccountRole);

    // Output role ARN
    new cdk.CfnOutput(this, 'CrossAccountRoleArn', {
      value: crossAccountRole.roleArn,
      description: 'Cross-account role ARN',
      exportName: 'CrossAccountS3RoleArn',
    });
  }
}

Account A (Requesting Account):

// lib/application-account-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';

export class ApplicationAccountStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Lambda functie die toegang nodig heeft tot Account B
    const dataProcessorFunction = new lambda.Function(this, 'DataProcessor', {
      runtime: lambda.Runtime.PYTHON_3_11,
      handler: 'index.handler',
      code: lambda.Code.fromInline(`
import boto3
import json

def handler(event, context):
    # Assume role in Account B
    sts = boto3.client('sts')
    assumed_role = sts.assume_role(
        RoleArn='arn:aws:iam::444455556666:role/CrossAccountS3Access',
        RoleSessionName='DataProcessorSession'
    )

    credentials = assumed_role['Credentials']

    # Benader S3 in Account B met assumed role
    s3 = boto3.client(
        's3',
        aws_access_key_id=credentials['AccessKeyId'],
        aws_secret_access_key=credentials['SecretAccessKey'],
        aws_session_token=credentials['SessionToken']
    )

    # Lijst objecten in Account B bucket
    response = s3.list_objects_v2(Bucket='account-b-data-bucket')

    return {
        'statusCode': 200,
        'body': json.dumps({
            'objects': response.get('Contents', [])
        })
    }
      `),
    });

    // Verleen permission om cross-account role te assumen
    dataProcessorFunction.addToRolePolicy(new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: ['sts:AssumeRole'],
      resources: ['arn:aws:iam::444455556666:role/CrossAccountS3Access'],
    }));
  }
}

Patroon 2: Resource-Based Policies

Scenario: Lambda in Account A moet worden aangeroepen door EventBridge in Account B

// Account A - Lambda met resource policy
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class CrossAccountLambdaStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const targetFunction = new lambda.Function(this, 'TargetFunction', {
      runtime: lambda.Runtime.PYTHON_3_11,
      handler: 'index.handler',
      code: lambda.Code.fromInline(`
def handler(event, context):
    print(f"Event ontvangen van Account B: {event}")
    return {'statusCode': 200, 'body': 'Verwerkt'}
      `),
    });

    // Voeg resource-based policy toe voor cross-account invocation
    targetFunction.addPermission('AllowAccountBInvoke', {
      principal: new iam.AccountPrincipal('444455556666'), // Account B
      action: 'lambda:InvokeFunction',
      sourceAccount: '444455556666',
    });

    new cdk.CfnOutput(this, 'FunctionArn', {
      value: targetFunction.functionArn,
      description: 'Lambda functie ARN voor cross-account invocation',
    });
  }
}

Patroon 3: AWS Resource Access Manager (RAM)

Deel resources zoals Transit Gateways, Subnets of Route53 Resolver rules tussen accounts.

import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ram from 'aws-cdk-lib/aws-ram';
import { Construct } from 'constructs';

export class NetworkAccountStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Creëer Transit Gateway in Network account
    const transitGateway = new ec2.CfnTransitGateway(this, 'TransitGateway', {
      description: 'Gedeelde Transit Gateway voor alle accounts',
      defaultRouteTableAssociation: 'enable',
      defaultRouteTablePropagation: 'enable',
    });

    // Deel Transit Gateway met gehele organisatie
    const tgwShare = new ram.CfnResourceShare(this, 'TGWShare', {
      name: 'TransitGatewayShare',
      resourceArns: [
        `arn:aws:ec2:${this.region}:${this.account}:transit-gateway/${transitGateway.ref}`,
      ],
      principals: [
        'arn:aws:organizations::111122223333:organization/o-xxxxxxxxxx', // Organization ARN
      ],
      allowExternalPrincipals: false,
    });

    new cdk.CfnOutput(this, 'TransitGatewayId', {
      value: transitGateway.ref,
      description: 'Gedeelde Transit Gateway ID',
      exportName: 'SharedTransitGatewayId',
    });
  }
}

Best Practices

1. Account Naamgeving en Tagging

Consistente Naamconventie:

{omgeving}-{businessunit}-{doel}-{regio}

Voorbeelden:
- prod-finance-api-euw1
- dev-engineering-sandbox-euw1
- shared-network-tgw-euw1

Verplichte Tags:

CostCenter: "CC-12345"
Environment: "Productie" | "Acceptatie" | "Ontwikkeling" | "Sandbox"
Owner: "team-naam@bedrijf.nl"
Project: "project-alpha"
Compliance: "PCI-DSS" | "AVG" | "Geen"
DataClassification: "Publiek" | "Intern" | "Vertrouwelijk" | "Beperkt"
BackupPolicy: "Dagelijks" | "Wekelijks" | "Geen"

2. Security Baseline Automatisering

Deploy security baseline automatisch naar elk nieuw account:

# baseline-security.yaml
SecurityBaseline:
  - Schakel CloudTrail in (organization trail)
  - Schakel AWS Config in
  - Schakel GuardDuty in
  - Schakel Security Hub in
  - Schakel IAM Access Analyzer in
  - Blokkeer S3 publieke toegang (account-level)
  - Schakel EBS encryptie standaard in
  - Dwing IMDSv2 af voor EC2
  - Deploy AWS Systems Manager Session Manager
  - Creëer CloudWatch Log Groups voor VPC Flow Logs

3. Kostenbeheer

Implementeer Kostentoewijzing:

  • Tag enforcement via SCPs
  • Cost and Usage Reports naar S3
  • AWS Cost Explorer toegang per account
  • Budget alerts per account/OU
  • Reserved Instance delen over organisatie

Voorbeeld Budget Alarm:

import * as budgets from 'aws-cdk-lib/aws-budgets';

new budgets.CfnBudget(this, 'MaandelijkseBudget', {
  budget: {
    budgetName: 'MaandelijksAccountBudget',
    budgetType: 'COST',
    timeUnit: 'MONTHLY',
    budgetLimit: {
      amount: 1000,
      unit: 'USD',
    },
  },
  notificationsWithSubscribers: [
    {
      notification: {
        notificationType: 'ACTUAL',
        comparisonOperator: 'GREATER_THAN',
        threshold: 80,
      },
      subscribers: [
        {
          subscriptionType: 'EMAIL',
          address: 'finance-alerts@bedrijf.nl',
        },
      ],
    },
  ],
});

4. Netwerk Connectiviteit

Hub-and-Spoke met Transit Gateway:

  • Network account: Transit Gateway hub
  • Spoke accounts: VPCs gekoppeld aan TGW
  • Gecentraliseerde egress/ingress
  • Gedeelde services toegang

Vermijd:

  • VPC peering op schaal (schaalt niet, complexe routing)
  • Publiek internet voor inter-account communicatie
  • Over-permissieve security groups

5. Compliance en Auditing

Gecentraliseerde Logging:

Alle accounts → CloudTrail → Log Archive Account S3
Alle accounts → Config → Log Archive Account S3
Alle accounts → VPC Flow Logs → Log Archive Account S3

Read-Only Audit Toegang:

// Security Audit account kan alle accounts lezen
const auditRole = new iam.Role(this, 'SecurityAuditRole', {
  roleName: 'SecurityAuditor',
  assumedBy: new iam.AccountPrincipal('999988887777'), // Security Audit account
  managedPolicies: [
    iam.ManagedPolicy.fromAwsManagedPolicyName('SecurityAudit'),
    iam.ManagedPolicy.fromAwsManagedPolicyName('ViewOnlyAccess'),
  ],
});

6. Account Lifecycle Management

Nieuw Account Checklist:

  1. Creëer via Account Factory (geautomatiseerde baseline)
  2. Wijs toe aan geschikte OU
  3. Pas vereiste SCPs toe
  4. Schakel security services in
  5. Creëer baseline VPC
  6. Configureer cross-account roles
  7. Stel billing alerts in
  8. Tag account correct
  9. Documenteer in CMDB
  10. Verleen toegang aan teams

Account Decommissioning:

  1. Verplaats naar “Suspended” OU
  2. Pas restrictieve SCP toe (weiger alles behalve billing)
  3. Verwijder resources
  4. Annuleer subscriptions
  5. Archiveer logs naar Glacier
  6. Sluit account na retentie periode

Conclusie

Een goed ontworpen multi-account AWS strategie met AWS Organizations is fundamenteel voor veilige, schaalbare en compliant cloud operaties. Door de patronen en practices uit deze gids te implementeren, bouw je een robuuste cloud omgeving die meegroeit met je organisatie.

Belangrijkste Punten:

  • Begin met een duidelijke OU structuur afgestemd op je business
  • Gebruik SCPs om guardrails in te stellen, niet om permissions te beheren
  • Automatiseer account provisioning en baseline security
  • Implementeer cross-account toegang met IAM roles
  • Tag alles voor kostentoewijzing en compliance
  • Centraliseer logging en security monitoring
  • Plan voor schaal vanaf dag één

Volgende Stappen:

  1. Beoordeel je huidige account structuur
  2. Ontwerp je doel OU hiërarchie
  3. Definieer essentiële SCPs voor je compliance vereisten
  4. Deploy AWS Control Tower of custom IaC oplossing
  5. Migreer bestaande accounts naar nieuwe structuur
  6. Implementeer geautomatiseerde account vending
  7. Train teams op multi-account best practices

Klaar om een robuuste multi-account strategie te implementeren? Forrict kan je helpen bij het ontwerpen en deployen van een AWS Organizations structuur op maat voor jouw bedrijf.

Resources

F

Forrict Team

AWS expert en consultant bij Forrict, gespecialiseerd in cloud architectuur en AWS best practices voor Nederlandse bedrijven.

Tags

AWS AWS Organizations Control Tower Multi-Account Security Cloud Architectuur Infrastructure as Code CDK

Gerelateerde Artikelen

Klaar om je AWS Infrastructuur te Transformeren?

Laten we bespreken hoe we je cloud journey kunnen optimaliseren