A Small-Team AWS Organization: Five Accounts, Three SCPs, No Long-Lived Keys
Running everything in one AWS account works until the day a staging script deletes a production table, or a leaked developer key turns out to reach everything. Separate accounts provide a useful boundary for credentials, quotas and policies. Cross-account trust, shared deployment roles and network paths can still cross it; review those explicitly.
You don’t need a 40-account landing zone to get most of the benefit. This is a minimal setup that follows the shape of AWS’s own multi-account guidance, sized for a small team.
The accounts
Management account. The AWS Organizations root. It owns billing, the organization, IAM Identity Center (or delegates it) and service control policies. Run no workloads here. SCPs don’t apply to the management account, so anything running in it sits outside your guardrails. Very few people should have access.
Log archive. Receives the organization CloudTrail trail and other audit logs, in a bucket that workload-account administrators can’t modify or delete. Its purpose is that even full admin in production can’t erase the record of what they did.
Security tooling. The delegated administrator for GuardDuty, Security Hub and IAM Access Analyzer across the organization. It’s where findings collect, separate from the accounts they’re about.
Non-production. Development and staging. At small scale one account is fine. Split it when teams or compliance need to.
Production. Customer-facing workloads only. Fewer people with write access, and changes arrive through CI.
That’s five accounts. AWS’s guidance puts log archive and security tooling in a Security OU and workloads in a Workloads OU with separate prod and non-prod OUs, which lets you attach different SCPs to each. Add accounts when a specific risk calls for the isolation (a separate account per tenant, or for an experimental agent with broad permissions), not because a diagram has more boxes.
Service control policies worth having on day one
SCPs constrain member-account IAM principals, including the root user. They do not constrain management-account principals or service-linked roles, and do not grant permissions. Keep the first set small and uncontroversial. These three go on the Workloads OU:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyLeavingOrganization",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
},
{
"Sid": "ProtectAuditTrail",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"cloudtrail:UpdateTrail",
"cloudtrail:PutEventSelectors"
],
"Resource": "*"
},
{
"Sid": "DenyUnusedRegions",
"Effect": "Deny",
"NotAction": [
"iam:*", "organizations:*", "sts:*", "support:*",
"cloudfront:*", "route53:*", "route53domains:*",
"budgets:*", "ce:*", "health:*", "waf:*", "wafv2:*", "shield:*",
"sso:*", "identitystore:*", "account:*", "trustedadvisor:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:RequestedRegion": ["us-east-1", "us-west-2"] }
}
}
]
}This illustrative region restriction exempts selected services whose endpoint behavior needs separate review. Global services do not all share one endpoint rule. The exceptions also leave those services unrestricted by this statement. Test in non-production first, and expect to add a service or two. AWS publishes an example region-deny SCP to compare against.
The organization trail itself is managed from the management account, where SCPs don’t apply, so the audit-trail statement doesn’t get in your way.
For root users, turn on centralized root access management from the management account. Use its supported workflows to remove member-account root credentials where appropriate and perform supported privileged tasks through short-lived sessions. Enabling the feature is not evidence that every existing credential has been removed. That’s cleaner than an SCP that denies the root user, which can get in the way of those same tasks.
Human access: IAM Identity Center, no IAM users
Nobody should have an IAM user with a password or an access key in any workload account. IAM Identity Center gives people one sign-in, backed by your existing identity provider, which federates into short-lived role sessions in each account.
Define permission sets by job, not by person:
- ReadOnly, assigned to engineers in every account.
- Developer, with write access in non-production and read-only in production.
- ProductionOperator, with scoped write access in production for the few people on call. Session duration one hour.
- BreakGlass, full admin in production, assigned to two people, alerting on every use.
Offboarding starts with disabling the identity and assignments. Already issued sessions may remain valid; include session revocation and verification in the offboarding procedure. See Identity Center session controls.
Machine access: OIDC, no stored keys
CI needs to deploy, and that’s where long-lived keys usually creep back in. Use GitHub Actions’ OIDC federation instead: each workload account has a deploy role that trusts GitHub’s OIDC provider, restricted to a specific repository and branch:
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:acme/platform:ref:refs/heads/main"
}
}An audience check alone is insufficient: restrict the subject to the intended repository and deployment context. AWS also validates GitHub trust-policy subjects; review the complete trust policy rather than treating this fragment as deployable JSON. Pin it exactly. For production, pin to a GitHub environment (repo:acme/platform:environment:production) that requires approval.
Terraform layout
- One state file per account. Prefer separate applies for production and non-production so a review can identify the target and failure boundary.
- An
account-baselinemodule applied to every workload account: CloudTrail forwarding checks, GuardDuty membership, the default VPC removed, the OIDC deploy role, and a budget alert. - Workload infrastructure in separate root modules per account, which can read baseline outputs but never modify the baseline.
Where this stops
Accounts limit blast radius between environments. Inside production, the same IAM discipline still applies: an over-permissioned agent role in the production account can still do production-sized damage. The account structure means that damage stops at the account boundary, and the log archive means you’ll be able to see exactly what happened.