Restricting Security Group Modifications in Production Using AWS Service Control Policies (SCP)
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:
- Go to AWS Identity Center console
- Navigate to Permission sets
- Click Create permission set
- Name:
Security-Team
- Add policies:
EC2FullAccess
- Assign to security team users
Developer Team Permission Set:
- Create another permission set
- Name:
Developer-Team
- Add policies:
EC2LimitedAccess
- 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:
- Developer → Dev SG → ✅ SUCCESS (no restrictions)
- Developer → Prod SG → ❌ FAILED (SCP blocks)
- 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
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
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.
- Language
- English
Relevant content
- asked a year ago