iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Security Groups / NACLs

Security Groups are stateful virtual firewalls attached to ENIs (EC2, RDS, Lambda VPC, load balancers). Default-deny inbound, default-allow outbound, and rules reference other security groups so “web -> db” is one rule instead of an ever-changing IP list.

Stateful, references, deny, audit

EXAMPLE
// 1) Anatomy
// • Inbound rules:  default DENY ALL — explicitly allow what you need
// • Outbound rules: default ALLOW ALL — restrict for tight environments
// • Stateful: a response to an allowed inbound packet is automatically allowed outbound
// • Multiple groups can be attached to one ENI; rules UNION

// 2) A typical web tier
awscli> aws ec2 create-security-group \\
    --group-name web-sg --description 'web tier' --vpc-id vpc-123
# returns groupId like sg-aaaa

// Allow HTTP/HTTPS from anywhere (ALB)
awscli> aws ec2 authorize-security-group-ingress --group-id sg-aaaa \\
    --ip-permissions IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges='[{CidrIp=0.0.0.0/0}]' \\
                                      IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges='[{CidrIp=0.0.0.0/0}]'

// 3) The database tier — reference the WEB SG, not IPs
awscli> aws ec2 create-security-group \\
    --group-name db-sg --description 'db tier' --vpc-id vpc-123
# returns sg-bbbb

awscli> aws ec2 authorize-security-group-ingress --group-id sg-bbbb \\
    --ip-permissions IpProtocol=tcp,FromPort=5432,ToPort=5432,UserIdGroupPairs='[{GroupId=sg-aaaa}]'

// Now any instance in sg-aaaa can talk to Postgres on sg-bbbb — no IP list to maintain.
// Add a new EC2 instance to sg-aaaa and it automatically gets DB access.

// 4) Terraform — declarative groups
resource 'aws_security_group' 'web' {
    name        = 'web-sg'
    description = 'web tier'
    vpc_id      = var.vpc_id

    ingress {
        from_port   = 80
        to_port     = 80
        protocol    = 'tcp'
        cidr_blocks = ['0.0.0.0/0']
    }
    ingress {
        from_port   = 443
        to_port     = 443
        protocol    = 'tcp'
        cidr_blocks = ['0.0.0.0/0']
    }
    egress {
        from_port   = 0
        to_port     = 0
        protocol    = '-1'
        cidr_blocks = ['0.0.0.0/0']     # default — restrict in tight envs
    }
}

resource 'aws_security_group' 'db' {
    name        = 'db-sg'
    vpc_id      = var.vpc_id

    ingress {
        from_port       = 5432
        to_port         = 5432
        protocol        = 'tcp'
        security_groups = [aws_security_group.web.id]
    }
}

// 5) Outbound restriction (zero-trust)
# By default outbound is open. For sensitive services, lock it down:
resource 'aws_security_group' 'app' {
    egress {
        from_port       = 443
        to_port         = 443
        protocol        = 'tcp'
        cidr_blocks     = ['0.0.0.0/0']     # only HTTPS to internet
    }
    egress {
        from_port       = 5432
        to_port         = 5432
        protocol        = 'tcp'
        security_groups = [aws_security_group.db.id]
    }
}

// 6) NACLs vs Security Groups
// • Security Groups — STATEFUL; attached to ENI; default deny in
// • NACLs           — STATELESS; attached to subnet; rules are numbered; default allow
// Use SGs for app-level allowlisting; NACLs for subnet-level guardrails (block specific IPs, RFC1918 exceptions).

// 7) Audit + visualisation
awscli> aws ec2 describe-security-groups --group-ids sg-aaaa
awscli> aws ec2 describe-network-interfaces --filters 'Name=group-id,Values=sg-aaaa'

# Reachability Analyzer — does sg-A's EC2 actually reach sg-B's RDS?
awscli> aws ec2 create-network-insights-path \\
    --source eni-aaa --destination eni-bbb --protocol tcp --destination-port 5432
awscli> aws ec2 start-network-insights-analysis --network-insights-path-id nip-123
awscli> aws ec2 describe-network-insights-analyses --network-insights-analysis-ids nia-123

# Visualise: VPC > Network Analysis > Reachability Analyzer

// 8) Common patterns
// • ALB SG       — allow 80/443 from internet; allow ALL to app SG
// • App SG        — allow 8080 from ALB SG; restrict outbound
// • DB SG         — allow 5432 from App SG ONLY
// • Bastion SG    — allow 22 from corporate IP range only; allow to App SG on 22
// • Lambda SG     — outbound to DB SG; needed for VPC Lambdas

// 9) Cross-account / cross-VPC references
// • Reference an SG in a peered VPC by its full SG id
// • For Transit Gateway / VPC peering, SGs don't traverse — use prefix lists or IP CIDRs

// 10) Prefix Lists — managed IP collections
# Use to grant access to a list of IPs that may change (CDN, GitHub Actions, etc.):
awscli> aws ec2 create-managed-prefix-list --prefix-list-name 'cdn-ips' --max-entries 30 \\
    --address-family IPv4 --entries Cidr=151.101.0.0/16

# In SG rule:
--source-prefix-list pl-123

# AWS-managed prefix lists exist for S3, DynamoDB endpoints — use them in VPC endpoint policies.

// 11) Limits
# • Up to 60 inbound + 60 outbound rules per SG (raise to 1000 via support)
# • Up to 5 SGs per ENI (raise to 16)
# • Up to 2,500 SGs per VPC
# If you hit limits, consolidate or refactor — usually a sign of over-fragmented rules.

// 12) Cleanup hygiene
# • Quarterly review — list SGs, find unused (no ENIs attached)
# • Tag SGs with Owner + Purpose
# • Remove '0.0.0.0/0' on SSH / RDP — use SSM Session Manager or bastion + IP restriction
# • Watch for 'any' rules: 0.0.0.0/0 + protocol -1 + all ports = open door

// 13) Common bugs
// • SG attached to instance but no ENI rule path — verify by tracing ENI through subnet route table
// • 0.0.0.0/0 on database ports — common mistake; lock to app SG only
// • Forgot to allow outbound 443 from VPC Lambdas calling external APIs — VPC Lambdas don't get internet for free
// • Reaching cross-VPC over SG references — only works in peered VPCs
// • Adding a rule and waiting 'a few minutes' for it to take effect — SG changes are nearly instant
// • Treating SGs as static IP lists — use SG-to-SG references; auto-scaling makes IPs ephemeral
// • NACL deny overriding SG allow — NACL evaluated first; check both

Why it matters

Security Groups are stateful and reference each other — let the “web SG can talk to the DB SG” rule replace IP lists that go stale every deploy. Default-deny inbound, restrict outbound on sensitive tiers, use Reachability Analyzer to verify before you ship, and prune unused groups quarterly so the inventory stays auditable.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# Security Groups — stateful, allow-only.
# NACLs — stateless, evaluated at the subnet boundary.
Try it Yourself »

Discussion

Loading…