I spent hours debugging why engineers in one OU could create EC2 instances, but engineers in a child OU couldn’t—despite both having identical IAM policies. The problem wasn’t IAM policy; it was a Service Control Policy (SCP) on the parent OU that was blocking the action at the organizational level. In this post, I’ll walk through exactly how SCPs inherit and how to fix unexpected permission denials.
The Problem
You’ve attached a Service Control Policy to an OU, but permissions aren’t behaving as expected:
- An SCP on a parent OU is blocking actions in a child OU that you want to allow
- Permissions seem to disappear when you move an account to a different OU
- Adding an Allow SCP to a child OU doesn’t grant permissions that a parent OU denies
- You attached
FullAWSAccessto your organization root, then detached it, and now nothing works
Why Does This Happen?
Service Control Policies use intersection logic, not union logic. The effective permission for any account is the intersection of all SCPs in the hierarchy from root → OU → account. An Allow in a child OU means nothing if a parent OU or root has a Deny or a missing Allow.
Think of it like stacked filters: if any filter says “no,” the request is blocked.
| Level | SCP | Effect |
|---|---|---|
| Root | FullAWSAccess (Allow all) |
Baseline permission |
| Parent OU | DenyS3 (Deny s3:*) |
Blocks S3 everywhere below |
| Child OU | AllowS3ReadOnly (Allow s3:GetObject) |
Doesn’t override parent deny |
| Result | Intersection | S3 blocked (deny wins) |
The Fix
Step 1: Check the Effective Policy for an Account
To see which SCPs actually apply to an account, use the describe-effective-policy command:
# Get the effective SCP for a specific account
aws organizations describe-effective-policy \
--policy-type SERVICE_CONTROL_POLICY \
--target-id 123456789012 \
--query 'EffectivePolicy.Content' \
--output text | jq .
This returns the merged SCP (intersection of all SCPs in the hierarchy). If an action isn’t in this merged policy, it’s denied.
Step 2: List All SCPs in the Hierarchy
Identify which SCPs are attached at each level:
# List SCPs attached to the root
aws organizations list-policies-for-target \
--target-id r-0123456789abcdef0 \
--filter SERVICE_CONTROL_POLICY \
--query 'Policies[*].{Name:Name,Id:Id,Type:Type}' \
--output table
# List SCPs attached to a specific OU
aws organizations list-policies-for-target \
--target-id ou-0123-xxxxxxxx \
--filter SERVICE_CONTROL_POLICY \
--query 'Policies[*].{Name:Name,Id:Id}' \
--output table
# Get the content of a specific SCP
aws organizations describe-policy \
--policy-id p-0123456789abcdef0 \
--query 'Policy.Content' \
--output text | jq .
Step 3: Understand the Root-OU Hierarchy
Build a mental map of your OU structure and which SCPs apply at each level:
# List all OUs
aws organizations list-organizational-units-for-parent \
--parent-id r-0123456789abcdef0 \
--query 'OrganizationalUnits[*].{Name:Name,Id:Id}' \
--output table
# For each OU, list attached SCPs
aws organizations list-policies-for-target \
--target-id ou-xxxx-yyyyyyyy \
--filter SERVICE_CONTROL_POLICY
Step 4: Ensure FullAWSAccess is Attached Somewhere
Every account must have a path from root → OU → account where at least one SCP in the chain allows the action. If no FullAWSAccess or equivalent Allow SCP exists, all actions are blocked.
Always ensure FullAWSAccess (or a broad Allow SCP) is attached to the root or a parent OU:
# Check if FullAWSAccess is attached to root
aws organizations list-policies-for-target \
--target-id r-0123456789abcdef0 \
--filter SERVICE_CONTROL_POLICY \
--query 'Policies[?Name==`FullAWSAccess`]'
If missing, attach it:
# Get the policy ID for FullAWSAccess (AWS managed)
aws organizations list-policies \
--filter SERVICE_CONTROL_POLICY \
--query 'Policies[?Name==`FullAWSAccess`].Id' \
--output text
# Attach it to root
aws organizations attach-policy \
--policy-id p-FullAWSAccess \
--target-id r-0123456789abcdef0
Step 5: Use IAM Access Analyzer to Test Permissions
To test whether a specific action is allowed, use IAM Access Analyzer or the IAM policy simulator:
# Simulate an action for a principal in an account
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/MyRole \
--action-names ec2:RunInstances s3:GetObject \
--resource-arns arn:aws:ec2:*:123456789012:* arn:aws:s3:::my-bucket/*
This shows whether the action would be allowed, denied by SCP, or denied by IAM policy.
How to Run This
- For the account in question, run
describe-effective-policyto see the merged SCP - Trace the OU hierarchy from root to the account and list SCPs at each level
- Verify
FullAWSAccessis attached to the root - Check if a parent OU SCP is denying the action (look for explicit Deny statements)
- If the parent Deny is too broad, refine it or remove it
- Use IAM Access Analyzer to simulate the action and confirm it’s now allowed
Is This Safe?
Yes, these are read-only queries. Attaching policies requires explicit action and is safe if you understand the implications (test in a development OU first).
Key Takeaway
SCPs use intersection logic—the effective policy is the intersection of all SCPs in the root-OU hierarchy. Always ensure FullAWSAccess is attached somewhere in the chain, and trace the hierarchy to find where a Deny SCP might be blocking your intended actions.