AWS Service Control Policies (SCPs) are a powerful tool for governing permissions across your AWS organization, but their actual impact often surprises people because they deny permissions, rather than grant them.

Imagine you have a central AWS account where you manage your organization and a few member accounts for development, testing, and production. You want to ensure that no one in the development account can accidentally launch EC2 instances with public IP addresses, a common security misconfiguration.

Here’s a simplified SCP that accomplishes this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyPublicIPs",
      "Effect": "Deny",
      "Action": [
        "ec2:AssociateAddress"
      ],
      "Resource": "*",
      "Condition": {
        "Bool": {
          "ec2:AssociatePublicIpAddress": "true"
        }
      }
    }
  ]
}

This SCP, when attached to the development organizational unit (OU) or the development account itself, will prevent any IAM principal (user or role) within that account from associating a public IP address with an EC2 instance. Notice the Effect: "Deny". This is key. SCPs are deny-by-default policies applied at the OU or account level. If an IAM policy allows an action, but an SCP denies it, the action is denied.

Let’s break down how this works and what you can control.

The Problem SCPs Solve

SCPs address a critical gap in IAM. IAM policies grant permissions to principals on resources. This is great for fine-grained access control within an account. However, there’s no mechanism within IAM itself to prevent a powerful IAM user in a member account from doing something broadly unsafe, like enabling public access to S3 buckets, launching expensive instances in unapproved regions, or deleting critical resources. SCPs provide guardrails at the organizational level, ensuring that even administrators within a member account cannot violate your organization’s security and compliance requirements.

How SCPs Work Internally

When a principal attempts an action in AWS, the permissions evaluation process is a bit like a multi-layered defense. First, AWS checks the IAM policies attached to the principal (identity-based policies). If any of those policies explicitly deny the action, it’s blocked immediately. If not, AWS then checks if any IAM policies allow the action. If there’s at least one "Allow" and no "Deny," the action proceeds.

This is where SCPs come in. SCPs are attached to AWS accounts or OUs. They are evaluated after identity-based policies but before resource-based policies. Crucially, SCPs can only deny actions. They cannot grant permissions. If an SCP attached to the account or OU where the action is being attempted denies the action, the action is blocked, regardless of what the IAM policies allow.

Levers You Control

  1. Target: SCPs can be attached to the root of your organization, individual OUs, or specific AWS accounts. Attaching to the root is the broadest; attaching to an OU or account is more targeted.
  2. Actions: Just like IAM policies, SCPs specify Action elements. You can deny specific API calls (e.g., ec2:RunInstances, s3:PutBucketPublicAccess) or use wildcards to deny broad categories of actions.
  3. Resources: While SCPs can specify Resource elements, they are often less useful because SCPs are evaluated before the specific resource is known in many cases. For actions like ec2:RunInstances, you can’t specify a particular EC2 instance ARN in the SCP. However, for actions that do target specific resources (like s3:PutBucketPolicy), you can use ARNs.
  4. Conditions: This is where SCPs become incredibly powerful. You can use condition keys to deny actions based on various factors, such as:
    • aws:RequestedRegion: Deny actions in specific AWS regions.
    • ec2:AssociatePublicIpAddress: As shown in the example, control public IP association.
    • s3:DataAccessPointArn: Deny access to specific S3 Access Points.
    • aws:UserAgent: Deny actions initiated by specific client applications.

Putting it into Practice: A Production Account Example

Let’s say you want to prevent any new S3 buckets in your production account from being made public.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyPublicS3Buckets",
      "Effect": "Deny",
      "Action": [
        "s3:PutBucketAcl",
        "s3:PutBucketPolicy"
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ],
      "Condition": {
        "StringEquals": {
          "s3:x-amz-acl": "public-read",
          "s3:x-amz-grant-read": "*"
        }
      }
    },
    {
      "Sid": "DenyPublicReadS3Buckets",
      "Effect": "Deny",
      "Action": "s3:PutBucketPublicAccess",
      "Resource": "arn:aws:s3:::*",
      "Condition": {
        "StringEquals": {
          "s3:PublicAccessBlockState": "DISABLING_ALL_PUBLIC_ACCESS"
        }
      }
    }
  ]
}

This SCP has two statements. The first denies setting ACLs to public-read or explicitly granting public read access via a bucket policy. The second denies the s3:PutBucketPublicAccess action if it would result in disabling all public access blocks, which is how you explicitly open up buckets. This SCP, attached to your production OU, ensures that even if an administrator in the production account has IAM permissions to make buckets public, the SCP will block it.

The one thing most people don’t realize is how the evaluation order of all policies (identity, SCP, resource, permissions boundaries) combines. An action is allowed only if it’s explicitly allowed by an identity-based policy and not denied by any applicable SCP, permissions boundary, or resource-based policy. Because SCPs are evaluated for all principals within an account, they act as a crucial organizational control plane.

The next challenge you’ll face is understanding how to strategically layer SCPs across different OUs to enforce a tiered security posture.

Want structured learning?

Take the full Aws course →