Multi-account & org identity architecture
Landing-zone identity, cross-account roles, and blast radius by design.
At one account, cloud identity is a policy problem. At fifty accounts it is an architecture problem: where identities live, how they cross account boundaries, and how a single compromise is contained. A landing zone answers those questions once, centrally, so every new account inherits the same guardrails instead of drifting into its own snowflake of permissions.
The account is the blast-radius boundary
An AWS account (or a GCP project, or an Azure subscription) is the strongest isolation the cloud gives you: separate resource namespaces, separate default trust, and a clean seam for billing and policy. Group them into organizational units and you can apply a service control policy (SCP) or organization policy to a whole branch of the tree at once. The design goal is that a total compromise of one workload account cannot read, write, or pivot into another — production is a different account from staging, security tooling is a different account from both, and the log archive is an account almost nobody can log into.
Organization (management account — no workloads)|- Security OU| |- log-archive (write-once audit sink)| |- security-tooling (GuardDuty/Config aggregation)|- Infrastructure OU| |- shared-services (CI, DNS, networking)|- Workloads OU|- prod|- staging|- sandbox# SCP attached to Workloads OU: deny leaving the approved region# and deny disabling the guardrails, for EVERY principal incl. admins{"Version": "2012-10-17","Statement": [{ "Sid": "RegionLock", "Effect": "Deny", "NotAction": ["iam:*","sts:*","cloudfront:*"],"Resource": "*", "Condition": { "StringNotEquals": { "aws:RequestedRegion": "eu-west-1" } } },{ "Sid": "ProtectGuardrails", "Effect": "Deny","Action": ["config:StopConfigurationRecorder","cloudtrail:StopLogging","guardduty:DeleteDetector"],"Resource": "*" }]}
Cross-account access is a role you assume, never a key you share
Access between accounts should always flow through a role in the target account that the source principal assumes for a short, logged session. Never copy an IAM user key from one account into another — that destroys attribution and cannot be revoked without rotating the key everywhere. A role has a trust policy (who may assume it) separate from its permission policy (what it can do), and every assumption shows up in CloudTrail as an sts:AssumeRole event you can alert on.
# Role "DeployBot" in the prod account. Its TRUST policy names who may assume it:{"Version": "2012-10-17","Statement": [{"Effect": "Allow","Principal": { "AWS": "arn:aws:iam::111111111111:role/ci-runner" },"Action": "sts:AssumeRole","Condition": { "StringEquals": { "sts:ExternalId": "prod-deploys-2026" } }}]}# The CI runner assumes it for a 15-minute, fully-logged session:aws sts assume-role \--role-arn arn:aws:iam::222222222222:role/DeployBot \--role-session-name ci-build-8891 \--duration-seconds 900 \--external-id prod-deploys-2026