Understanding Security Groups with AWS CloudFormation: A VPC with Bastion Hosts and Private Instances
AWS CloudFormation simplifies infrastructure as code, making it easier to deploy complex environments consistently. In this blog, we'll design and deploy a Virtual Private Cloud (VPC) using CloudFormation that spans two Availability Zones (AZs). This setup will emphasize security by exploring security groups, proper configuration, and secure access workflows.
Architecture Overview
The CloudFormation template will create the following resources:
VPC: A secure virtual network with isolated subnets.
Public Subnets: One in each AZ, hosting bastion hosts.
Private Subnets: Two sets per AZ, hosting application instances.
Security Groups: Fine-tuned rules to control traffic between a bastion host and a private instance.
Internet Gateway: To allow bastion hosts to connect to the internet.
Security Use Case
We'll demonstrate:
SSH Access: Use bastion hosts in public subnets to SSH into private instances.
Inter-AZ Communication: Ping the private instance in the other AZ.
Restricted Internet Access: Allow private instances to connect to the internet via the bastion host while blocking inbound traffic.
Key Security Group Configuration
Bastion Hosts Security Group:
Allow inbound SSH traffic (port 22) from your IP range.
Allow outbound SSH traffic to private instances (port 22).
Private Instances Security Group:
Allow inbound SSH traffic only from bastion hosts.
Allow inbound ICMP (ping) traffic from instances in the other AZ.
Deploying the Environment
Step 1: Create the CloudFormation Template
Here’s a snippet of the CloudFormation YAML for this setup specifically regarding security group configuration:
Bastion Host Security Group
This security group allows SSH access to the bastion host from your public IP and enables it to connect to private instances via SSH.
BastionSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Allow SSH access to the bastion host"
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: # Replace with your public IP
Private Instance Security Group
This security group restricts access to private instances, allowing only SSH from the bastion host and ICMP traffic (ping) from other private instances.
PrivateInstanceSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: "Allow SSH and ICMP traffic for private instances"
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId: !Ref BastionSG # SSH only from bastion
- IpProtocol: icmp
FromPort: -1
ToPort: -1
CidrIp: # Include private IP from other instance
Step 2: Deploy the Template
Save the template as
vpc.yaml
.Use the AWS CLI or Management Console to deploy:
aws cloudformation create-stack --stack-name vpc-stack --template-body file://vpc.yaml
Step 3: Testing Security Group Configuration
SSH into Bastion Host:
ssh -i bastion.pem ec2-user@<bastion-public-ip>
SSH into Private Instance: From the bastion host:
ssh -i bastion.pem ec2-user@<private-instance-ip>
Ping the Other AZ Instance: From the private instance:
ping <private-instance-ip-in-other-az>
Security Best Practices
Restrict SSH Access: Always limit SSH access to specific IPs using CIDR blocks (e.g.,
x.x.x.x/32
).Avoid Overly Permissive Rules: Ensure security groups only allow the minimum required traffic.
Use Bastion Hosts: Never open SSH access directly to private instances or databases.
Final Thoughts
This project demonstrates how CloudFormation can create robust VPCs while maintaining a security-first approach. By understanding and properly configuring security groups, you can ensure secure communication between resources without exposing your environment unnecessarily.
What other networking or security challenges have you faced with AWS? Let me know in the comments!
Subscribe to my newsletter
Read articles from Daniel Her directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by