AWS Organizations SCPs
Service control policies that restrict without breaking things. Deny lists over allow lists, why an SCP grants nothing, and break-glass exemptions.
Service Control Policies
An SCP sets the maximum permissions available in an account. It is a ceiling, not a grant, and that distinction is the source of nearly every SCP problem.
Two rules to hold on to:
An SCP never grants anything. A principal's effective permissions are the intersection of the SCP and their IAM policy. Both must allow an action; either can deny it. An SCP permitting s3:* gives nobody S3 access. IAM still has to grant it.
SCPs never apply to the management account. No exceptions, which is why nothing should run there.
Allow lists and deny lists
Every OU and account starts with FullAWSAccess attached, an allow-everything policy. That is what makes a deny list possible: keep FullAWSAccess and attach policies that carve exceptions out of it.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyLeavingOrganization",
"Effect": "Deny",
"Action": ["organizations:LeaveOrganization"],
"Resource": "*"
},
{
"Sid": "DenyCloudTrailTampering",
"Effect": "Deny",
"Action": ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail",
"cloudtrail:UpdateTrail", "cloudtrail:PutEventSelectors"],
"Resource": "*"
},
{
"Sid": "DenyRootUser",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": { "StringLike": { "aws:PrincipalArn": "arn:aws:iam::*:root" } }
}
]
}
An allow list means removing FullAWSAccess and enumerating everything permitted. It is more restrictive and considerably more work. A service you forgot is simply unavailable, with an error that does not mention SCPs.
Use deny lists. Allow lists are defensible in a tightly regulated environment and will otherwise consume your time in a permanent game of catch-up.
aws organizations create-policy --name deny-dangerous-actions \
--type SERVICE_CONTROL_POLICY \
--content file://scp.json
aws organizations attach-policy --policy-id p-abc123 --target-id "$OU_ID"
Policies worth having
Restrict regions. Reduces attack surface and stops resources appearing where nobody is looking:
{
"Effect": "Deny",
"NotAction": [
"iam:*", "organizations:*", "route53:*", "cloudfront:*",
"support:*", "sts:*", "budgets:*", "waf:*", "wafv2:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": { "aws:RequestedRegion": ["eu-west-1", "eu-west-2"] }
}
}
The NotAction list is essential. Global services have endpoints in us-east-1, so a naive region restriction breaks IAM, Route 53, CloudFront and support cases, and the resulting errors give no clue that a region policy is responsible.
Protect security infrastructure:
{
"Effect": "Deny",
"Action": ["guardduty:Delete*", "guardduty:Disassociate*", "guardduty:Stop*",
"config:DeleteConfigurationRecorder", "config:StopConfigurationRecorder",
"securityhub:Disable*", "iam:DeleteRole"],
"Resource": "*",
"Condition": {
"ArnNotLike": { "aws:PrincipalArn": "arn:aws:iam::*:role/SecurityBreakGlass" }
}
}
Require encryption:
{
"Effect": "Deny",
"Action": "s3:PutObject",
"Resource": "*",
"Condition": { "Null": { "s3:x-amz-server-side-encryption": "true" } }
}
Prevent IAM user creation, pushing everyone to federated access with short-lived credentials:
{
"Effect": "Deny",
"Action": ["iam:CreateUser", "iam:CreateAccessKey", "iam:CreateLoginProfile"],
"Resource": "*",
"Condition": { "ArnNotLike": { "aws:PrincipalArn": "arn:aws:iam::*:role/PlatformAdmin" } }
}
Always exempt a break-glass role
Every restrictive SCP should exclude at least one role, or you can lock yourself out of your own account with no way back except AWS Support:
"Condition": {
"ArnNotLike": { "aws:PrincipalArn": "arn:aws:iam::*:role/OrgBreakGlass" }
}
That role should exist in every account, be assumable only from the management account, require MFA, and raise an alarm whenever it is used. The alarm is what keeps it from becoming an ordinary administrative path.
Note the deliberate use of ArnNotLike with aws:PrincipalArn rather than aws:PrincipalType. A role session ARN differs from the role ARN, and wildcards handle that.
Test before attaching broadly
An SCP mistake affects every account beneath its attachment point, and the errors it produces are not obviously SCP-related. The safe sequence:
One sandbox account first.
aws organizations attach-policy --policy-id p-abc123 --target-id 210987654321
Check what a principal can actually do:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::210987654321:role/AppRole \
--action-names s3:PutObject ec2:RunInstances \
--query 'EvaluationResults[].[EvalActionName,EvalDecision]' --output table
Look for the actions in use before denying them, so the policy reflects reality rather than assumption:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=StopLogging \
--start-time 2026-06-01
Then widen to a non-production OU, and only afterwards to production.
Diagnosing an SCP denial
The error does not say "an SCP blocked this", which makes this one of the harder AWS things to debug:
An error occurred (AccessDenied) when calling the CreateBucket operation:
User: arn:aws:sts::210987654321:assumed-role/AppRole/session is not authorized
to perform: s3:CreateBucket with an explicit deny in a service control policy
That trailing phrase, "with an explicit deny in a service control policy", is the one reliable indicator, and it is easy to miss at the end of a long message. Not every SCP denial includes it, though: an implicit denial under an allow-list SCP produces an ordinary access denied with no explanation at all.
aws organizations list-policies-for-target --target-id 210987654321 \
--filter SERVICE_CONTROL_POLICY
aws organizations describe-effective-policy --policy-type SERVICE_CONTROL_POLICY \
--target-id 210987654321
Work up the tree: policies attached to the account, its OU, parent OUs, and the root all combine, and a deny anywhere wins.
Resource control policies
RCPs are the newer counterpart and worth knowing about. An SCP limits what principals in your accounts can do; an RCP limits what can be done to resources in your accounts, including by principals outside the organisation.
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "EnforceOrgIdentities",
"Effect": "Deny",
"Principal": "*",
"Action": ["s3:*", "sts:AssumeRole"],
"Resource": "*",
"Condition": {
"StringNotEqualsIfExists": { "aws:PrincipalOrgID": "o-abc123" },
"Bool": { "aws:PrincipalIsAWSService": "false" }
}
}]
}
That prevents any external account accessing your S3 buckets or assuming your roles, regardless of what a resource policy in a member account says. It closes a gap SCPs cannot: an SCP cannot stop someone in a member account writing a bucket policy that grants access to an outside party.
Common errors
... is not authorized to perform: <action> with an explicit deny in a
service control policy
Found it. Work out which policy in the chain contains it.
ConstraintViolationException: You have exceeded the allowed number of
policies of type SERVICE_CONTROL_POLICY
Five SCPs per entity and a 5,120-byte limit per policy. Consolidate statements.
PolicyTypeNotEnabledException
SCPs must be enabled for the root first, and the organisation needs --feature-set ALL.
A policy appears to have no effect. The target is the management account, which is exempt, or an inherited Allow is not what you think. SCPs filter, they never grant.
Quick reference
| Task | Command |
|---|---|
| Enable SCPs | aws organizations enable-policy-type --policy-type SERVICE_CONTROL_POLICY |
| Create | aws organizations create-policy --type SERVICE_CONTROL_POLICY |
| Attach | aws organizations attach-policy --target-id <ou-or-account> |
| What applies here? | aws organizations list-policies-for-target |
| Effective policy | aws organizations describe-effective-policy |
| Test a principal | aws iam simulate-principal-policy |
| Detach | aws organizations detach-policy |
Key takeaways
- An SCP is a ceiling, not a grant. IAM must still allow the action.
- SCPs never apply to the management account.
- Use deny lists. Allow lists mean constant catch-up and opaque failures.
- Region restrictions need a
NotActionlist for global services, or IAM and Route 53 break. - Exempt a break-glass role from every restrictive policy, and alarm when it is used.
- Test on one sandbox account, then non-production, then production.
- "with an explicit deny in a service control policy" is the phrase to look for.
- Five policies per entity, 5,120 bytes each.
- RCPs restrict what outsiders can do to your resources: a gap SCPs cannot close.
Check your understanding
An SCP that granted nothing
A team attaches an SCP allowing s3:* and ec2:* to their OU, expecting developers to gain those permissions. Nobody can do anything new. Why?
A region restriction that broke IAM
An SCP denying all actions outside eu-west-1 is attached. Immediately, IAM role creation, Route 53 changes and support case creation all fail. What is wrong with the policy?
Frequently Asked Questions
Do SCPs grant permissions?
No, and assuming otherwise causes most SCP confusion. An SCP defines the maximum set of actions available in an account; actual permissions still come from IAM policies, and a principal can act only where both allow it. So attaching an SCP permitting s3:* gives nobody S3 access. Worse, replacing the default FullAWSAccess with a narrow allow list silently removes everything not enumerated, so a policy intended to grant can end up restricting far more than expected.
What is the difference between an allow list and a deny list SCP?
A deny list keeps FullAWSAccess attached and adds policies that deny specific actions, so everything remains permitted except what you have carved out. An allow list removes FullAWSAccess and enumerates every permitted action, blocking everything else. Deny lists are far easier to live with: a service you did not think of still works. Allow lists are more restrictive and appropriate in tightly regulated environments, but they turn every new service adoption into a policy change, and the failures give no hint of the cause.
Why doesn't my SCP affect the management account?
Because the management account is exempt from all SCPs by design. The reasoning is that it must always retain the ability to manage the organisation, including removing a policy that has locked every other account out. The consequence is that it has no guardrails at all, which is the reason to run no workloads there, keep no data in it, lock root away with hardware MFA, and delegate security services to a separate account.
How do I know whether an SCP is blocking my request?
Look for the phrase "with an explicit deny in a service control policy" at the end of the access denied message. It is the one reliable indicator and easy to miss. An implicit denial under an allow-list SCP produces no such hint, just an ordinary access denied. Use list-policies-for-target and describe-effective-policy for the account to see every applicable policy, remembering that policies attached to the account, its OU, parent OUs and the root all combine, and a deny anywhere wins.
How do I avoid locking myself out with an SCP?
Exempt a break-glass role from every restrictive statement using a condition on aws:PrincipalArn, and make sure that role exists in every account, is assumable only from the management account, requires MFA and raises an alarm when used. Test policies on a single sandbox account first, then a non-production OU, then production. And check CloudTrail for real usage of the actions you plan to deny. A restriction that looks obviously safe often turns out to block an automation nobody remembered.
What is the difference between an SCP and a resource control policy?
An SCP limits what principals in your accounts may do. An RCP limits what may be done to resources in your accounts, including by principals outside the organisation entirely. That closes a real gap: an SCP cannot stop someone in a member account writing a bucket policy granting access to an external AWS account, because the restriction applies to your principals rather than to the resource. An RCP denying access to anything without your aws:PrincipalOrgID blocks that regardless of what the resource policy says.