Field Note 34 current
A resource-free 'bouncer' account: the single gateway to customer resources
TL;DR
Route all access to customer resources — and anything reachable from the outside that can touch them — through roles in a single dedicated cloud account that holds no resources of its own: a “bouncer” account. Make it the only principal that customer-side trust allows in, and fence it both ways:
- Inbound: every customer-resource trust policy trusts only the bouncer role (conditioned with
SourceArn/SourceAccount/ExternalIdand owner-pinning, per ZFN-10). No other account or workload can reach customer resources directly. - Outbound: the bouncer role’s own policy explicitly denies acting on resources inside your org
(
aws:ResourceOrgIDIAM condition key: aws:ResourceOrgIDAn IAM policy condition key holding the AWS Organizations ID that owns the resource a request targets. Comparing it to your own organization's ID lets a policy allow or deny based on whether the target is inside or outside your org — the basis for a deny-inward rule.docs.aws.amazon.com ↗ equal to yours), with a tight allowlist for the few internal targets it genuinely needs. So even a tricked or compromised bouncer can only ever reach outward.
Because the account has no resources, there’s nothing in it to compromise, run code on, or pivot through — it’s a pure, audited identity hop. This collapses the confused-deputy surface to one narrow, governed gateway with a single audit trail.
Context
Any workload that holds credentials able to reach a customer’s resources is a confused-deputy risk (ZFN-10): attacker-influenced input can trick it into acting on the wrong customer, and a compromise of it inherits its standing access to customer data. If many of your workloads can assume into customer resources directly, the trust surface is enormous and impossible to reason about — every workload is a potential deputy, every trust policy is a thing to get right, and a foothold anywhere is a foothold toward customer data.
Cross-account AssumeRole is the right primitive; pointing it at customer resources from everywhere is
the wrong topology. You want the opposite: one place allowed to touch customer resources, controlled
and audited to the hilt, with the rest of your estate unable to reach customers except through it. The
cleanest way to express “one controlled principal” is a dedicated account whose only job is to hold that
principal — and to hold nothing else, so there’s nothing in it to attack.
Recommendation
Make one resource-free account the sole gateway to customer resources, fenced in both directions.
-
Customer-side trust names only the bouncer role. Every trust relationship granting access to a customer resource — the role you assume for a customer, the resource policy on a customer-touching resource — trusts only a role in the bouncer account, conditioned per ZFN-10. No other account or workload appears in that trust; adding one is a reviewed exception, not a local choice.
-
Workloads reach customers only through the bouncer. A workload that needs a customer resource assumes (or calls a narrow service that assumes) the bouncer role, which performs the access on its behalf. The bouncer is the one deputy: minimal, narrowly scoped, and its assumption tightly gated.
-
The account holds no resources — and enforce it. No compute, no data, no queues, no secrets beyond what the gateway role needs. A Service Control Policy denies creating resources, so “no resources” is structural, not a convention. An account with nothing in it has nothing to exfiltrate, nothing to run attacker code on, and nothing to pivot through.
-
Deny the bouncer from acting on your own org (defense in depth). Its job is to reach outside; it must never be usable against your own organization. The role’s identity policy carries an explicit deny on any operation whose target is inside your org (
aws:ResourceOrgIDIAM condition key: aws:ResourceOrgIDAn IAM policy condition key holding the AWS Organizations ID that owns the resource a request targets. Comparing it to your own organization's ID lets a policy allow or deny based on whether the target is inside or outside your org — the basis for a deny-inward rule.docs.aws.amazon.com ↗ equals yours):{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPutObjectAnywhere", "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::*/*" }, { "Sid": "DenyPutObjectInsideOurOrg", "Effect": "Deny", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::*/*", "Condition": { "StringEquals": { "aws:ResourceOrgID": "o-xxxxxxxxxx" } } } ] }Allow broadly to the outside, then deny whenever
aws:ResourceOrgIDis yours. (If the gateway genuinely needs a specific internal target, carve it out narrowly withNotResource— kept tight and documented — but default to no carve-out.) Because an explicitDenyalways wins, this holds even if a broader allow is added later — the inward block can’t be accidentally undone by a future grant. Apply the same allow-out / deny-inward shape to every action the role can perform, not justs3:PutObject. -
Assume the bouncer via federated identity, not static keys (ZFN-9), and keep applying the per-call owner-pinning of ZFN-10 at the bouncer→customer call — the gateway concentrates the deputy; it doesn’t excuse the per-call checks.
Consequences
Easier:
- The confused-deputy and cross-tenant blast radius collapses to one governed gateway: exactly one principal can reach customer resources, and one account to harden.
- Customer-side trust policies are simple and uniform (they trust one principal), and easier to audit.
- All customer-resource access flows through one account — a single audit trail where every access is attributable.
- A resource-free account has no blast radius of its own: compromising it grants only the conditioned, audited ability to make the gateway calls, not a foothold with data or compute.
- Fenced both ways, the bouncer can neither be reached from your estate nor turned back against it.
Harder:
- An extra hop: workloads chain through the bouncer instead of calling customer resources directly — latency, an assume-role step, and a shared library/service to do it well.
- The bouncer role and account become a critical control point — high-value, on the availability path for all customer access; its permissions and assumption conditions need tight governance and review.
- “No resources” and the deny-inward allowlist must be actively enforced and policed; the temptation to “just put one small thing here” or “just allow this one internal write” has to be refused, or the property erodes.
References
- ZFN-10 — the confused-deputy defense this account structurally concentrates; per-call owner-pinning and trust conditions still apply at the gateway.
- ZFN-9 — assume the bouncer role via federated identity, not static keys.
- ZFN-2 — security-first ordering that justifies the extra hop and the dedicated account.
aws:ResourceOrgIDIAM condition key: aws:ResourceOrgIDAn IAM policy condition key holding the AWS Organizations ID that owns the resource a request targets. Comparing it to your own organization's ID lets a policy allow or deny based on whether the target is inside or outside your org — the basis for a deny-inward rule.docs.aws.amazon.com ↗ and AWS Organizations / SCP multi-account guidance.
Changelog
- 2026-06-12: First published as a Field Note.