Skip to content

SCPs

General Info

Definition
  • AWS Service Control Policies (SCPs) are a way of restricting the actions that can be taken in an AWS account so that all IAM users and roles, and even the root user, cannot perform them
  • This feature is part of AWS Organizations, and the SCPs are controlled by the Organization Management (Master) account
Restrictions
  • SCPs cannot restrict the Management account of the Organization
    • This is a primary reason why it is best practice not to use the Organization Master account for anything other than Organization activities
  • SCPs cannot restrict principals outside of the Organization
    • If an S3 bucket is public, an SCP will not be able to stop Internet users from accessing it
  • SCPs are similar to IAM boundaries, in that they define the maximum set of actions that can be allowed, but do not actually grant any privileges.
    • The default SCP is Allow * on *, but this doesn't mean that anyone in the accounts can do anything
    • It only means that the SCP is not further restricting them
  • The Member accounts of the Organization are unable to see the SCPs that have been applied to them
    • Further, when actions are denied, there is no way to know whether that is due to an IAM policy, an SCP, or something else

Exceptions

Condition Key Description
aws:PrincipalAccount Account level exceptions
aws:PrincipalOrgPaths Environment/OU level exceptions
aws:PrincipalArn User/role level exceptions

Sample SCPs

Deny ability to leave Organization
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Action": "organizations:LeaveOrganization",
        "Resource": "*"
    }
}
Deny API calls from root users
{
  "Version": "2012-10-17",
  "Statement": {
    "Sid": "DenyRootUser",
    "Effect": "Deny",
    "Action": "*",
    "Resource": "*",
    "Condition": {
      "StringLike": { "aws:PrincipalArn": "arn:aws:iam::*:root" }
    }
  }
}
Require MFA to perform an API action
{
    "Version": "2012-10-17",
    "Statement": [
        {
        "Sid": "DenyStopAndTerminateWhenMFAIsNotPresent",
        "Effect": "Deny",
        "Action": [
            "ec2:StopInstances",
            "ec2:TerminateInstances"
        ],
        "Resource": "*",
        "Condition": {"BoolIfExists": {"aws:MultiFactorAuthPresent": false}}
        }
    ]
}
Deny ability to disrupt CloudTrail
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Action": [
            "cloudtrail:DeleteTrail",
            "cloudtrail:StopLogging",
            "cloudtrail:PutEventSelectors",
            "cloudtrail:UpdateTrail"
        ],
        "Resource": ["arn:aws:cloudtrail:*:*:trail/exampletrail"]
    }
}
Deny ability to disrupt GuardDuty
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Action": [
            "guardduty:DeleteDetector",
            "guardduty:DisassociateFromMasterAccount",
            "guardduty:UpdateDetector",
            "guardduty:CreateFilter",
            "guardduty:CreateIPSet"
        ],
        "Resource": "*"
    }
}

Prevent also modifying its configuration:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Action": [
                "guardduty:AcceptInvitation",
                "guardduty:ArchiveFindings",
                "guardduty:CreateDetector",
                "guardduty:CreateFilter",
                "guardduty:CreateIPSet",
                "guardduty:CreateMembers",
                "guardduty:CreatePublishingDestination",
                "guardduty:CreateSampleFindings",
                "guardduty:CreateThreatIntelSet",
                "guardduty:DeclineInvitations",
                "guardduty:DeleteDetector",
                "guardduty:DeleteFilter",
                "guardduty:DeleteInvitations",
                "guardduty:DeleteIPSet",
                "guardduty:DeleteMembers",
                "guardduty:DeletePublishingDestination",
                "guardduty:DeleteThreatIntelSet",
                "guardduty:DisassociateFromMasterAccount",
                "guardduty:DisassociateMembers",
                "guardduty:InviteMembers",
                "guardduty:StartMonitoringMembers",
                "guardduty:StopMonitoringMembers",
                "guardduty:TagResource",
                "guardduty:UnarchiveFindings",
                "guardduty:UntagResource",
                "guardduty:UpdateDetector",
                "guardduty:UpdateFilter",
                "guardduty:UpdateFindingsFeedback",
                "guardduty:UpdateIPSet",
                "guardduty:UpdatePublishingDestination",
                "guardduty:UpdateThreatIntelSet"
            ],
            "Resource": "*"
        }
    ]
}

