Network & Infrastructure Security
Security Groups & Network ACLs
4 min read
Security groups and Network ACLs (NACLs) provide layered network security. Understanding their differences and proper configuration is critical for defense in depth.
Security Groups vs NACLs
| Feature | Security Groups | Network ACLs |
|---|---|---|
| Level | Instance/Resource | Subnet |
| State | Stateful | Stateless |
| Rules | Allow only | Allow and Deny |
| Evaluation | All rules evaluated | Rules processed in order |
| Default | Deny all inbound | Allow all |
| Best for | Application-level filtering | Subnet-level blocking |
AWS Security Groups
Stateful Behavior
Security groups are stateful—if you allow inbound traffic, the response is automatically allowed:
Inbound Rule: Allow TCP 443 from 0.0.0.0/0
→ Response traffic automatically allowed (no outbound rule needed)
Secure Security Group Patterns
Web server security group:
resource "aws_security_group" "web" {
name = "web-server-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
# HTTPS from load balancer only
ingress {
description = "HTTPS from ALB"
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
# No direct SSH - use Session Manager
# ingress { ... port 22 } # AVOID
egress {
description = "HTTPS to app tier"
from_port = 8080
to_port = 8080
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
tags = {
Name = "web-server-sg"
}
}
Database security group:
resource "aws_security_group" "database" {
name = "database-sg"
description = "Security group for databases"
vpc_id = aws_vpc.main.id
# PostgreSQL from app tier only
ingress {
description = "PostgreSQL from app tier"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app.id]
}
# No outbound internet access
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["10.0.0.0/16"] # VPC only
}
}
Common Security Group Mistakes
| Mistake | Risk | Solution |
|---|---|---|
| 0.0.0.0/0 on SSH (22) | Brute force attacks | Use Session Manager or bastion |
| 0.0.0.0/0 on RDP (3389) | Ransomware vector | Use VPN or bastion |
| 0.0.0.0/0 on databases | Data breach | Application tier only |
| Outbound 0.0.0.0/0 all | Data exfiltration | Limit to required |
Network ACLs (NACLs)
Stateless Behavior
NACLs are stateless—you must explicitly allow both inbound and outbound:
Inbound Rule: Allow TCP 443 from 0.0.0.0/0
Outbound Rule: Allow TCP 1024-65535 to 0.0.0.0/0 (ephemeral ports for response)
NACL for Defense in Depth
Use NACLs to block known bad actors at the subnet level:
resource "aws_network_acl" "private" {
vpc_id = aws_vpc.main.id
subnet_ids = [aws_subnet.private.id]
# Block known malicious IPs
ingress {
protocol = "-1"
rule_no = 100
action = "deny"
cidr_block = "192.0.2.0/24" # Example blocked range
from_port = 0
to_port = 0
}
# Allow VPC traffic
ingress {
protocol = "-1"
rule_no = 200
action = "allow"
cidr_block = "10.0.0.0/16"
from_port = 0
to_port = 0
}
# Deny all other inbound
ingress {
protocol = "-1"
rule_no = 32766
action = "deny"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
# Allow all outbound to VPC
egress {
protocol = "-1"
rule_no = 100
action = "allow"
cidr_block = "10.0.0.0/16"
from_port = 0
to_port = 0
}
}
Azure Network Security Groups
NSG Rules
# Create NSG
az network nsg create \
--name web-nsg \
--resource-group myRG
# Allow HTTPS from load balancer
az network nsg rule create \
--nsg-name web-nsg \
--resource-group myRG \
--name AllowHTTPS \
--priority 100 \
--access Allow \
--direction Inbound \
--protocol Tcp \
--destination-port-ranges 443 \
--source-address-prefixes AzureLoadBalancer
# Deny direct SSH
az network nsg rule create \
--nsg-name web-nsg \
--resource-group myRG \
--name DenySSH \
--priority 200 \
--access Deny \
--direction Inbound \
--protocol Tcp \
--destination-port-ranges 22 \
--source-address-prefixes Internet
Application Security Groups (ASG)
Group VMs by function instead of IP:
# Create ASGs
az network asg create --name web-asg --resource-group myRG
az network asg create --name db-asg --resource-group myRG
# NSG rule using ASGs
az network nsg rule create \
--nsg-name app-nsg \
--resource-group myRG \
--name AllowWebToDb \
--priority 100 \
--access Allow \
--direction Inbound \
--protocol Tcp \
--destination-port-ranges 5432 \
--source-asgs web-asg \
--destination-asgs db-asg
GCP Firewall Rules
VPC Firewall Rules
# Allow HTTPS from load balancer
gcloud compute firewall-rules create allow-https-lb \
--network=production-vpc \
--allow=tcp:443 \
--source-ranges=130.211.0.0/22,35.191.0.0/16 \
--target-tags=web-server
# Allow internal traffic
gcloud compute firewall-rules create allow-internal \
--network=production-vpc \
--allow=tcp,udp,icmp \
--source-ranges=10.0.0.0/16
# Deny all ingress (implicit, but explicit is better)
gcloud compute firewall-rules create deny-all-ingress \
--network=production-vpc \
--action=DENY \
--rules=all \
--source-ranges=0.0.0.0/0 \
--priority=65534
Security Group Audit Checklist
| Check | Action | Priority |
|---|---|---|
| No 0.0.0.0/0 on admin ports | Remove or restrict | Critical |
| Database not internet-facing | Application tier only | Critical |
| Outbound restricted | Limit egress rules | High |
| Unused rules removed | Audit and clean | Medium |
| Flow logs enabled | Monitor traffic | High |
| Documentation current | Review quarterly | Medium |
Next, we'll explore WAF, DDoS protection, and edge security services. :::