Filter concepts by levelShowing all levels.

AWS · Section 2

AWS Accounts and Organizations

Level
intermediate
Read
24 min
Concepts
5

An AWS account is a hard security and billing boundary, not a label. This section covers why production should never share an account with development, the small deliberate set of accounts a mature setup creates, and how AWS Organizations and SCPs keep every one of them governed without manual review.

What is true here

  1. An account boundary stops IAM permissions, quotas, and a compromised credential from crossing it — a naming convention does not.
  2. A mature layout is deliberate and small: one account per lifecycle stage, plus a write-only security/logging account and a shared-services account.
  3. A service control policy (SCP) is a ceiling on what an account can do — it can only remove permissions, never grant any.
  4. Cross-account access should always go through an assumed role, issuing short-lived credentials — never a long-lived key copied between accounts.

What you will be able to do

  • Explain why production should not share an account with development or staging
  • Name the minimal deliberate account set a mature AWS setup uses, and what each one is for
  • Tell a preventive guardrail (SCP) from a detective one (Config/CloudTrail), and when each applies
  • Grant one account temporary access into another without copying a long-lived credential
From one account to a governed multi-account setup
triggersattachesat birthkeeps inpolicy

New account needed

a new environment or workload

Account vending

automated creation, joined to the Organization

Guardrails attached

SCPs, baseline IAM, centralized logging

Isolated, governed account

reachable cross-account only via an assumed role

  • New account needed — a new environment or workload
    • leads to Account vending (triggers)
  • Account vending — automated creation, joined to the Organization
    • leads to Guardrails attached (attaches at birth)
  • Guardrails attached — SCPs, baseline IAM, centralized logging
    • leads to Isolated, governed account (keeps in policy)
  • Isolated, governed account — reachable cross-account only via an assumed role

Accounts and Organizations

Why the account boundary matters, which accounts to actually create, and how to keep them governed.

Why production should not live in one undifferentiated account

corebeginner

An AWS account is a hard boundary — IAM permissions, resource limits, even a compromised credential all stop at the account edge. Putting development, staging and production in one account means a mistake in one can reach the others.

Think of it as

One account is one building with no internal walls. A fire in the test kitchen and a fire in the server room are the same fire. Separate accounts are separate buildings — a disaster in one cannot walk into another.

text
One account, three environments        Three accounts, one per environment
─────────────────────────────           ─────────────────────────────
dev + staging + prod resources    →     111111111111  dev
share IAM, quotas, blast radius         222222222222  staging
                                         333333333333  prod

What we're doing: See what a shared account actually risks, using a real failure shape rather than a hypothetical.

incident.txttext
# a load test script, run against what the engineer believed was "the dev database"
DATABASE_URL=prod-payments.abc123.us-east-1.rds.amazonaws.com

# the script ran 50,000 writes/sec against it for four minutes
# before anyone noticed the connection string was wrong
2
A copy-pasted connection string pointed at production — nothing in the account stopped it, because dev and prod were the same account.
5
In a separate prod account, that same script would have failed at the credential/connectivity level, long before it reached a real table.

Why this works: A configuration mistake — the wrong URL, the wrong tag, the wrong IAM policy target — is normal and will happen. Separate accounts turn that mistake into a connection failure. One shared account turns it into an incident.

Relying on naming conventions instead of account boundaries

Wrong

text
# "isolation" enforced only by naming
payments-db-dev
payments-db-staging
payments-db-prod
# all three in the same AWS account, same IAM policies apply to all three

Better

text
# isolation enforced by the account itself
111111111111  payments-dev      (dev IAM roles can only reach this account)
222222222222  payments-staging
333333333333  payments-prod     (only the deploy role and a small on-call group)

What you see: An IAM policy meant to scope a developer to "anything with -dev in the name" is either too loose (matches more than intended) or too strict (breaks a legitimate case) — and nothing stops a typo from reaching the prod-named resource anyway.

Why: A naming convention is a string a human or a script can get wrong. An account boundary is enforced by AWS itself at the IAM and networking layer — it does not depend on every policy and script getting the naming pattern right, every time, forever.

What actually changes when you split accounts

One account for everything

  • +A dev IAM policy typo can touch a prod resource
  • +Runaway test traffic competes with prod for the same quotas
  • +One compromised credential is a blast radius over everything
  • +The bill is one number — cost by environment needs tagging discipline

One account per environment

  • IAM policies in dev cannot reach prod resources at all
  • Each environment has its own service quotas
  • A compromised dev credential stops at the dev account boundary
  • The bill is naturally split per environment, no tagging required
  • One account for everything
    • A dev IAM policy typo can touch a prod resource
    • Runaway test traffic competes with prod for the same quotas
    • One compromised credential is a blast radius over everything
    • The bill is one number — cost by environment needs tagging discipline
  • One account per environment
    • IAM policies in dev cannot reach prod resources at all
    • Each environment has its own service quotas
    • A compromised dev credential stops at the dev account boundary
    • The bill is naturally split per environment, no tagging required

Remember: An account is a real security boundary. Separating dev/staging/prod by account, not by naming convention, stops a mistake in one reaching another.

See also: environment separation · organizations and scps

AWS Organizations, OUs, and service control policies

standardintermediate

AWS Organizations groups accounts under one management account: it bills them together, folds accounts into organizational units (OUs), and can attach a service control policy (SCP) to cap what every account in an OU may do.

Think of it as

A franchise head office. Each store (member account) runs its own daily operations, but head office (the management account) sends one consolidated invoice and can impose company-wide rules (SCPs) that no individual store manager is allowed to override.

text
aws organizations create-organization
aws organizations create-organizational-unit --parent-id <root-id> --name Prod
aws organizations create-account --email prod@company.com --account-name payments-prod
aws organizations attach-policy --policy-id <scp-id> --target-id <ou-id>

