How to Reference and Attach AWS VPC Default Security Group in CloudFormation Templates


2 views

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:

  1. The default security group gets created after VPC creation completes
  2. 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.