Deny ability to disable Config or changing its rules
{
    "Version": "2012-10-17",
    "Statement": [
        {
        "Effect": "Deny",
        "Action": [
            "config:DeleteConfigRule",
            "config:DeleteConfigurationRecorder",
            "config:DeleteDeliveryChannel",
            "config:StopConfigurationRecorder"
        ],
        "Resource": "*"
        }
    ]
}
Deny ability to disrupt CloudWatch Event collection
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Action": [
            "events:DeleteRule",
            "events:DisableRule",
            "events:RemoveTargets"
        ],
        "Resource": "arn:aws:events:*:*:rule/default/NAME"
    }
}

Prevent also altering its configuration:

{
    "Version": "2012-10-17",
    "Statement": [
        {
        "Effect": "Deny",
        "Action": [
            "cloudwatch:DeleteAlarms",
            "cloudwatch:DeleteDashboards",
            "cloudwatch:DisableAlarmActions",
            "cloudwatch:PutDashboard",
            "cloudwatch:PutMetricAlarm",
            "cloudwatch:SetAlarmState"
        ],
        "Resource": "*"
        }
    ]
}

Deny ability to modify an important IAM role
  • Such as the ones used by Security teams:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyAccessToASpecificRole",
      "Effect": "Deny",
      "Action": [
        "iam:AttachRolePolicy",
        "iam:DeleteRole",
        "iam:DeleteRolePermissionsBoundary",
        "iam:DeleteRolePolicy",
        "iam:DetachRolePolicy",
        "iam:PutRolePermissionsBoundary",
        "iam:PutRolePolicy",
        "iam:UpdateAssumeRolePolicy",
        "iam:UpdateRole",
        "iam:UpdateRoleDescription"
      ],
      "Resource": [
        "arn:aws:iam::*:role/NAME"
      ]
    }
  ]
}
Prevent deletion or modification of IAM roles created by the Information Security team
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PreventInfoSecRoleActions",
      "Effect": "Deny",
      "Action": ["*"],
      "Resource": ["arn:aws:iam::*:role/InfoSec*"],
      "Condition": {
          "StringNotLike": {"aws:PrincipalARN": "arn:aws:iam::*:role/InfoSec*"}
      }
    }
  ]
}
Prevent creation of IAM users with a login profile
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PreventIAMUserWithLoginprofile",
      "Effect": "Deny",
      "Action": [
          "iam:ChangePassword",
          "iam:CreateLoginProfile",
          "iam:UpdateLoginProfile",
          "iam:UpdateAccountPasswordPolicy"
      ],
      "Resource": ["*"]
    }
  ]
}
Deny ability to create IAM access keys
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Action": ["iam:CreateAccessKey"],
        "Resource": "*"
    }
}
Deny ability to create IAM access keys for the root user
{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Deny",
        "Action": "iam:CreateAccessKey",
        "Resource": ["*"],
        "Condition": {
          "StringLike": { "aws:PrincipalArn": "arn:aws:iam::*:root" }
        }
    }
}
Require the use of IMDSv2
  • Require role credentials for an EC2 to have been retrieved using the IMDSv2:
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "RequireAllEc2RolesToUseV2",
                "Effect": "Deny",
                "Action": "*",
                "Resource": "*",
                "Condition": {
                    "NumericLessThan": {
                        "ec2:RoleDelivery": "2.0"
                    }
                }
            }
        ]
    }
    
  • Ensure that EC2s can only be created if you enforce that they use IMDSv2:
    {
        "Version": "2012-10-17",
        "Statement": {
            "Sid": "RequireImdsV2",
            "Effect": "Deny",
            "Action": "ec2:RunInstances",
            "Resource": "arn:aws:ec2:*:*:instance/*",
            "Condition": {
                "StringNotEquals": {
                    "ec2:MetadataHttpTokens": "required"
                }
            }
        }
    }
    
  • Deny option to change type of metadata:
    {
        "Version": "2012-10-17",
        "Statement": {
            "Effect": "Deny",
            "Action": "ec2:ModifyInstanceMetadataOptions",
            "Resource": "*"
        }
    }
    
  • Ensure the hop count that can be set is restricted:
    {
        "Version": "2012-10-17",
        "Statement": {
            "Sid": "MaxImdsHopLimit",
            "Effect": "Deny",
            "Action": "ec2:RunInstances",
            "Resource": "arn:aws:ec2:*:*:instance/*",
            "Condition": {
                "NumericGreaterThan": {"ec2:MetadataHttpPutResponseHopLimit": "1"}
            }
        }
    }
    
