Skip to content

Restricting Security Group Modifications in Production Using AWS Service Control Policies (SCP)

6 minute read
Content level: Intermediate
0

This article demonstrates a proof of concept for implementing enterprise security governance to prevent unauthorized security group modifications while maintaining development team flexibility using AWS Service Control Policies (SCP)

Problem Statement

Security teams face a critical challenge: preventing unauthorized modifications to hardened security groups in production environments while allowing development teams operational flexibility. Organizations struggle with unauthorized production changes where developers accidentally modify production security groups, compliance requirements for maintaining audit trails and separation of duties, operational bottlenecks from manual approval processes, and the need for scalable governance across multiple AWS accounts. Traditional approaches either create operational bottlenecks or fail to provide adequate governance controls.

Solution Architecture

Organizational Control Structure

The solution implements hierarchical governance using AWS Organizations with Service Control Policies (SCPs):

Organization Root (SCP Applied)
├── Production OU
│   ├── Prod Account 1 → Security Groups (env=prod) 🔒
│   └── Prod Account 2 → Security Groups (env=prod) 🔒
└── Development OU
    ├── Dev Account 1 → Security Groups (env=dev) ✅
    └── Dev Account 2 → Security Groups (env=dev) ✅

Key Insight: Since the SCP uses tag-based conditions (ec2:ResourceTag/env = "prod"), it can be safely applied at the Organization Root level. Development workloads remain unrestricted because they use env=dev tags, which don't match the SCP conditions.

SCP Control Mechanism

The Service Control Policy uses tag-based restrictions with role-based exceptions to control security group modifications:

{
  "Sid": "RestrictSecurityGroupModificationInProduction",
  "Effect": "Deny",
  "Action": [
    "ec2:AuthorizeSecurityGroupIngress",
    "ec2:AuthorizeSecurityGroupEgress",
    "ec2:RevokeSecurityGroupIngress",
    "ec2:RevokeSecurityGroupEgress",
    "ec2:ModifySecurityGroupRules",
    "ec2:UpdateSecurityGroupRuleDescriptionsIngress",
    "ec2:UpdateSecurityGroupRuleDescriptionsEgress"
  ],
  "Resource": "*",
  "Condition": {
    "StringNotLike": {
          "aws:PrincipalArn": "*/AWSReservedSSO_Security-Team_*"
    },
    "StringEquals": {
      "ec2:ResourceTag/env": "prod"
    }
  }
}

Key Features

  • Tag-Based Control: Only affects resources with env=prod tag
  • Role-Based Exception: Security team bypasses restrictions via Identity Center role pattern
  • Environment Isolation: Development environments remain unrestricted
  • Focused Control: Blocks security group rule modifications in production
  • Secure Pattern Matching: Uses specific ARN patterns to prevent bypass attempts

Implementation Guide

Step 1: Configure Identity Center Permission Sets

Create permission sets in AWS Identity Center:

First, create the required permission sets that will be used for testing. The SCP will use the permission set names to control access.

Security Team Permission Set:

  1. Go to AWS Identity Center console
  2. Navigate to Permission sets
  3. Click Create permission set
  4. Name: Security-Team
  5. Add policies: EC2FullAccess
  6. Assign to security team users

Developer Team Permission Set:

  1. Create another permission set
  2. Name: Developer-Team
  3. Add policies: EC2LimitedAccess
  4. Assign to developer users

Important: Note the exact permission set names as they will be used in the SCP policy.

Step 2: Create Service Control Policy

Execute these commands in the AWS Organizations management account:

The management account is the root account of your AWS Organization that has permissions to create and manage Service Control Policies. You must have administrative access to this account to create organizational policies.

# Save the SCP policy from Appendix A as scp-policy-secure.json first

# Create the SCP in the management account
aws organizations create-policy \
  --name "RestrictSecurityGroupModification" \
  --description "Restrict security group modifications in production environments" \
  --type SERVICE_CONTROL_POLICY \
  --content file://scp-policy-secure.json

# Get the policy ID from the create command output
POLICY_ID=$(aws organizations list-policies \
  --filter SERVICE_CONTROL_POLICY \
  --query 'Policies[?Name==`RestrictSecurityGroupModification`].Id' \
  --output text)

echo "Policy ID: $POLICY_ID"

# Attach SCP to your test/sandbox account (replace with your account ID)
aws organizations attach-policy \
  --policy-id $POLICY_ID \
  --target-id 123456789012  # Replace with your test account ID

Step 3: Deploy Test Infrastructure

Execute these commands in your test/sandbox account:

Switch to the test/sandbox account where you attached the SCP in Step 2. This account will be subject to the SCP restrictions, allowing you to validate the policy effectiveness. Ensure you have appropriate permissions to create CloudFormation stacks and EC2 resources in this test account.

# Save the CloudFormation template from Appendix B as minimal-infrastructure.yaml first

# Deploy single stack with both prod and dev security groups
aws cloudformation create-stack \
    --stack-name SecurityGroupControl-PoC-Minimal \
    --template-body file://minimal-infrastructure.yaml

