AWS VPC: Networking and Security Architecture
Designing secure network architectures with AWS VPC. Subnets, route tables, security groups, NACLs, VPC endpoints, and multi-account networking strategies.
AWS VPC: Networking and Security Architecture
Amazon Virtual Private Cloud (VPC) provides isolated network environments in AWS. Designing secure, scalable VPC architectures is fundamental to cloud infrastructure. Understanding subnets, routing, security groups, and connectivity options enables building robust multi-tier applications.
VPC Architecture
Basic Components
VPC Architecture Overview:
┌─────────────────────────────────────────────────────────────────┐
│ VPC (10.0.0.0/16) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Availability Zone A ││
│ │ ┌─────────────────┐ ┌─────────────────┐ ││
│ │ │ Public Subnet │ │ Private Subnet │ ││
│ │ │ 10.0.0.0/24 │ │ 10.0.10.0/24 │ ││
│ │ │ │ │ │ ││
│ │ │ [NAT Gateway] │────▶│ [EC2/Lambda] │ ││
│ │ │ [ALB] │ │ [RDS] │ ││
│ │ └────────┬────────┘ └─────────────────┘ ││
│ └───────────┼──────────────────────────────────────────────────┘│
│ │ │
│ ┌───────────┼──────────────────────────────────────────────────┐│
│ │ │ Availability Zone B ││
│ │ ┌────────▼────────┐ ┌─────────────────┐ ││
│ │ │ Public Subnet │ │ Private Subnet │ ││
│ │ │ 10.0.1.0/24 │ │ 10.0.11.0/24 │ ││
│ │ │ │ │ │ ││
│ │ │ [NAT Gateway] │────▶│ [EC2/Lambda] │ ││
│ │ │ [ALB] │ │ [RDS] │ ││
│ │ └────────┬────────┘ └─────────────────┘ ││
│ └───────────┼──────────────────────────────────────────────────┘│
│ │ │
│ ▼ │
│ [Internet Gateway] │
└─────────────────────────────────────────────────────────────────┘Subnet Strategy
| Subnet Type | CIDR Example | Use Case |
|---|---|---|
| Public | 10.0.0.0/24 | Load balancers, NAT gateways, bastion hosts |
| Private | 10.0.10.0/24 | Application servers, containers |
| Data | 10.0.20.0/24 | Databases, caches |
| Management | 10.0.30.0/24 | CI/CD, monitoring, admin tools |
VPC Configuration
Terraform Module
# vpc/main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.tags, {
Name = "${var.project}-vpc"
})
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(var.tags, {
Name = "${var.project}-igw"
})
}
# Public Subnets
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.project}-public-${var.availability_zones[count.index]}"
Type = "public"
})
}
# Private Subnets
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.project}-private-${var.availability_zones[count.index]}"
Type = "private"
})
}
# Data Subnets (for databases)
resource "aws_subnet" "data" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 20)
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.project}-data-${var.availability_zones[count.index]}"
Type = "data"
})
}
# Elastic IP for NAT Gateway
resource "aws_eip" "nat" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.availability_zones)) : 0
domain = "vpc"
tags = merge(var.tags, {
Name = "${var.project}-nat-eip-${count.index}"
})
depends_on = [aws_internet_gateway.main]
}
# NAT Gateway
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.availability_zones)) : 0
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = merge(var.tags, {
Name = "${var.project}-nat-${count.index}"
})
depends_on = [aws_internet_gateway.main]
}
# Public Route Table
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = merge(var.tags, {
Name = "${var.project}-public-rt"
})
}
resource "aws_route_table_association" "public" {
count = length(var.availability_zones)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# Private Route Tables
resource "aws_route_table" "private" {
count = var.enable_nat_gateway ? (var.single_nat_gateway ? 1 : length(var.availability_zones)) : 0
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[var.single_nat_gateway ? 0 : count.index].id
}
tags = merge(var.tags, {
Name = "${var.project}-private-rt-${count.index}"
})
}
resource "aws_route_table_association" "private" {
count = length(var.availability_zones)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[var.single_nat_gateway ? 0 : count.index].id
}Security Groups
Layered Security
# security-groups.tf
# ALB Security Group
resource "aws_security_group" "alb" {
name = "${var.project}-alb-sg"
description = "Security group for Application Load Balancer"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTPS from anywhere"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTP from anywhere (redirect to HTTPS)"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "All traffic to VPC"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = [var.vpc_cidr]
}
tags = merge(var.tags, {
Name = "${var.project}-alb-sg"
})
}
# Application Security Group
resource "aws_security_group" "app" {
name = "${var.project}-app-sg"
description = "Security group for application servers"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from ALB"
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress {
description = "HTTPS for AWS services"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "Database access"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.database.id]
}
egress {
description = "Redis access"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [aws_security_group.cache.id]
}
tags = merge(var.tags, {
Name = "${var.project}-app-sg"
})
}
# Database Security Group
resource "aws_security_group" "database" {
name = "${var.project}-db-sg"
description = "Security group for RDS database"
vpc_id = aws_vpc.main.id
ingress {
description = "PostgreSQL from app servers"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
tags = merge(var.tags, {
Name = "${var.project}-db-sg"
})
}
# Cache Security Group
resource "aws_security_group" "cache" {
name = "${var.project}-cache-sg"
description = "Security group for ElastiCache"
vpc_id = aws_vpc.main.id
ingress {
description = "Redis from app servers"
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
tags = merge(var.tags, {
Name = "${var.project}-cache-sg"
})
}Network ACLs
Subnet-Level Security
# nacls.tf
# Public Subnet NACL
resource "aws_network_acl" "public" {
vpc_id = aws_vpc.main.id
subnet_ids = aws_subnet.public[*].id
# Inbound Rules
ingress {
protocol = "tcp"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 443
to_port = 443
}
ingress {
protocol = "tcp"
rule_no = 110
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 80
to_port = 80
}
ingress {
protocol = "tcp"
rule_no = 120
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 1024
to_port = 65535 # Ephemeral ports
}
# Outbound Rules
egress {
protocol = "tcp"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 443
to_port = 443
}
egress {
protocol = "tcp"
rule_no = 110
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 80
to_port = 80
}
egress {
protocol = "tcp"
rule_no = 120
action = "allow"
cidr_block = var.vpc_cidr
from_port = 8080
to_port = 8080
}
egress {
protocol = "tcp"
rule_no = 130
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 1024
to_port = 65535
}
tags = merge(var.tags, {
Name = "${var.project}-public-nacl"
})
}
# Private Subnet NACL
resource "aws_network_acl" "private" {
vpc_id = aws_vpc.main.id
subnet_ids = aws_subnet.private[*].id
# Allow traffic from public subnets
ingress {
protocol = "tcp"
rule_no = 100
action = "allow"
cidr_block = var.vpc_cidr
from_port = 8080
to_port = 8080
}
# Allow return traffic
ingress {
protocol = "tcp"
rule_no = 110
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 1024
to_port = 65535
}
# Allow outbound to anywhere
egress {
protocol = "-1"
rule_no = 100
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
tags = merge(var.tags, {
Name = "${var.project}-private-nacl"
})
}VPC Endpoints
Private AWS Service Access
# endpoints.tf
# S3 Gateway Endpoint (free)
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = concat(
[aws_route_table.public.id],
aws_route_table.private[*].id
)
tags = merge(var.tags, {
Name = "${var.project}-s3-endpoint"
})
}
# DynamoDB Gateway Endpoint (free)
resource "aws_vpc_endpoint" "dynamodb" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.dynamodb"
vpc_endpoint_type = "Gateway"
route_table_ids = aws_route_table.private[*].id
tags = merge(var.tags, {
Name = "${var.project}-dynamodb-endpoint"
})
}
# Security Group for Interface Endpoints
resource "aws_security_group" "vpc_endpoints" {
name = "${var.project}-vpc-endpoints-sg"
description = "Security group for VPC endpoints"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTPS from VPC"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [var.vpc_cidr]
}
tags = merge(var.tags, {
Name = "${var.project}-vpc-endpoints-sg"
})
}
# ECR API Endpoint
resource "aws_vpc_endpoint" "ecr_api" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ecr.api"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true
tags = merge(var.tags, {
Name = "${var.project}-ecr-api-endpoint"
})
}
# ECR DKR Endpoint
resource "aws_vpc_endpoint" "ecr_dkr" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ecr.dkr"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true
tags = merge(var.tags, {
Name = "${var.project}-ecr-dkr-endpoint"
})
}
# CloudWatch Logs Endpoint
resource "aws_vpc_endpoint" "logs" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.logs"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true
tags = merge(var.tags, {
Name = "${var.project}-logs-endpoint"
})
}
# Secrets Manager Endpoint
resource "aws_vpc_endpoint" "secretsmanager" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.secretsmanager"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true
tags = merge(var.tags, {
Name = "${var.project}-secretsmanager-endpoint"
})
}VPC Flow Logs
Network Traffic Monitoring
# flow-logs.tf
resource "aws_flow_log" "main" {
iam_role_arn = aws_iam_role.flow_logs.arn
log_destination = aws_cloudwatch_log_group.flow_logs.arn
traffic_type = "ALL"
vpc_id = aws_vpc.main.id
tags = merge(var.tags, {
Name = "${var.project}-flow-logs"
})
}
resource "aws_cloudwatch_log_group" "flow_logs" {
name = "/aws/vpc-flow-logs/${var.project}"
retention_in_days = 30
tags = var.tags
}
resource "aws_iam_role" "flow_logs" {
name = "${var.project}-flow-logs-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "vpc-flow-logs.amazonaws.com"
}
}]
})
}
resource "aws_iam_role_policy" "flow_logs" {
name = "${var.project}-flow-logs-policy"
role = aws_iam_role.flow_logs.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
]
Effect = "Allow"
Resource = "*"
}]
})
}Multi-Account Networking
Transit Gateway
# transit-gateway.tf
resource "aws_ec2_transit_gateway" "main" {
description = "${var.project} Transit Gateway"
default_route_table_association = "disable"
default_route_table_propagation = "disable"
dns_support = "enable"
vpn_ecmp_support = "enable"
tags = merge(var.tags, {
Name = "${var.project}-tgw"
})
}
resource "aws_ec2_transit_gateway_vpc_attachment" "main" {
subnet_ids = aws_subnet.private[*].id
transit_gateway_id = aws_ec2_transit_gateway.main.id
vpc_id = aws_vpc.main.id
tags = merge(var.tags, {
Name = "${var.project}-tgw-attachment"
})
}
# Share Transit Gateway with other accounts
resource "aws_ram_resource_share" "tgw" {
name = "${var.project}-tgw-share"
allow_external_principals = false
tags = var.tags
}
resource "aws_ram_resource_association" "tgw" {
resource_arn = aws_ec2_transit_gateway.main.arn
resource_share_arn = aws_ram_resource_share.tgw.arn
}
resource "aws_ram_principal_association" "org" {
principal = var.organization_arn
resource_share_arn = aws_ram_resource_share.tgw.arn
}Key Takeaways
-
Plan CIDR blocks: Use non-overlapping ranges for VPC peering/Transit Gateway
-
Subnet strategy: Separate public, private, and data tiers
-
Security groups first: Use security groups as primary defence, NACLs for subnet-level rules
-
VPC endpoints: Reduce NAT Gateway costs and improve security
-
Flow logs: Enable for troubleshooting and security analysis
-
High availability: Deploy across multiple availability zones
-
NAT Gateway costs: Consider single NAT for dev, multi-AZ for production
-
Transit Gateway: Simplify multi-VPC and multi-account networking
Proper VPC design is foundational to secure, scalable AWS architectures. Invest time in planning your network topology before deployment.