Prevent public S3 access account-wide
  • Prevents modifying S3 public access block settings account-wide:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PreventS3PublicAccess",
      "Action": ["s3:PutAccountPublicAccessBlock"],
      "Resource": "*",
      "Effect": "Deny"
    }
  ]
}
Prevent deleting Amazon VPC flow logs
{
    "Version": "2012-10-17",
    "Statement": [
        {
        "Effect": "Deny",
        "Action": [
            "ec2:DeleteFlowLogs",
            "logs:DeleteLogGroup",
            "logs:DeleteLogStream"
        ],
        "Resource": "*"
        }
    ]
}
Deny ability to make a VPC accessible from the Internet that isn't already
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "ec2:AttachInternetGateway",
        "ec2:CreateInternetGateway",
        "ec2:CreateEgressOnlyInternetGateway",
        "ec2:CreateVpcPeeringConnection",
        "ec2:AcceptVpcPeeringConnection",
        "globalaccelerator:Create*",
        "globalaccelerator:Update*"
      ],
      "Resource": "*"
    }
  ]
}
Allow only approved services
{
  "Version": "2012-10-17",
  "Statement": {
    "Sid": "AllowList",
    "Effect": "Allow",
    "Action": ["a4b:*","access-analyzer:*","account:*","acm:*","acm-pca:*","amplify:*","apigateway:*","application-autoscaling:*","applicationinsights:*","appmesh:*","appmesh-preview:*","appstream:*","appsync:*","arsenal:*","artifact:*","athena:*","autoscaling:*","autoscaling-plans:*","aws-marketplace:*","aws-marketplace-management:*","aws-portal:*","backup:*","backup-storage:*","batch:*","budgets:*","cassandra:*","ce:*","chatbot:*","chime:*","cloud9:*","clouddirectory:*","cloudformation:*","cloudfront:*","cloudhsm:*","cloudsearch:*","cloudtrail:*","cloudwatch:*","codebuild:*","codecommit:*","codedeploy:*","codeguru-profiler:*","codeguru-reviewer:*","codepipeline:*","codestar:*","codestar-notifications:*","cognito-identity:*","cognito-idp:*","cognito-sync:*","comprehend:*","comprehendmedical:*","compute-optimizer:*","config:*","connect:*","cur:*","dataexchange:*","datapipeline:*","datasync:*","dax:*","dbqms:*","deeplens:*","deepracer:*","detective:*","devicefarm:*","directconnect:*","discovery:*","dlm:*","dms:*","ds:*","dynamodb:*","ebs:*","ec2:*","ec2-instance-connect:*","ec2messages:*","ecr:*","ecs:*","eks:*","elastic-inference:*","elasticache:*","elasticbeanstalk:*","elasticfilesystem:*","elasticloadbalancing:*","elasticmapreduce:*","elastictranscoder:*","es:*","events:*","execute-api:*","firehose:*","fms:*","forecast:*","frauddetector:*","freertos:*","fsx:*","gamelift:*","glacier:*","globalaccelerator:*","glue:*","greengrass:*","groundstation:*","groundtruthlabeling:*","guardduty:*","health:*","iam:*","imagebuilder:*","importexport:*","inspector:*","iot:*","iot-device-tester:*","iot1click:*","iotanalytics:*","iotevents:*","iotsitewise:*","iotthingsgraph:*","kafka:*","kendra:*","kinesis:*","kinesisanalytics:*","kinesisvideo:*","kms:*","lakeformation:*","lambda:*","launchwizard:*","lex:*","license-manager:*","lightsail:*","logs:*","machinelearning:*","macie:*","managedblockchain:*","mechanicalturk:*","mediaconnect:*","mediaconvert:*","medialive:*","mediapackage:*","mediapackage-vod:*","mediastore:*","mediatailor:*","mgh:*","mobileanalytics:*","mobilehub:*","mobiletargeting:*","mq:*","neptune-db:*","networkmanager:*","opsworks:*","opsworks-cm:*","organizations:*","outposts:*","personalize:*","pi:*","polly:*","pricing:*","qldb:*","quicksight:*","ram:*","rds:*","rds-data:*","rds-db:*","redshift:*","rekognition:*","resource-groups:*","robomaker:*","route53:*","route53domains:*","route53resolver:*","s3:*","sagemaker:*","savingsplans:*","schemas:*","sdb:*","secretsmanager:*","securityhub:*","serverlessrepo:*","servicecatalog:*","servicediscovery:*","servicequotas:*","ses:*","shield:*","signer:*","sms:*","sms-voice:*","snowball:*","sns:*","sqs:*","ssm:*","ssmmessages:*","sso:*","sso-directory:*","states:*","storagegateway:*","sts:*","sumerian:*","support:*","swf:*","synthetics:*","tag:*","textract:*","transcribe:*","transfer:*","translate:*","trustedadvisor:*","waf:*","waf-regional:*","wafv2:*","wam:*","wellarchitected:*","workdocs:*","worklink:*","workmail:*","workmailmessageflow:*","workspaces:*","xray:*"],
    "Resource": "*"
  }
}
Region enforcement
  • Enforce that only eu-west-1 can be used, along with the global services:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "RestrictRegion",
            "Effect": "Deny",
            "NotAction": [
                "a4b:*",
                "budgets:*",
                "ce:*",
                "chime:*",
                "cloudfront:*",
                "cur:*",
                "globalaccelerator:*",
                "health:*",
                "iam:*",
                "importexport:*",
                "mobileanalytics:*",
                "organizations:*",
                "route53:*",
                "route53domains:*",
                "shield:*",
                "support:*",
                "trustedadvisor:*",
                "waf:*",
                "wellarchitected:*"
            ],
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "aws:RequestedRegion": [
                        "eu-west-1"
                    ]
                }
            }
        }
    ]
}

Additional Samples

Other collections of SCPs:

Terraform Sample

The following sample defines an SCP which denies accounts the ability to leave the Organization, and attaches it to an OU named prod (aws_organizations_organizational_unit.prod.id):

resource "aws_organizations_policy" "deny_leave_org" {
  name = "Deny ability to leave Organization"
  type = "SERVICE_CONTROL_POLICY"

  content = <<CONTENT
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Action": "organizations:LeaveOrganization",
            "Resource": "*"
        }
    ]
}
CONTENT
}

resource "aws_organizations_policy_attachment" "deny_leave_org_ou_prod" {
  policy_id = aws_organizations_policy.deny_leave_org.id
  target_id = aws_organizations_organizational_unit.prod.id
}

Tooling

Tool Description
aws-allowlister Automatically compile an AWS Service Control Policy that ONLY allows AWS services that are compliant with your preferred compliance frameworks
Back to top