Organizations vocabulary

Organizations vocabulary
TermWhat it is
Management accountThe account that created the organization; owns billing and can attach SCPs
Member accountAny other account joined to the organization
Organizational unit (OU)A folder-like grouping of accounts, can be nested
Consolidated billingOne invoice across every member account, often at better volume pricing
Service control policy (SCP)A permission ceiling attached to an account or OU
Account-level isolationEach account still has its own IAM, resources, and quotas

Together

text
Organization "acme-corp" (management account: 111111111111)
├─ OU "Security"      → 222222222222 (log-archive)
├─ OU "Workloads"
│   ├─ OU "Prod"       → 333333333333 (payments-prod)
│   └─ OU "NonProd"    → 444444444444 (payments-dev)
└─ SCP "deny-leave-org" attached at the ROOT — no account can leave the organization

Remember: An SCP is a ceiling, never a grant — it can only take permissions away from what IAM in the account would otherwise allow, never add any.

See also: why multiple accounts · account vending and governance

Environment separation: dev, staging, prod, security, shared services

coreintermediate

A mature AWS setup is a small, deliberate account set: one per lifecycle stage (dev, staging, prod), one dedicated to security/logging that nothing else can write to, and one shared-services account for things every other account needs.

Think of it as

Rooms in a building sorted by who is allowed to make noise. Dev is the workshop — mistakes are expected and contained. Prod is the vault — few people hold a key. Security/logging is the one room with a one-way door: things get written in, nothing gets edited out, not even by an admin of another account.

text
security-log-archive     ← every account ships CloudTrail/logs here, write-only
shared-services          ← container registry, shared VPC endpoints, tooling
payments-dev
payments-staging
payments-prod            ← smallest IAM footprint, most restricted

What we're doing: Show why the security/logging account is write-only, not just "another account."

log-flow.txttext
payments-prod    --CloudTrail-->  security-log-archive  (PutObject only, no Delete*)
payments-staging --CloudTrail-->  security-log-archive  (PutObject only, no Delete*)

# an attacker who compromises payments-prod's admin role still cannot
# delete or edit the trail of what they did, because that permission
# does not exist anywhere in payments-prod's own account
1
Logs are shipped OUT of prod into a separate account, not just written to a bucket inside prod itself.
4
Deletion permission for those logs was never granted to prod — it cannot be escalated to, because it does not exist there at all.

Why this works: If audit logs lived in the same account as the workload, a fully compromised admin role could edit or delete the evidence of what happened. Shipping logs to an account that has no delete permission for them removes that capability entirely — not by policy discipline, but because the permission is absent.

Creating one account per developer or per team instead of per environment

Wrong

text
alice-sandbox
bob-sandbox
carol-sandbox
payments-team-account   (dev + staging + prod all inside it)

Better

text
payments-dev       (shared by the whole team)
payments-staging
payments-prod

What you see: Dozens of personal sandbox accounts accumulate with no consistent guardrails, while the actual production workload still shares one account across every lifecycle stage — the opposite of where isolation was needed.

Why: Account boundaries are valuable where blast radius actually matters: between lifecycle stages of a real workload, and around security-sensitive data. Splitting by person multiplies management overhead without addressing that risk at all.

A minimal, deliberate account layout

security-log-archive

write-only from every other account

shared-services

registry, shared tooling, shared VPC endpoints

payments-dev

widest access, least trust

payments-staging

mirrors prod configuration

payments-prod

narrowest access, most trust

  1. security-log-archive — write-only from every other account
  2. shared-services — registry, shared tooling, shared VPC endpoints
  3. payments-dev — widest access, least trust
  4. payments-staging — mirrors prod configuration
  5. payments-prod — narrowest access, most trust

Remember: Separate by lifecycle stage and trust level — dev/staging/prod, plus write-only logging and shared-services — not by team or naming convention.

See also: why multiple accounts · account vending and governance

Cross-account IAM roles

standardintermediate

A cross-account role lives in one account and its trust policy names exactly which other account may assume it. A user or automation there can temporarily become the role — no long-lived credential ever crosses accounts.

Think of it as

A hotel key card that only works for one specific room and expires at checkout. You never get a permanent copy of the room's own key; you get a temporary card, scoped to exactly what you were allowed to enter, that stops working on its own.

text
# in the target account (prod), the role's trust policy:
{
  "Principal": { "AWS": "arn:aws:iam::222222222222:root" },  # the source account
  "Action": "sts:AssumeRole"
}

# from the source account (222222222222):
aws sts assume-role \
  --role-arn arn:aws:iam::333333333333:role/DeployRole \
  --role-session-name deploy-2026-08-20

Remember: Assuming a role issues short-lived, temporary credentials — nothing long-lived ever needs to be copied or stored across the account boundary.

See also: why multiple accounts · environment separation

Account vending, guardrails, and organization-wide governance

standardintermediate

Account vending stands up a new account through an automated process — SCPs, logging, and baseline IAM roles already attached — instead of clicking through creation by hand. Guardrails keep every vended account in policy automatically.

Think of it as

A hotel that hands every new guest a room already fitted with a smoke detector, a keyed lock, and a housekeeping log — set up once, at check-in, by policy — rather than sending an inspector to remodel every room after guests have already moved in.

text
New account request

Account vending (automated): create account, join Organization,
  attach baseline SCPs, enable CloudTrail → security-log-archive,
  create baseline IAM roles

Guardrails stay active: preventive (SCP blocks a disallowed action),
  detective (AWS Config flags a drift from policy)

Remember: A guardrail is preventive (an SCP blocks it) or detective (Config/CloudTrail flag it after) — a mature setup uses both, not one instead of the other.

See also: organizations and scps · environment separation

Advertisement