Field Note 55current
On AWS, the account is the unit of isolation
IAM inside one account is access control; the account boundary is isolation. Quotas, billing, credential scope, and blast radius are all account-shaped, and cross-account access fails closed. One workload per account, guardrailed by SCPs and RCPs — plumbing friction is the point.
TL;DR
Within a single AWS account, IAM is access control. Between accounts, it’s isolation. Those are different things, and you need both. A policy bug inside an account fails open — some principal can reach some resource you never intended, found later by an auditor or an attacker. A missing grant across an account boundary fails closed — the access simply doesn’t exist until both sides say so.
So partition by account, aggressively:
- One workload per account — a service (or small cohesive family of them) × environment. Production and non-production never share an account, for anything.
- Baseline accounts with one job each: management (touched rarely, by almost nobody), log archive (write-only from everywhere else), security tooling, and a bouncer for external trustField Note · currentZFN-34 — A resource-free 'bouncer' account: the single gateway to customer resourcesFunnel access to customer resources through one dedicated account that holds no resources. Customer trust names only its role; the role is denied from your own org (aws:ResourceOrgID). Fenced both ways, it shrinks the confused-deputy surface to one audited gateway.Why it's cited here: The bouncer-account pattern is this note applied to third-party access: a whole account whose only job is to be the fenced place where customer trust terminates.Open ZFN-34 →.
- Organization guardrails that member accounts can’t lift: SCPsService control policies (AWS Organizations)Organization-level policies that set the maximum available permissions for every principal in an account, including its root user. The mechanism that turns an account from a default-open container into one with organization-enforced guardrails no local admin can lift.docs.aws.amazon.com ↗ capping what principals may do, RCPsResource control policies (AWS Organizations)The resource-side twin of SCPs: organization-level policies that cap what any principal — including external ones — can do with resources in your accounts. The primitive that makes 'only our organization can touch this bucket' a fleet-wide invariant rather than a per-bucket policy.docs.aws.amazon.com ↗ capping what may be done to resources, deny-by-default on regions and services you don’t use.
- An account factory, so a new account is a vending-machine operation with the baseline stamped in — not a quarterly ceremony.
If creating an account feels too heavyweight to do per workload, fix the factory, not the architecture.
Context
Everything that actually limits damage on AWS is account-shaped:
- Credential scope. A leaked credential, an SSRF’d instance role
(ZFN-11Field Note · currentZFN-11 — Route outbound HTTP through an isolated egress proxyApplication compute shouldn't make arbitrary outbound HTTP — it's an SSRF pivot to internal services and the cloud metadata endpoint. Route all egress through a proxy (SOCKS, or a gRPC egress service) on isolated compute with no route inward. The proxy's network is the boundary.Open ZFN-11 →), a compromised CI runner — the theft is bounded
by what that account can reach. In a shared account, “what it can reach” is whatever IAM
gymnastics were supposed to prevent, and IAM at scale accumulates the same sediment as any
other policy language: wildcard ARNs from a deadline, a
*in a resource field nobody revisited, tag-based conditions half-applied. In a small single-workload account you can allow broadly and still sleep, because the account is the fence. - Service quotas. Quotas are per-account per-region. A load test in a shared account eats the API rate limits, ENI counts, and instance capacity that production needed — self-inflicted denial of service with no attacker involved. Separate accounts make the noisy neighbour structurally impossible (ZFN-18Field Note · currentZFN-18 — Enforce a quota at ingress on every endpoint — even unabused onesPut a quota on every endpoint and enforce it at ingress from day one — per tenant, principal, IP — even for endpoints nobody abuses yet. Unlimited-by-default means the first runaway client or compromised key is an outage. Return 429 + Retry-After; retrofitting limits is painful.Open ZFN-18 → is the same instinct, applied to your own tenants).
- Blast radius of change. Terraform/OpenTofu state, deletion of “unused” resources, a misdirected cleanup script — the worst case is the account, not the company.
- Billing and attribution. Cost by account is exact and free; cost by tag is a forensic reconstruction that’s never quite right.
- Forensics and containment. When an account is compromised, you can freeze it — snapshot, detach, quarantine with an SCP — without touching production’s twin. Try that when both live in one account.
AWS says this themselves, in the multi-account whitepaperOrganizing Your AWS Environment Using Multiple Accounts (AWS whitepaper)AWS's own guidance on multi-account architecture: accounts as the boundary for security, billing, and service quotas, organised into an Organization with organizational units, and the recommended baseline accounts (management, log archive, security tooling) that every environment should start with.docs.aws.amazon.com ↗: the account is the boundary they build to. Fighting it — one big account, isolation by IAM condition keys and prayer — means hand-building, in the world’s most detail-hostile policy language, a property the platform would have given you for free.
Recommendation
Make the account the default answer to “how do we separate these?” — and make accounts cheap enough that the default is painless.
-
One workload × one environment = one account. A “workload” is a service or a small family that deploys and fails together. When in doubt, split — the cost of a boundary you didn’t need is some cross-account plumbing; the cost of a boundary you needed and don’t have is a shared fate you can’t untangle.
-
Never share prod with anything. Not staging (“it’s basically prod”), not tooling, not the one convenient Lambda. Every prod incident story that starts in a non-prod system walked through a shared account or a shared credential.
-
Stamp the baseline from a factory. New accounts arrive with the org’s guardrails, the log-archive trail already flowing, break-glass access configured, federation wired (ZFN-9Field Note · currentZFN-9 — No long-lived cloud keys; workloads authenticate by federated identityNo static AWS or GCP keys anywhere — not in code, secret stores, or env. Workloads use their runtime's own identity and cross clouds by exchanging it (OIDC) for short-lived credentials via federation. Static keys are a documented carve-out only.Why it's cited here: Federated, short-lived credentials are what make many accounts liveable — role assumption is the same motion whether you have three accounts or three hundred.Open ZFN-9 →), and no root credentials in circulation. If this takes more than minutes, invest there first — factory friction is why teams share accounts.
-
Guardrails live above the account. SCPsService control policies (AWS Organizations)Organization-level policies that set the maximum available permissions for every principal in an account, including its root user. The mechanism that turns an account from a default-open container into one with organization-enforced guardrails no local admin can lift.docs.aws.amazon.com ↗ for “no principal here may leave these regions / touch these services / disable the audit trail”; RCPsResource control policies (AWS Organizations)The resource-side twin of SCPs: organization-level policies that cap what any principal — including external ones — can do with resources in your accounts. The primitive that makes 'only our organization can touch this bucket' a fleet-wide invariant rather than a per-bucket policy.docs.aws.amazon.com ↗ for “no resource here may be touched from outside the organization” — which closes, fleet-wide, the confused-deputy paths that per-resource policies close one bucket at a time (ZFN-10Field Note · currentZFN-10 — Pin the expected owner on cross-account resource calls (confused-deputy defense)Authority to call a resource isn't proof it's the one you meant. Any call crossing an account boundary must assert the expected owner: ExpectedBucketOwner on S3, aws:ResourceAccount conditions, validation of untrusted ARNs, plus inbound trust pinned with SourceArn/ExternalId.Why it's cited here: Once everything is cross-account, every call needs the expected owner pinned — account isolation and confused-deputy defence are two halves of one design.Open ZFN-10 →, ZFN-34Field Note · currentZFN-34 — A resource-free 'bouncer' account: the single gateway to customer resourcesFunnel access to customer resources through one dedicated account that holds no resources. Customer trust names only its role; the role is denied from your own org (aws:ResourceOrgID). Fenced both ways, it shrinks the confused-deputy surface to one audited gateway.Why it's cited here: The bouncer-account pattern is this note applied to third-party access: a whole account whose only job is to be the fenced place where customer trust terminates.Open ZFN-34 →). The point of enforcing at the organization is that a compromised account admin can’t lift the rules from inside.
-
Cross-account access is explicit, and that’s the feature. Every legitimate flow between workloads becomes a named role with a named trust policy — an inventory of your real internal trust graph that you can read, review, and revoke. Inside one account that graph exists too; it’s just invisible.
-
The extreme tier: account-per-tenant. For customers whose regulatory or contractual isolation needs outgrow row-level tenancy (ZFN-15Field Note · currentZFN-15 — Partition customer data by tenant from day oneMake customer data tenant-partitioned from day one: tenant-scope every query, never join across tenants, route through a tenant→location directory. Run one physical database at first — but keep the model shardable. Retrofitting isolation onto a shared DB is brutal.Open ZFN-15 →), the account is the honest version of “dedicated environment” — and if your factory is real, it’s an attainable SKU rather than a bespoke project.
Consequences
Easier:
- IAM policies get simpler and safer simultaneously. Broad grants within a small account beat surgical grants within a huge one — less policy code, fewer places to be wrong.
- Compromise containment is a boundary you already drew. Freeze the account, keep the fleet.
- Cost, quotas, and ownership become facts (the account says so) rather than reconstructions (the tags suggest so).
- Deleting things becomes possible. Decommissioning a workload is closing an account — a clean, total operation — instead of an archaeology project in a shared one.
Harder:
- Everything is cross-account now. Role hops, resource shares, peering/PrivateLink where networks must meet, endpoint policies. The plumbing is real work, and it’s on every path that used to be a same-account convenience.
- You must invest in the factory and the org layer — account vending, SCP/RCP hygiene, centralised identity — before the pattern pays. Half-adopted, you get sprawl without guardrails, which is worse than one well-run shared account.
- Some limits are organizational, not per-account — org-level API rates, policy size and attachment caps — and hundreds of accounts will find them.
- Local development gets a hop harder: engineers hold federated access to many accounts, and tooling has to make “which account am I in?” impossible to get wrong (ZFN-40Field Note · currentZFN-40 — No anonymous "system" actorIf "system" appears as an actor in your audit log, attribution is already broken. Every automated action — cron job, cleanup task, migration, agent — runs as a named identity with its own credentials and scope, so "who did this?" has an answer and revocation is surgical.Open ZFN-40 → applies to humans too).
New obligations:
- The account inventory is now a security document. Every account has an owner, a purpose, and a lifecycle; an unowned account is an unwatched credential scope.
- The trust graph needs review. Cross-account roles are legible — so read them, periodically, and delete the stale ones.
References
- ZFN-42Conviction · currentZFN-42 — My one cloud is AWSApplying the one-cloud principle (ZFN-32), my pick is AWS — 100%, a league of its own. Go native: skip Kubernetes, use ECS; lean into SQS, SNS, Kinesis, IAM, ALB, RDS. The one exception to native — provision with OpenTofu, not CloudFormation, now that LLMs write .tf so well.Why it's cited here: The conviction this note builds on: all-in on AWS. Going all-in is what makes it worth learning the one isolation primitive AWS itself treats as hard.Open ZFN-42 → — the all-in-on-AWS conviction that makes deep use of the account primitive rational rather than lock-in anxiety.
- ZFN-34Field Note · currentZFN-34 — A resource-free 'bouncer' account: the single gateway to customer resourcesFunnel access to customer resources through one dedicated account that holds no resources. Customer trust names only its role; the role is denied from your own org (aws:ResourceOrgID). Fenced both ways, it shrinks the confused-deputy surface to one audited gateway.Why it's cited here: The bouncer-account pattern is this note applied to third-party access: a whole account whose only job is to be the fenced place where customer trust terminates.Open ZFN-34 → — a whole account whose only job is to terminate external trust: this note’s pattern at its sharpest.
- ZFN-9Field Note · currentZFN-9 — No long-lived cloud keys; workloads authenticate by federated identityNo static AWS or GCP keys anywhere — not in code, secret stores, or env. Workloads use their runtime's own identity and cross clouds by exchanging it (OIDC) for short-lived credentials via federation. Static keys are a documented carve-out only.Why it's cited here: Federated, short-lived credentials are what make many accounts liveable — role assumption is the same motion whether you have three accounts or three hundred.Open ZFN-9 → — federation and short-lived credentials, which make a many-account world navigable without a key under every doormat.
- ZFN-10Field Note · currentZFN-10 — Pin the expected owner on cross-account resource calls (confused-deputy defense)Authority to call a resource isn't proof it's the one you meant. Any call crossing an account boundary must assert the expected owner: ExpectedBucketOwner on S3, aws:ResourceAccount conditions, validation of untrusted ARNs, plus inbound trust pinned with SourceArn/ExternalId.Why it's cited here: Once everything is cross-account, every call needs the expected owner pinned — account isolation and confused-deputy defence are two halves of one design.Open ZFN-10 → — pinning the expected owner on cross-account calls; mandatory once account boundaries are your architecture.
- ZFN-15Field Note · currentZFN-15 — Partition customer data by tenant from day oneMake customer data tenant-partitioned from day one: tenant-scope every query, never join across tenants, route through a tenant→location directory. Run one physical database at first — but keep the model shardable. Retrofitting isolation onto a shared DB is brutal.Open ZFN-15 → — the same partition-first instinct one level down, inside the database.
- AWS multi-account whitepaperOrganizing Your AWS Environment Using Multiple Accounts (AWS whitepaper)AWS's own guidance on multi-account architecture: accounts as the boundary for security, billing, and service quotas, organised into an Organization with organizational units, and the recommended baseline accounts (management, log archive, security tooling) that every environment should start with.docs.aws.amazon.com ↗, SCPsService control policies (AWS Organizations)Organization-level policies that set the maximum available permissions for every principal in an account, including its root user. The mechanism that turns an account from a default-open container into one with organization-enforced guardrails no local admin can lift.docs.aws.amazon.com ↗, RCPsResource control policies (AWS Organizations)The resource-side twin of SCPs: organization-level policies that cap what any principal — including external ones — can do with resources in your accounts. The primitive that makes 'only our organization can touch this bucket' a fleet-wide invariant rather than a per-bucket policy.docs.aws.amazon.com ↗ — the platform’s own statement that the account is the boundary, and the two policy layers that enforce it from above.
Changelog
- 2026-08-12: First published as a Field Note.