When working with AWS CloudFormation, one common pain point emerges when trying to reference the default security group that's automatically created with each new VPC. Unlike explicitly defined security groups, this default group (with its "allow all intra-group traffic" rules) doesn't have a predictable ID during stack creation.
The core issue stems from two CloudFormation behaviors:
- The default security group gets created after VPC creation completes
- CloudFormation doesn't expose intrinsic functions to retrieve this dynamically generated resource
Traditional approaches like !Ref
or !GetAtt
won't work since the default SG isn't a defined resource in your template.
Here are three viable approaches to solve this:
1. Using a Custom Resource with Lambda
This method provides the most flexibility by using AWS Lambda to fetch the default security group ID:
"DefaultSGLookup": {
"Type": "Custom::DefaultSecurityGroup",
"Properties": {
"ServiceToken": { "Fn::GetAtt": ["DefaultSGLambdaFunction", "Arn"] },
"VpcId": { "Ref": "DevVPC" }
}
},
"Instance001": {
"Type": "AWS::EC2::Instance",
"Properties": {
"SecurityGroupIds": [
{ "Ref": "AllowSSHSecGroup" },
{ "Fn::GetAtt": ["DefaultSGLookup", "GroupId"] }
],
// Other instance properties
}
}
The Lambda function would use EC2's describe_security_groups
API to find the default group.
2. AWS Systems Manager Parameter Store
For more static environments, you can pre-store the default SG ID:
"InstanceSecurityGroups": {
"Type": "AWS::EC2::Instance",
"Properties": {
"SecurityGroupIds": [
{ "Ref": "AllowSSHSecGroup" },
"{{resolve:ssm:/network/default-sg-id:1}}"
]
}
}
3. CloudFormation Stack Outputs + Nested Stacks
When using nested stacks, pass the default SG ID as an output:
Outputs:
DefaultSecurityGroupId:
Description: The default security group ID
Value: !GetAtt DefaultSecurityGroup.GroupId
Export:
Name: !Sub "${AWS::StackName}-DefaultSG"
When implementing these solutions, remember:
- Default security group rules vary by region
- The "allow all" rule only applies to instances in the same security group
- Consider security best practices - the default SG might be too permissive
Instead of using the default security group, consider:
"CustomDefaultSG": {
"Type": "AWS::EC2::SecurityGroup",
"Properties": {
"GroupDescription": "Replacement for default SG",
"VpcId": { "Ref": "DevVPC" },
"SecurityGroupIngress": [
{
"IpProtocol": "-1",
"FromPort": "-1",
"ToPort": "-1",
"SourceSecurityGroupId": { "Fn::GetAtt": ["CustomDefaultSG", "GroupId"] }
}
]
}
}
This gives you full control while maintaining similar functionality.
When working with AWS CloudFormation, one common pain point emerges when trying to reference the default security group that AWS automatically creates with every new VPC. Unlike manually created security groups, this default group doesn't have a predictable ID that can be referenced in your template.
The standard approach using SecurityGroupIds
fails because:
\"SecurityGroupIds\": [
{\"Ref\": \"AllowSSHSecGroup\"},
// Can't reference default SG here as we don't know its ID
]
This limitation occurs because CloudFormation can't resolve the dynamically generated ID of the default security group during stack creation.
Option 1: Using Custom Resources
Create a Lambda-backed custom resource to fetch the default security group ID:
\"GetDefaultSG\": {
\"Type\": \"Custom::GetDefaultSG\",
\"Properties\": {
\"ServiceToken\": {\"Fn::GetAtt\": [\"GetDefaultSGLambda\", \"Arn\"]},
\"VpcId\": {\"Ref\": \"DevVPC\"}
}
},
\"Instance001\": {
\"Type\": \"AWS::EC2::Instance\",
\"Properties\": {
\"SecurityGroupIds\": [
{\"Ref\": \"AllowSSHSecGroup\"},
{\"Fn::GetAtt\": [\"GetDefaultSG\", \"GroupId\"]}
]
// Other instance properties...
}
}
Option 2: Post-Creation Modification
Use AWS Systems Manager Run Command or a user data script to modify security groups after instance launch:
\"Instance001\": {
\"Type\": \"AWS::EC2::Instance\",
\"Properties\": {
\"UserData\": {
\"Fn::Base64\": {
\"Fn::Join\": [\"\", [
\"#!/bin/bash\n\",
\"INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)\n\",
\"DEFAULT_SG=$(aws ec2 describe-security-groups \\\n\",
\" --filters Name=vpc-id,Values=\", {\"Ref\": \"DevVPC\"}, \" \\\n\",
\" Name=group-name,Values=default \\\n\",
\" --query 'SecurityGroups[0].GroupId' --output text)\n\",
\"aws ec2 modify-instance-attribute \\\n\",
\" --instance-id $INSTANCE_ID \\\n\",
\" --groups \", {\"Ref\": \"AllowSSHSecGroup\"}, \" $DEFAULT_SG\n\"
]]
}
}
// Other instance properties...
}
}
- The default security group allows all traffic between members - ensure this aligns with your security requirements
- Custom resources add complexity but provide the cleanest CloudFormation-native solution
- Post-creation modifications may cause brief periods where security groups aren't fully configured
Consider whether you truly need the default security group. Often, explicitly defined security groups provide better security posture and more maintainable infrastructure-as-code.