Skip to main content

Service control policies

An SCP defines the maximum permissions available to accounts in an organisational unit. It does not grant anything; it removes the ability to do things that no account in that OU should ever do.

How SCPs work​

  • They apply to member accounts, not the management account.
  • They are evaluated before identity policies. An action allowed by IAM is still denied if an SCP denies it.
  • An account must have at least one Allow. The default FullAWSAccess policy provides it; custom policies that list allowed services replace it where a stricter posture is wanted.
  • Deny statements are the primary tool. Allow lists are used where the approved service set is genuinely narrow.

Baseline policies​

These are applied to every workload OU unless a documented exception exists.

Protect the audit trail​

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDisableCloudTrail",
"Effect": "Deny",
"Action": [
"cloudtrail:StopLogging",
"cloudtrail:DeleteTrail",
"cloudtrail:UpdateTrail"
],
"Resource": "*"
},
{
"Sid": "DenyDisableConfig",
"Effect": "Deny",
"Action": [
"config:StopConfigurationRecorder",
"config:DeleteConfigurationRecorder",
"config:DeleteDeliveryChannel"
],
"Resource": "*"
},
{
"Sid": "DenyDisableGuardDuty",
"Effect": "Deny",
"Action": [
"guardduty:DeleteDetector",
"guardduty:UpdateDetector",
"guardduty:DisassociateFromMasterAccount"
],
"Resource": "*"
}
]
}

Deny root user​

{
"Version": "2012-10-17",
"Statement": {
"Sid": "DenyRootUser",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringLike": {"aws:PrincipalArn": "arn:aws:iam::*:root"}
}
}
}

The exception path for root is a break-glass procedure with an approved change and an alarm.

Require IMDSv2​

{
"Version": "2012-10-17",
"Statement": {
"Sid": "RequireImdsV2",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {"NumericLessThan": {"ec2:RoleDelivery": "2.0"}}
}
}

IMDSv1 allows a server-side request forgery to retrieve instance credentials. Requiring v2 removes that path.

Deny long-lived credentials​

{
"Version": "2012-10-17",
"Statement": {
"Sid": "DenyIamUserAndKeyCreation",
"Effect": "Deny",
"Action": ["iam:CreateAccessKey", "iam:CreateUser"],
"Resource": "*"
}
}

Region enforcement​

{
"Version": "2012-10-17",
"Statement": {
"Sid": "RestrictRegion",
"Effect": "Deny",
"NotAction": [
"a4b:*", "budgets:*", "ce:*", "cloudfront:*", "cur:*", "health:*",
"iam:*", "organizations:*", "route53:*", "route53domains:*",
"shield:*", "support:*", "trustedadvisor:*", "waf:*", "wellarchitected:*"
],
"Resource": "*",
"Condition": {
"StringNotEquals": {"aws:RequestedRegion": ["us-east-1", "ap-south-1"]}
}
}
}

Global services are excluded through NotAction. The approved region list is set per customer.

Block public access​

{
"Version": "2012-10-17",
"Statement": {
"Sid": "DenyPublicS3",
"Effect": "Deny",
"Action": [
"s3:PutBucketPublicAccessBlock",
"s3:PutBucketAcl",
"s3:PutAccountPublicAccessBlock"
],
"Resource": "*",
"Condition": {
"StringEquals": {"s3:x-amz-acl": ["public-read", "public-read-write"]}
}
}
}

Paired with account-level S3 Block Public Access, which is the stronger control.

Protect network boundaries​

Deny changes that would make a private VPC internet-accessible, and deny deletion of VPC Flow Logs:

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyFlowLogDeletion",
"Effect": "Deny",
"Action": [
"ec2:DeleteFlowLogs",
"logs:DeleteLogGroup",
"logs:DeleteLogStream"
],
"Resource": "*"
},
{
"Sid": "DenyIgwAttach",
"Effect": "Deny",
"Action": ["ec2:AttachInternetGateway"],
"Resource": "*"
}
]
}

Require tags​

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRunInstanceWithoutProjectTag",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": ["arn:aws:ec2:*:*:instance/*", "arn:aws:ec2:*:*:volume/*"],
"Condition": {"Null": {"aws:RequestTag/Project": "true"}}
}
]
}

Extend across the resource types that carry cost: RDS, ECS, Lambda, S3, DynamoDB, Secrets Manager.

Deny leaving the organisation​

{
"Version": "2012-10-17",
"Statement": {
"Sid": "DenyLeaveOrganization",
"Effect": "Deny",
"Action": "organizations:LeaveOrganization",
"Resource": "*"
}
}

Protect critical roles​

{
"Version": "2012-10-17",
"Statement": {
"Sid": "DenyCriticalRoleChanges",
"Effect": "Deny",
"Action": [
"iam:AttachRolePolicy", "iam:DeleteRole", "iam:DeleteRolePolicy",
"iam:DetachRolePolicy", "iam:PutRolePolicy", "iam:UpdateAssumeRolePolicy"
],
"Resource": "arn:aws:iam::*:role/OrganizationAccountAccessRole",
"Condition": {
"StringNotLike": {"aws:PrincipalARN": "arn:aws:iam::*:role/platform-admin"}
}
}
}

Quarantine​

For a suspected-compromised account, apply:

{
"Version": "2012-10-17",
"Statement": {"Sid": "Quarantine", "Effect": "Deny", "Action": "*", "Resource": "*"}
}

This stops all activity in the account while preserving the evidence and the ability for the security account to investigate.

Policy set by OU​

PolicySandboxDevStageProdSecurity
Deny root user✓✓✓✓✓
Protect audit trail✓✓✓✓✓
Deny leaving organisation✓✓✓✓✓
Require IMDSv2✓✓✓✓
Deny long-lived credentials✓✓✓✓
Region restriction✓✓✓
Block public access✓✓✓✓
Require tags✓✓✓
Protect network boundaries✓✓✓
Quarantine (on demand)✓✓✓✓✓

Operating rules​

  1. Test in sandbox first. SCPs fail closed and can break a running workload.
  2. Version and review. Policies live in a repository and are reviewed like code.
  3. Document every exception. An exception is time-bound and reviewed at expiry.
  4. Monitor denials. CloudTrail records AccessDenied events caused by SCPs; review them for signals of attempted misuse as well as for accidental friction.
  5. Never attach an SCP that removes all allows. An OU with no allow statement cannot be used, including by the people who need to fix it.