In a multi-account AWS environment, governance means controlling what can happen and where. AWS gives you two complementary mechanisms for this: Service Control Policies (SCPs) and Permission Boundaries. They both restrict effective permissions, but they operate at completely different levels — and the gap between them is where most IAM mistakes happen.
What SCPs actually do
A Service Control Policy is an AWS Organizations policy that sets the maximum permissions available to every IAM principal in an account — or an entire OU. It applies regardless of what any individual IAM policy says. Even an account root user is subject to an SCP.
The most important thing to understand about SCPs: they do not grant any permissions on their own. They only restrict. If no SCP is attached, the default is a full allow on everything. Once you attach an SCP, you've drawn a ceiling that nothing in that account can exceed.
An SCP with a Deny statement overrides any Allow in an identity-based or resource-based policy. An SCP with only Allow statements acts as a whitelist — anything not explicitly listed is implicitly denied. In practice, most landing zone architectures use a combination: a baseline allow-all SCP to maintain current behavior, then targeted deny SCPs to block specific high-risk actions.
Typical use cases for SCPs:
- Prevent anyone from disabling CloudTrail or turning off GuardDuty — even the account's own administrators
- Restrict which AWS regions workloads can be deployed to (e.g., only eu-central-1 and eu-west-1 for GDPR scope)
- Block the purchase of Reserved Instances or Savings Plans from individual accounts — centralizing purchasing in a management or billing account
- Prevent the creation of IAM users in workload accounts, enforcing SSO-only access
- Require a specific tag to be present on all EC2 and RDS resources before they can be created
What Permission Boundaries actually do
A Permission Boundary is an IAM policy you attach to a specific IAM user or role to set the maximum permissions that principal can be granted. It doesn't grant anything by itself — it just defines the outer limit of what the principal's identity-based policies can do.
The key distinction: Permission Boundaries operate on individual principals, not on accounts. An IAM role without a permission boundary can receive any permissions its attached policies grant. With a permission boundary set, it can only use the intersection of what its identity policies allow and what the boundary permits.
The primary use case is safe delegation. If you're allowing developers to create IAM roles for their Lambda functions or EKS service accounts, you need a way to ensure they can't create roles with more permissions than they themselves have. Without a permission boundary, a developer with iam:CreateRole can create a role with AdministratorAccess and use it to escalate privileges. With a permission boundary enforced on any role they create, that escalation path is closed.
Typical use cases for Permission Boundaries:
- Letting a DevOps team manage IAM roles for their own workloads without being able to grant themselves or others administrator access
- Limiting CI/CD pipeline roles so they can deploy infrastructure but cannot modify IAM policies outside a defined scope
- Scoping what an automated account vending process can do when it runs in the context of a cross-account role
- Enabling application teams to create service accounts for their microservices without requiring a central IAM admin for every request
The effective permissions model
Understanding effective permissions requires thinking about all three layers together. For any API call to succeed, it must be allowed by all of the following that are applicable:
The ceiling for everything in the account. A Deny here overrides everything else.
What the IAM user or role is explicitly allowed to do via attached policies or inline policies.
Caps what the identity policy can grant. The effective permission is the intersection of layers 2 and 3.
For cross-account access, the resource policy must also allow the principal. For same-account access, the identity policy alone is usually sufficient.
An optional further restriction passed at role assumption time, e.g., via AWS STS AssumeRole. Rarely used directly but important in automated pipelines.
The effective permission for any given action is the intersection of all the layers that apply. One explicit Deny anywhere kills the request. No Allow in any applicable layer kills the request just as surely.
The common mistake: using SCPs for principal-level control
Teams new to multi-account governance often try to use SCPs to solve problems that Permission Boundaries are designed for. The mistake looks like this: a developer team is allowed to create IAM roles, and the security team wants to prevent them from granting excessive permissions. Someone writes an SCP that blocks creation of roles with AdministratorAccess. This works — until the team finds a different set of overly broad policies that aren't explicitly blocked, and now you're playing whack-a-mole with increasingly complex SCP conditions.
The correct solution is a Permission Boundary. You define what the maximum useful permission set looks like for developer-created roles, attach that as a required boundary in an IAM condition, and enforce it with an SCP that prevents creating roles without the boundary. Now the developer team can create as many roles as they need — they just can't make any of them more powerful than the boundary you defined. It's a one-time decision, not an ongoing blocklist.
The correct pattern: combine them
SCPs and Permission Boundaries are designed to work together, not replace each other. The pattern that holds up in enterprise environments:
- Use SCPs for account-wide invariants — things that should be true for every principal in an account without exception. Region restrictions, CloudTrail protection, prohibited services. These are your governance guardrails and belong at the Organizations level.
- Use Permission Boundaries for delegation control — when you need to let teams manage their own IAM resources without introducing privilege escalation risk. Define the maximum scope, enforce it as a required boundary via an SCP condition, and then get out of the way.
- Enforce the boundary requirement with an SCP — add an SCP condition like
aws:RequestedRegionpattern: if a principal tries to create an IAM role without attaching a specific permission boundary, the SCP denies the action. This closes the gap between policy design and actual behavior.
A note on IAM policy evaluation in cross-account scenarios
One area where this gets more complex: cross-account role assumptions. When role A in account 1 assumes role B in account 2, both the SCPs in account 1 and account 2 apply to the session. The SCP in account 1 constrains what the calling principal can request. The SCP in account 2 constrains what the assumed role can do once the session is established. Both permission boundaries — if set — apply to their respective principals. In practice, this means cross-account automation roles need to be validated against both accounts' policy stacks, not just the target account.
Quick reference
Use for: region restrictions, prohibited services, CloudTrail/GuardDuty protection, IAM user creation blocks, cost guardrails. Attach to OUs for broad coverage or to individual accounts for exceptions.
Use for: developer-created roles and users, CI/CD pipeline roles, automated vending processes. Combine with an SCP that requires the boundary to actually be attached.
If you're setting up governance in a new landing zone, put SCPs in place first — they establish the non-negotiable floor of security and compliance. Then layer Permission Boundaries to enable safe self-service for the teams operating inside that foundation. The combination gives you enforcement at the account level and flexibility at the principal level, which is the structure enterprise environments actually need.