Least-privilege IAM work is mostly done at the resource boundary. Teams audit role policies, tighten S3 bucket policies, scope KMS key policies to specific principals. That work is real and valuable. But it operates on the wrong assumption: that the resource boundary is the primary enforcement surface.
The organization boundary — SCPs in AWS, Org Policies in Azure, Organization Policy constraints in GCP — is a coarser control but it sits higher in the evaluation hierarchy. An SCP applies before any IAM policy evaluation at the account level. You can have a perfectly scoped role policy and still expose your entire account to a class of action if your SCP doesn't constrain it. Conversely, even an overly permissive role policy can't grant access to actions your SCP denies.
Most teams know this in principle. In practice, they spend 90% of their IAM hardening effort at the resource boundary and treat the org boundary as an afterthought.
The IAM Policy Evaluation Order
IAM policy evaluation in AWS follows a specific order that determines which policies take precedence. The full evaluation logic is documented in the IAM documentation, but the relevant sequence for least-privilege enforcement is:
- Organizations SCPs — if the SCP denies an action, evaluation stops. Full deny, no override.
- Resource-based policies — apply to the resource regardless of who's calling (with some cross-account nuances)
- Identity-based policies — the caller's role/user policies
- Permission boundaries — if present, limit the maximum permissions the identity policy can grant
- Session policies — apply during sts:AssumeRole if passed
The SCP is not just "another layer" — it's a ceiling that no lower-level policy can exceed. If you're spending time tuning identity policies but your SCPs are broad or incomplete, you're working on a problem that lives inside a larger unfixed problem.
What the Org Boundary Controls That Resource Boundaries Don't
Resource boundaries control access to specific resources. The org boundary controls the action space that any principal in the account is allowed to attempt, regardless of the resource.
This distinction matters for a few classes of control that are inherently account-wide rather than resource-specific:
Service enablement control. If a service is available in a region and an account has no SCP restricting it, any principal with broad IAM permissions can create resources in that service. You can't express "no EC2 instances in eu-central-1" as a resource policy because there are no EC2 instances yet to attach a policy to. This is purely an org-boundary problem:
{
"Sid": "DenyEC2InUnauthorizedRegions",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "us-west-2"]
}
}
}
Privilege escalation paths. IAM privilege escalation happens when a principal can call an IAM action that grants themselves or another principal a higher level of access than they currently have. The classic example is iam:AttachRolePolicy with a broad resource scope — a principal with this permission can attach AdministratorAccess to any role, including their own. Blocking specific escalation paths at the resource level requires knowing all the possible paths and writing a policy for each. Blocking them at the org boundary is coarser but more complete:
{
"Sid": "DenyPrivilegeEscalation",
"Effect": "Deny",
"Action": [
"iam:CreatePolicyVersion",
"iam:SetDefaultPolicyVersion",
"iam:PutGroupPolicy",
"iam:PutRolePolicy",
"iam:PutUserPolicy",
"iam:AttachGroupPolicy",
"iam:AttachRolePolicy",
"iam:AttachUserPolicy"
],
"Resource": "*",
"Condition": {
"ArnNotLike": {
"aws:PrincipalArn": [
"arn:aws:iam::*:role/SecurityBreakGlass",
"arn:aws:iam::*:role/OrgSecurityAdmin"
]
}
}
}
Data exfiltration surface. Resource policies can restrict cross-account access. But an SCP can enforce that any S3 PutObject call into an external account is denied, regardless of whether the destination bucket has a policy that allows it. This prevents the "push data to attacker-controlled bucket" exfiltration vector that resource policies alone can't close:
{
"Sid": "DenyS3CrossAccountCopy",
"Effect": "Deny",
"Action": [
"s3:PutObject",
"s3:CopyObject"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {
"s3:ResourceAccount": "${aws:PrincipalAccount}"
}
}
}
The Coarseness Problem — and Why It's Often an Advantage
The typical objection to org-level controls is their coarseness. An SCP that blocks all IAM privilege escalation actions doesn't distinguish between a legitimate break-glass procedure and an attacker. An SCP that blocks inter-region operations doesn't distinguish between a backup job and an unauthorized export.
This is a real constraint, not a theoretical one. The response to it is exception management — carving out approved principals, conditions, and contexts where the broad deny doesn't apply. That's also real work.
But coarseness has an advantage that precision lacks: it's harder to misconfigure. A precise resource-level policy with 47 statements and 6 conditions is also a policy with 47 possible misconfigurations and 6 logical errors. A coarse org-boundary deny with 3 exceptions is harder to accidentally weaken. And when an auditor asks "what prevents privilege escalation in your AWS org," pointing to a single SCP with a clear exception list is more defensible than explaining a distributed set of role policies across 40 accounts.
We're not saying resource-level controls are wrong. We're saying they solve a different problem: scoping access for specific applications and workloads. The org boundary solves the problem of what any principal in the org is ever allowed to attempt. Both layers are necessary. The imbalance we see is teams spending significant effort on one and almost none on the other.
Permission Boundaries as the Bridge
Permission boundaries sit between the org boundary and the resource boundary in the evaluation hierarchy. They're per-identity constraints — a permission boundary attached to a role limits what that role can be granted, even if an identity policy allows it. They're the right tool for delegated access scenarios: "this team can create IAM roles, but those roles can never exceed these permissions."
The combination that works well at scale is:
- Org boundary: org-wide action space constraints (what nobody can do, period)
- Permission boundaries: team-level privilege ceiling (what this team's roles can be granted)
- Identity policies: workload-specific access (what this service role actually needs)
Permission boundaries are how you make org-level restrictions practical. Rather than a monolithic SCP that has to enumerate every exception across every team, you set the org boundary at the coarse level and use permission boundaries to carve out legitimate operational space per team. The SCP blocks the highest-risk actions unconditionally; the permission boundary limits what a specific team's automation can grant; the identity policy scopes the workload.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowApprovedServices",
"Effect": "Allow",
"Action": [
"s3:*",
"dynamodb:*",
"lambda:*",
"sqs:*",
"sns:*",
"logs:*"
],
"Resource": "*"
},
{
"Sid": "DenyIAMEscalation",
"Effect": "Deny",
"Action": [
"iam:CreateRole",
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"iam:PassRole"
],
"Resource": "*",
"Condition": {
"ArnNotLike": {
"iam:PassedToService": "lambda.amazonaws.com"
}
}
}
]
}
This boundary policy lets a team operate their application (S3, DynamoDB, Lambda, queues) and pass roles to Lambda functions, but they cannot create or modify IAM roles for anything other than Lambda. Combined with an org-level SCP that blocks roles without a permission boundary, this creates a structure where neither the team's automation nor a compromised credential can exceed what the security team has pre-approved.
Auditing the Org Boundary vs Auditing Individual Accounts
One practical argument for org-boundary controls over resource-boundary controls: audit scope. When you have 30 accounts, auditing each account's IAM policies for least-privilege compliance is a significant effort. Auditing the org boundary SCPs is an effort that scales with policy count, not account count.
For a SOC 2 or ISO 27001 audit, being able to say "these controls apply to all 30 accounts because they're set at the organization root" is simpler to evidence than "these controls apply to all 30 accounts because we've configured them consistently in each one." The first is a structure property. The second is a consistency claim that requires evidence from each account.
The realistic situation in most growing cloud environments: the org boundary is under-specified because it was set up early, by one person, with a narrow initial use case. New accounts were added, new OUs were created, but the SCP structure didn't keep up. The resource-level IAM work continued because it was closer to the day-to-day application work. The gap between what the SCPs cover and what the actual threat model requires has grown silently.
Closing that gap is a discrete task, not a continuous process. It requires mapping your current SCP coverage against your threat model, identifying the classes of action that aren't covered at the org level, and writing the missing constraints. That's the work the resource-boundary approach doesn't substitute for, no matter how carefully done.