Complete VPC Security Setup
This demonstration walks through setting up a complete VPC security architecture with multiple security groups for a production-ready environment.
Step 1: Create the VPC
First, set up the VPC infrastructure that will host the security groups.
module "vpc" {
source = "registry.patterneddesigns.ca/patterneddesigns/vpc/aws"
version = "2.0.0"
name = "production"
cidr_block = "10.0.0.0/16"
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
private_subnets = ["10.0.11.0/24", "10.0.12.0/24", "10.0.13.0/24"]
data_subnets = ["10.0.21.0/24", "10.0.22.0/24", "10.0.23.0/24"]
}
Step 2: Create Load Balancer Security Group
Allow public HTTPS traffic to the application load balancer.
module "alb_sg" {
source = "registry.patterneddesigns.ca/patterneddesigns/security-group/aws"
version = "1.2.0"
name = "production-alb"
vpc_id = module.vpc.vpc_id
ingress_rules = [
{
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS from internet"
},
{
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP for redirect"
}
]
egress_rules = [
{
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [module.app_sg.security_group_id]
description = "Forward to application servers"
}
]
}
Step 3: Create Application Security Group
Allow traffic only from the load balancer.
module "app_sg" {
source = "registry.patterneddesigns.ca/patterneddesigns/security-group/aws"
version = "1.2.0"
name = "production-app"
vpc_id = module.vpc.vpc_id
ingress_rules = [
{
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [module.alb_sg.security_group_id]
description = "Application traffic from ALB"
}
]
egress_rules = [
{
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [module.db_sg.security_group_id]
description = "PostgreSQL database"
},
{
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [module.cache_sg.security_group_id]
description = "Redis cache"
},
{
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS to external services"
}
]
}
Step 4: Create Database Security Group
Allow access only from application servers.
module "db_sg" {
source = "registry.patterneddesigns.ca/patterneddesigns/security-group/aws"
version = "1.2.0"
name = "production-database"
vpc_id = module.vpc.vpc_id
ingress_rules = [
{
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [module.app_sg.security_group_id]
description = "PostgreSQL from application tier"
}
]
# No egress rules - database does not initiate connections
egress_rules = []
}
Step 5: Create Cache Security Group
Allow access from application servers only.
module "cache_sg" {
source = "registry.patterneddesigns.ca/patterneddesigns/security-group/aws"
version = "1.2.0"
name = "production-cache"
vpc_id = module.vpc.vpc_id
ingress_rules = [
{
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [module.app_sg.security_group_id]
description = "Redis from application tier"
}
]
egress_rules = []
}
Step 6: Create Bastion Security Group
Allow SSH access from corporate network for administration.
data "aws_ec2_managed_prefix_list" "corporate" {
name = "corporate-network"
}
module "bastion_sg" {
source = "registry.patterneddesigns.ca/patterneddesigns/security-group/aws"
version = "1.2.0"
name = "production-bastion"
vpc_id = module.vpc.vpc_id
ingress_rules = [
{
from_port = 22
to_port = 22
protocol = "tcp"
prefix_list_ids = [data.aws_ec2_managed_prefix_list.corporate.id]
description = "SSH from corporate network"
}
]
egress_rules = [
{
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [
module.app_sg.security_group_id,
module.db_sg.security_group_id
]
description = "SSH to internal instances"
}
]
}
Step 7: Output Security Group IDs
Export the security group IDs for use by other resources.
output "security_groups" {
value = {
alb = module.alb_sg.security_group_id
app = module.app_sg.security_group_id
db = module.db_sg.security_group_id
cache = module.cache_sg.security_group_id
bastion = module.bastion_sg.security_group_id
}
description = "Security group IDs for all tiers"
}
Architecture Diagram
Internet
│
▼
┌─────────────┐
│ ALB (443) │ ──────► alb_sg
└─────────────┘
│
▼ (8080)
┌─────────────┐
│ App Servers │ ──────► app_sg
└─────────────┘
│
├──────────────────┐
▼ (5432) ▼ (6379)
┌─────────────┐ ┌─────────────┐
│ Database │ │ Cache │
│ (RDS) │ │ (ElastiCache│
└─────────────┘ └─────────────┘
│ │
▼ ▼
db_sg cache_sg
Corporate Network (SSH)
│
▼
┌─────────────┐
│ Bastion │ ──────► bastion_sg
└─────────────┘
Security Considerations
- All security groups use security group references instead of CIDR blocks where possible
- Database and cache have no egress rules (deny all outbound)
- SSH access is restricted to the corporate network via prefix list
- Each tier can only communicate with its immediate neighbors