# Wait for completion
aws cloudformation wait stack-create-complete --stack-name SecurityGroupControl-PoC-Minimal

Testing and Validation

Core Test Scenarios

The solution validates three critical scenarios:

  1. Developer → Dev SG → ✅ SUCCESS (no restrictions)
  2. Developer → Prod SG → ❌ FAILED (SCP blocks)
  3. Security Team → Prod SG → ✅ SUCCESS (SCP allows)

Manual Testing Commands

A. Test with Developer Credentials:

# Get security group IDs from CloudFormation outputs
PROD_SG=$(aws cloudformation describe-stacks \
  --stack-name SecurityGroupControl-PoC-Minimal \
  --query 'Stacks[0].Outputs[?OutputKey==`ProdSecurityGroupId`].OutputValue' \
  --output text)
echo "Prod SG ID: $PROD_SG"

DEV_SG=$(aws cloudformation describe-stacks \
  --stack-name SecurityGroupControl-PoC-Minimal \
  --query 'Stacks[0].Outputs[?OutputKey==`DevSecurityGroupId`].OutputValue' \
  --output text)
echo "Dev SG ID: $DEV_SG"

# Test development security group (should succeed)
aws ec2 authorize-security-group-ingress \
  --group-id $DEV_SG \
  --protocol tcp \
  --port 8080 \
  --cidr 10.0.0.0/8

# Test production security group (should fail - SCP blocks)
aws ec2 authorize-security-group-ingress \
  --group-id $PROD_SG \
  --protocol tcp \
  --port 8080 \
  --cidr 10.0.0.0/8

SCP Block

Non Security Team cannot modify Production SG

B. Test with Security Team Credentials:

# Test production security group (should succeed - SCP allows)
aws ec2 authorize-security-group-ingress \
  --group-id $PROD_SG \
  --protocol tcp \
  --port 8080 \
  --cidr 10.0.0.0/8

# Clean up test rules
aws ec2 revoke-security-group-ingress \
  --group-id $PROD_SG \
  --protocol tcp \
  --port 8080 \
  --cidr 10.0.0.0/8

SCP Allow Security Team can modify Production SG

Infrastructure Templates

Appendix A: SCP Policy

Secure Service Control Policy (scp-policy-secure.json):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "RestrictSecurityGroupModificationInProduction",
      "Effect": "Deny",
      "Action": [
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:AuthorizeSecurityGroupEgress",
        "ec2:RevokeSecurityGroupIngress",
        "ec2:RevokeSecurityGroupEgress",
        "ec2:ModifySecurityGroupRules",
        "ec2:UpdateSecurityGroupRuleDescriptionsIngress",
        "ec2:UpdateSecurityGroupRuleDescriptionsEgress"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotLike": {
          "aws:PrincipalArn": "*/AWSReservedSSO_Security-Team_*"
        },
        "StringEquals": {
          "ec2:ResourceTag/env": "prod"
        }
      }
    }
  ]
}

Appendix B: Minimal Infrastructure Template

CloudFormation Template (minimal-infrastructure.yaml) for Step 3:

The minimal template provides cost-effective testing with only essential resources:

AWSTemplateFormatVersion: '2010-09-09'
Description: 'Security Group Control PoC - Minimal Infrastructure for SCP Testing'

Resources:
  # VPC (minimal)
  TestVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: '10.0.0.0/16'
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: 'SecurityGroupControl-Test-VPC'

  # Production Security Group (env=prod) - SCP will restrict rule modifications
  ProdSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: 'SecurityGroupControl-Prod-SG'
      GroupDescription: 'Production Security Group for SCP testing (empty rules)'
      VpcId: !Ref TestVPC
      Tags:
        - Key: Name
          Value: 'SecurityGroupControl-Prod-SG'
        - Key: env
          Value: 'prod'

  # Development Security Group (env=dev) - No SCP restrictions
  DevSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: 'SecurityGroupControl-Dev-SG'
      GroupDescription: 'Development Security Group for SCP testing (empty rules)'
      VpcId: !Ref TestVPC
      Tags:
        - Key: Name
          Value: 'SecurityGroupControl-Dev-SG'
        - Key: env
          Value: 'dev'

Outputs:
  VPCId:
    Description: VPC ID for the test environment
    Value: !Ref TestVPC

  ProdSecurityGroupId:
    Description: Production Security Group ID (env=prod)
    Value: !Ref ProdSecurityGroup

  DevSecurityGroupId:
    Description: Development Security Group ID (env=dev)
    Value: !Ref DevSecurityGroup

Conclusion

This solution demonstrates effective organizational security governance using AWS Service Control Policies with selective enforcement on production resources, role-based exceptions for security teams, and preserved development team autonomy. The approach balances security governance with operational efficiency at minimal cost.


⚠️ Remember to clean up test resources after validation to avoid ongoing costs!

⚠️ Always test your SCP on your test account before rolling out SCP to production environments.