The mechanisms
| Mechanism | Acts on | Typical use |
|---|---|---|
| SCP (service control policy) | IAM principals in member accounts | Deny regions, protect security services, block root, protect KMS keys |
| RCP (resource control policy) | Resources in member accounts, whoever calls them | Data perimeter: block access from outside the organisation to S3, KMS, Secrets Manager, STS, SQS (supported services) |
| Declarative policies | Service configuration across accounts | Enforce EC2 settings such as IMDSv2 defaults, block public AMI/snapshot sharing (supported services) |
| Proactive controls | Deployments (CloudFormation Hooks via Control Tower) | Stop non-compliant resources before creation |
| Detective controls | Existing resources (Config rules, Security Hub) | Find and report drift; trigger remediation |
None of them grant permissions. SCPs and RCPs only limit the maximum.
Map objectives to mechanisms
| Objective | Best fit |
|---|---|
| Only approved regions | SCP (with exceptions for global services) |
| Nobody disables CloudTrail, GuardDuty, Config | SCP |
| Only principals from our org can read our buckets | RCP (aws:PrincipalOrgID condition) |
| Our identities only access our resources | SCP with aws:ResourceOrgID (identity perimeter) |
| IMDSv2 required everywhere | Declarative policy + SCP on ec2:MetadataHttpTokens |
| No public S3 buckets | Account-level Block Public Access + SCP protecting the setting |
| Encryption required on new EBS volumes | EBS encryption by default + proactive/detective control |
| Tagging standards | Tag policies + proactive/detective controls |
SCP example: protect security tooling
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "ProtectSecurityServices",
"Effect": "Deny",
"Action": ["cloudtrail:StopLogging", "cloudtrail:DeleteTrail", "guardduty:DeleteDetector",
"guardduty:DisassociateFromMasterAccount", "config:StopConfigurationRecorder",
"config:DeleteConfigurationRecorder", "securityhub:DisableSecurityHub"],
"Resource": "*",
"Condition": {"ArnNotLike": {"aws:PrincipalArn": "arn:aws:iam::*:role/org-security-admin"}}
}]
}
RCP example: data perimeter for S3
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "EnforceOrgIdentities",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"StringNotEqualsIfExists": {"aws:PrincipalOrgID": "o-abc123xyz"},
"BoolIfExists": {"aws:PrincipalIsAWSService": "false"}
}
}]
}
Data perimeter policies need careful exceptions (AWS service principals, partner integrations, public datasets). Start from AWS's published data perimeter examples and test in a sandbox OU.
Roll out safely
- Write the control, its objective and the exception process.
- Test in a sandbox OU with representative workloads.
- Detective version first in production OUs (Config rule) to find what would break.
- Fix or exempt, then attach the preventive policy OU by OU.
- Monitor CloudTrail for
AccessDeniedwith the explicit-deny-in-SCP/RCP context after each attachment.
Exceptions
- Prefer conditions on tagged roles (
aws:PrincipalTag/exception = ...) or dedicated exception OUs over editing the policy per request. - Record owner, reason and expiry; review monthly.
Manage as code
Policies in Git, reviewed by security/platform, deployed by pipeline (Control Tower customisations, Terraform, CloudFormation StackSets). Note the policy size and count limits per target. Consolidate statements.
Warning
A broad Deny attached at the root can lock out every account, including the management account's ability to fix things (SCPs don't apply to the management account, but RCPs' and declarative policies' effects need checking). Test in a sandbox OU first and keep a documented break-glass path.
Checklist
- Objectives listed and mapped to SCP/RCP/declarative/proactive/detective
- Policies tested in a sandbox OU
- Detective-first rollout with impact review
- Exception process with expiry
- Policies as code with review
- AccessDenied monitoring after each change