Segment your network, expose the minimum, and defend the metadata endpoint. Private subnets, least-privilege security groups, no 0.0.0.0/0 on admin ports, and the SSRF-to-credentials attack you must block.
A well-designed cloud network puts only what must be reachable (a load balancer) in a public subnet, and keeps app servers and databases in private subnets with no direct internet route. This means even a compromised public component cannot directly reach your data tier, and your database can never be exposed by a single wrong toggle because it has no public path at all.
internet
|
[ Load Balancer ] <- public subnet (only thing exposed)
|
[ App servers ] <- private subnet (no public IP)
|
[ Database ] <- private subnet, most isolated
outbound via NAT only; no inbound from internet
Default posture: private. A resource is public only if there's a
documented reason. The DB should have NO route to the internet at all.Security groups are stateful firewalls on each resource. The common failure is opening a port to 0.0.0.0/0 (the whole internet) — especially admin ports like SSH (22), RDP (3389), or database ports. Allow only the specific source that needs each port: reference another security group, not a wide CIDR, so "the app tier" can reach "the DB tier" and nothing else can.
# GOOD: DB accepts 5432 ONLY from the app's security group
aws ec2 authorize-security-group-ingress \
--group-id sg-db --protocol tcp --port 5432 \
--source-group sg-app # reference the SG, not a CIDR range
# NEVER do this (SSH open to the entire internet -> constant brute force):
# --port 22 --cidr 0.0.0.0/0
# admin access should go through SSM Session Manager / a bastion + MFA,
# not an open port. Audit for the danger:
aws ec2 describe-security-groups \
--filters Name=ip-permission.cidr,Values=0.0.0.0/0Cloud instances expose a metadata service that hands out the instance's role credentials. If your app has an SSRF vulnerability, an attacker can make it fetch that endpoint and steal those credentials — a classic cloud kill chain (this was the Capital One breach). Enforce IMDSv2, which requires a signed token, so a naive SSRF cannot read credentials.
IMDSv2 required — Require IMDSv2 (session-token protected) on all instances so a basic SSRF cannot retrieve instance credentials from the metadata endpoint.The attack (Capital One, 2019):
SSRF in app -> attacker makes app GET http://169.254.169.254/.../security-credentials/
-> app returns the instance ROLE's temporary credentials -> attacker uses them.
# defense: require IMDSv2 (token-based) so a simple GET can't read creds
aws ec2 modify-instance-metadata-options --instance-id i-123 \
--http-tokens required --http-endpoint enabled --http-put-response-hop-limit 1
# also: least-privilege the instance role (lesson 2) so even stolen creds
# are nearly useless, and block egress to 169.254.169.254 from app code.Most controls focus on inbound traffic, but outbound (egress) matters too: it is how malware exfiltrates data and calls home. Restrict where instances can connect outbound, and use private endpoints so traffic to cloud services (S3, KMS) never traverses the public internet. Controlling egress turns a data-theft incident into a blocked connection.
Why egress control matters:
a compromised server with open outbound can exfiltrate your DB to
anywhere + pull down more tooling. Lock it down:
- restrict outbound in security groups / NACLs to required destinations
- route cloud-service traffic through PRIVATE endpoints (VPC endpoints),
so S3/KMS/etc. never leave your private network
- use egress firewall / proxy allowlists for internet-bound calls
- alert on unexpected outbound (to the metadata IP, to odd geos)
Inbound stops intrusions; egress control limits what an intrusion can steal.