Danh sách bài viết

Bài 117: Network Isolation — VPC, Firewall, Security Group

bind và protected-mode (bài 112) kiểm soát interface Redis lắng nghe ở cấp process. Network isolation là lớp bên ngoài: VPC, subnet, Security Group, firewall, và NetworkPolicy quyết định máy nào, pod nào được phép tạo kết nối TCP đến port Redis — trước khi packet chạm đến Redis process. Bài này đi qua kiến trúc VPC multi-tier, Security Group theo peer SG thay vì CIDR block, iptables và ufw cho server-level firewall, Kubernetes NetworkPolicy, bastion host, AWS Session Manager, PrivateLink/Private Endpoint, egress restriction phía Redis, và cách xác minh cô lập bằng nmap và CLIENT LIST.

01/06/2026
0 lượt xem
1

Mục Tiêu Bài Học

  • Hiểu tại sao network isolation là lớp tách biệt với bind/protected-mode và bổ sung gì vào defense in depth.
  • Thiết kế VPC multi-tier với subnet isolated cho Redis — không có egress Internet, không có ingress từ public.
  • Viết được Security Group đúng: source = peer SG thay vì CIDR block, không có egress ra ngoài.
  • Áp dụng iptables/ufw để hạn chế port 6379 ở cấp OS, độc lập với cloud firewall.
  • Viết Kubernetes NetworkPolicy cho phép chỉ app pod và worker pod kết nối Redis.
  • Biết khi nào dùng bastion host và khi nào dùng AWS Session Manager để admin Redis trong private network.
  • Hiểu PrivateLink và VPC Peering — dùng khi nào, khác nhau thế nào.
  • Xác minh cô lập bằng nmap và CLIENT LIST.
2

Bài Toán: Tại Sao bind Chưa Đủ

bind 127.0.0.1 hoạt động tốt khi app và Redis chạy cùng một máy. Ngay khi app chuyển sang server riêng — điều bắt buộc trong mọi multi-tier deployment — Redis phải bind vào private IP, và câu hỏi trở thành: IP private đó có ai khác trong mạng kết nối được không?

bind 10.0.20.5 cho phép Redis nghe trên private IP, nhưng bất kỳ máy nào trong cùng VPC hoặc cùng subnet đều kết nối được nếu không có thêm lớp kiểm soát. Trong một VPC có vài trăm EC2 instance hoặc vài ngàn pod K8s, đây là bề mặt tấn công lớn.

Network isolation giải quyết vấn đề này ở lớp mạng, trước khi packet đến Redis process:

  • VPC subnet: phân tầng subnet, Redis ở subnet không có route ra Internet và không có ingress từ public subnet.
  • Security Group / Firewall rule: chỉ cho phép app tier kết nối port 6379. Từ chối mọi nguồn khác.
  • OS-level firewall (iptables/ufw): thêm một lớp nữa ngay trên máy chạy Redis — nếu cloud firewall bị misconfigure, OS firewall vẫn chặn.
  • K8s NetworkPolicy: trong Kubernetes, mặc định mọi pod đều nói chuyện được với mọi pod. NetworkPolicy thêm whitelist tường minh.

Defense in depth: không phụ thuộc vào bất kỳ một lớp nào duy nhất. Nếu Security Group bị sửa nhầm, iptables vẫn chặn. Nếu Redis bị compromise, egress restriction ngăn nó gửi data ra Internet.

3

VPC Multi-tier Architecture

Kiến trúc VPC ba tầng tiêu chuẩn cho ứng dụng có Redis:

Internet Gateway
      |
ALB (public subnet: 10.0.1.0/24)
      |
App tier (private subnet: 10.0.10-11.0/24, egress via NAT)
      |
Redis tier (isolated subnet: 10.0.20.0/24, NO Internet route)
      |
DB tier (isolated subnet: 10.0.30.0/24, NO Internet route)

Điểm then chốt là route table của isolated subnet:

Cache subnet route table:
  Destination      Target
  10.0.0.0/16      local      ← chỉ có route local trong VPC
  (không có 0.0.0.0/0 → không có Internet gateway, không có NAT)

Redis ở isolated subnet không thể:

  • Nhận inbound từ Internet (không có route).
  • Tự kết nối ra Internet để exfiltrate data (không có NAT gateway).

App tier ở private subnet vẫn có egress qua NAT (để cài package, gọi external API), nhưng Redis tier không cần và không nên có.

Ví dụ cụ thể với AWS ElastiCache production:

VPC: 10.0.0.0/16
  Subnet group "cache":
    - 10.0.20.0/24  (us-east-1a)
    - 10.0.21.0/24  (us-east-1b)
    Route table: local only
    NACL: deny all inbound except từ 10.0.10.0/23 (app tier)
          deny all outbound to 0.0.0.0/0

NACL (Network ACL) là stateless firewall ở cấp subnet — hoạt động trước Security Group. Dùng NACL để deny toàn bộ inbound ngoại trừ app CIDR là một thêm một lớp bảo vệ nữa.

4

AWS Security Group — Peer SG Thay Vì CIDR

Security Group là stateful firewall ở cấp instance/ENI. Có hai cách viết ingress rule cho Redis SG:

Cách 1 — Source CIDR (kém hơn):

# Không khuyến khích
redis_sg:
  ingress:
    - protocol: tcp
      port: 6379
      cidr: 10.0.10.0/23   # toàn bộ app subnet CIDR

Vấn đề: bất kỳ instance nào ở 10.0.10.0/23 đều connect được, kể cả instance bị compromise hoặc instance dev được đặt nhầm subnet.

Cách 2 — Source Security Group (khuyến nghị):

redis_sg:
  ingress:
    - protocol: tcp
      port: 6379
      source_sg: sg-app-tier     # chỉ instance thuộc sg-app-tier
    - protocol: tcp
      port: 6379
      source_sg: sg-worker-tier  # chỉ worker instance
  egress: []                     # Redis không initiate outbound

Lợi thế của source = peer SG:

  • Dynamic membership: khi Auto Scaling Group thêm EC2 instance mới và gán vào sg-app-tier, instance đó tự động được phép kết nối Redis — không cần cập nhật CIDR rule.
  • Tách biệt role: developer box ở cùng CIDR nhưng không thuộc sg-app-tier thì không vào được.
  • Audit rõ ràng: nhìn vào SG rule biết ngay "Redis chỉ chấp nhận từ app tier và worker tier".

Terraform example:

resource "aws_security_group_rule" "redis_ingress_from_app" {
  type                     = "ingress"
  from_port                = 6379
  to_port                  = 6379
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.app_tier.id
  security_group_id        = aws_security_group.redis.id
}

resource "aws_security_group_rule" "redis_ingress_from_worker" {
  type                     = "ingress"
  from_port                = 6379
  to_port                  = 6379
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.worker_tier.id
  security_group_id        = aws_security_group.redis.id
}

# Không có egress rule → default deny all egress nếu dùng custom SG
# (với ElastiCache, egress rule không áp dụng vì managed service
#  không initiate outbound)

Chú ý: AWS Security Group mặc định deny all inbound và allow all outbound khi mới tạo. Nếu muốn deny egress hoàn toàn, xóa default outbound rule "allow all" và không thêm rule nào mới.

5

GCP Firewall Rule Theo Network Tag

GCP dùng network tag (hoặc service account) thay cho Security Group. Logic tương đương: chỉ VM có tag app-tier mới được kết nối VM có tag redis-tier.

# Cho phép traffic từ app-tier đến redis-tier trên port 6379
gcloud compute firewall-rules create allow-redis-from-app \
  --network=vpc-prod \
  --direction=INGRESS \
  --priority=1000 \
  --action=ALLOW \
  --rules=tcp:6379 \
  --source-tags=app-tier \
  --target-tags=redis-tier

# Deny tất cả inbound còn lại đến redis-tier (priority cao hơn = số nhỏ hơn)
gcloud compute firewall-rules create deny-redis-all \
  --network=vpc-prod \
  --direction=INGRESS \
  --priority=900 \
  --action=DENY \
  --rules=tcp:6379 \
  --target-tags=redis-tier

Lưu ý thứ tự priority: rule deny-redis-all có priority 900 (xử lý trước priority 1000), nhưng GCP firewall ưu tiên ALLOW trước DENY khi cùng priority. Để deny thắng, đặt priority của deny thấp hơn (số nhỏ hơn) so với allow. Cách an toàn hơn: đặt allow-redis-from-app priority=900, deny-redis-all priority=1000 — khi source có tag app-tier sẽ match allow (900) trước khi bị deny (1000) xử lý.

# Cách đúng: allow có priority thấp hơn (số nhỏ hơn = ưu tiên cao hơn)
gcloud compute firewall-rules create allow-redis-from-app \
  --priority=900 \
  --action=ALLOW \
  --rules=tcp:6379 \
  --source-tags=app-tier \
  --target-tags=redis-tier

gcloud compute firewall-rules create deny-redis-all \
  --priority=1000 \
  --action=DENY \
  --rules=tcp:6379 \
  --target-tags=redis-tier

Với GCP Memorystore (managed Redis), dùng VPC peering với private IP — xem mục 9.

6

iptables — Server-level Firewall

Kể cả khi đã có Security Group, thêm iptables rule trực tiếp trên Redis host là một lớp bổ sung. Nếu ai đó tạo nhầm một Security Group rule permissive, iptables vẫn giữ.

# Chỉ cho phép kết nối 6379 từ app subnet
iptables -A INPUT -p tcp --dport 6379 -s 10.0.10.0/23 -j ACCEPT

# Drop tất cả connection 6379 còn lại
iptables -A INPUT -p tcp --dport 6379 -j DROP

# Cho phép established connections (response packets)
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Persist rules (Debian/Ubuntu)
apt install iptables-persistent
netfilter-persistent save

# Verify
iptables -L INPUT -n -v | grep 6379

Chú ý thứ tự rule trong iptables: rule được xử lý từ trên xuống, rule nào match trước thì áp dụng. Đặt ACCEPT từ app subnet trước, rồi DROP all sau.

Nếu Redis chạy trong cluster và các node cần giao tiếp với nhau, thêm rule cho cluster bus port (xem mục 15).

Nếu dùng systemd, có thể viết rule vào file và load khi boot:

# /etc/iptables/rules.v4 (format iptables-save)
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp --dport 6379 -s 10.0.10.0/23 -j ACCEPT
-A INPUT -p tcp --dport 22 -s 10.0.1.0/24 -j ACCEPT  # SSH từ bastion subnet
COMMIT
7

ufw — Ubuntu Firewall

ufw (Uncomplicated Firewall) là wrapper dễ dùng hơn cho iptables, mặc định có trên Ubuntu.

# Đặt default policy
ufw default deny incoming
ufw default allow outgoing

# Cho phép SSH từ bastion subnet (làm trước để không lock out)
ufw allow from 10.0.1.0/24 to any port 22

# Cho phép Redis chỉ từ app subnet
ufw allow from 10.0.10.0/23 to any port 6379

# Bật firewall
ufw enable

# Kiểm tra trạng thái
ufw status verbose

Output mong đợi:

Status: active

To                    Action      From
--                    ------      ----
22/tcp                ALLOW IN    10.0.1.0/24
6379/tcp              ALLOW IN    10.0.10.0/23

ufw vẫn dùng iptables bên dưới — kiểm tra bằng iptables -L để xác nhận.

Nếu muốn giới hạn thêm egress từ Redis (không cho kết nối ra ngoài trừ DNS và NTP):

ufw default deny outgoing
# DNS
ufw allow out to any port 53
# NTP
ufw allow out to any port 123 proto udp
# Replication: Redis replica kết nối master trên port 6379
ufw allow out to 10.0.20.0/24 port 6379
8

Kubernetes NetworkPolicy

Kubernetes không enforce NetworkPolicy mặc định — cần cài CNI plugin hỗ trợ (Calico, Cilium, Weave Net). Khi đã có plugin, mặc định pod vẫn nói chuyện tự do. NetworkPolicy thêm whitelist tường minh.

Quy tắc quan trọng: nếu không có NetworkPolicy nào select một pod, pod đó không bị giới hạn. Ngay khi có một NetworkPolicy select pod, mọi traffic không được policy cho phép đều bị từ chối.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: redis-allow-from-app
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: redis          # policy này áp dụng cho Redis pod
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: web    # web pod được phép kết nối
        - podSelector:
            matchLabels:
              app: worker # worker pod được phép kết nối
      ports:
        - port: 6379
          protocol: TCP
  egress:
    # Cho phép Redis replica connect master (Redis Cluster / Replication)
    - to:
        - podSelector:
            matchLabels:
              app: redis
      ports:
        - port: 6379
          protocol: TCP
    # DNS resolution
    - to: []
      ports:
        - port: 53
          protocol: UDP
        - port: 53
          protocol: TCP

Nếu cần default-deny cho toàn namespace (bảo vệ tất cả pod, không chỉ Redis):

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}    # select tất cả pod trong namespace
  policyTypes:
    - Ingress
    - Egress

Sau đó viết policy allow tường minh cho từng service. Thứ tự apply không quan trọng — Kubernetes hợp nhất tất cả policies, allow thắng nếu có bất kỳ policy nào permit traffic đó.

Verify policy với Cilium:

cilium policy get
cilium connectivity test --test-namespace production

Verify với Calico:

calicoctl get networkpolicy -n production
# Simulate từ pod khác
kubectl exec -it test-pod -- nc -zv redis-svc 6379
9

Cloud-managed Redis: ElastiCache, Memorystore, Azure Cache

Managed Redis service mỗi cloud có cơ chế network isolation riêng:

AWS ElastiCache

  • Bắt buộc chạy trong VPC (không có public endpoint).
  • Cần tạo Subnet Group: chỉ định tập subnet (isolation tier) ElastiCache sẽ deploy vào.
  • Gắn Security Group vào cluster — áp dụng cùng nguyên tắc peer SG source như mục 4.
  • ElastiCache không có egress ra Internet — route table của subnet phải chứa "local only".
# Terraform: ElastiCache subnet group + SG
resource "aws_elasticache_subnet_group" "redis" {
  name       = "redis-subnet-group"
  subnet_ids = [aws_subnet.cache_a.id, aws_subnet.cache_b.id]
}

resource "aws_elasticache_replication_group" "redis" {
  replication_group_id = "prod-redis"
  description          = "Production Redis"
  node_type            = "cache.r7g.large"
  subnet_group_name    = aws_elasticache_subnet_group.redis.name
  security_group_ids   = [aws_security_group.redis.id]
  at_rest_encryption_enabled  = true
  transit_encryption_enabled  = true
}

GCP Memorystore

  • Không có public IP. Kết nối qua VPC peering với service producer network.
  • Khi tạo instance, chỉ định VPC network — Memorystore tự tạo peering với consumer VPC.
  • Firewall rule vẫn cần để kiểm soát traffic trong VPC (mục 5).
gcloud redis instances create prod-redis \
  --size=5 \
  --region=us-central1 \
  --network=vpc-prod \
  --tier=STANDARD_HA

Azure Cache for Redis

  • Có thể bật VNet integration (Private Endpoint hoặc VNet injection tùy tier).
  • Premium tier: hỗ trợ VNet injection — Redis endpoint có IP trong VNet của bạn.
  • Standard/Basic tier: dùng Azure Private Endpoint để expose vào VNet.

Điểm chung: managed Redis không expose public IP khi cấu hình đúng, nhưng bạn vẫn cần kiểm soát ai trong VPC được kết nối qua Security Group / firewall rule.

10

VPC Peering & Transit Gateway

Khi app VPC và Redis VPC là hai VPC riêng biệt (ví dụ: shared services VPC, hoặc multi-account), có hai cách kết nối:

VPC Peering

  • Kết nối point-to-point giữa hai VPC.
  • Traffic đi qua AWS backbone, không qua Internet.
  • Cần thêm route table entry ở cả hai VPC.
  • Hạn chế: không transitive — nếu A peer B, B peer C, thì A không nói chuyện được với C qua B.
App VPC (10.1.0.0/16)  <---- VPC Peering ---->  Redis VPC (10.2.0.0/16)

Route table app VPC:
  10.2.0.0/16 → pcx-xxxxxxxxx (peering connection)

Route table Redis VPC:
  10.1.0.0/16 → pcx-xxxxxxxxx

Transit Gateway

  • Hub-and-spoke: nhiều VPC kết nối qua một Transit Gateway trung tâm.
  • Transitive routing: A, B, C đều attach vào TGW → A nói chuyện được với C.
  • Hỗ trợ cross-region, cross-account.
  • Dùng Route Table TGW để kiểm soát VPC nào được đi đến VPC nào.
App VPC ─┐
Worker VPC ─┤── Transit Gateway ──── Redis VPC
Admin VPC ─┘
                (TGW Route Table kiểm soát granular)

Cả hai cách đều tránh routing traffic qua Internet. Security Group vẫn áp dụng độc lập — kể cả khi VPC peering đã thiết lập, SG phải có rule permit nguồn từ peer VPC CIDR hoặc prefix list.

11

Bastion Host Pattern

Redis ở isolated subnet không có public IP. Khi cần admin trực tiếp (chạy redis-cli, kiểm tra key, debug), cần một điểm trung chuyển: bastion host.

Admin machine
    |
    | SSH (public Internet → bastion public IP)
    |
Bastion Host (public subnet: 10.0.1.10)
    |
    | SSH tunnel → Redis (isolated subnet: 10.0.20.5:6379)
    |
Redis Instance

Thiết lập SSH tunnel:

# Tạo tunnel: port 6379 local → redis.internal:6379 qua bastion
ssh -L 6379:redis.internal:6379 -N [email protected]

# Trong terminal khác, kết nối qua tunnel
redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASSWORD"

Hoặc dùng ~/.ssh/config để tiện lợi:

Host redis-tunnel
  HostName bastion.example.com
  User ec2-user
  IdentityFile ~/.ssh/prod-key.pem
  LocalForward 6379 redis.prod.internal:6379
  ServerAliveInterval 60
ssh redis-tunnel -N &
redis-cli -h 127.0.0.1 -p 6379

Yêu cầu với bastion host:

  • Hardened OS: chỉ chạy SSH daemon, không có ứng dụng khác.
  • SSH key only, không accept password auth.
  • MFA cho SSH (ví dụ: duo-unix, google-authenticator PAM).
  • Audit log mọi command (auditd, hoặc tlog).
  • Chỉ SSH inbound từ IP tĩnh của team (corp NAT), không phải 0.0.0.0/0.
  • Không cài Redis client, curl, wget trên bastion — chỉ SSH.

Security Group bastion:

bastion_sg:
  ingress:
    - protocol: tcp
      port: 22
      cidr: 203.0.113.0/30   # corp office CIDR, không phải 0.0.0.0/0
  egress:
    - protocol: tcp
      port: 22
      destination_sg: redis_sg   # chỉ SSH đến Redis host
      # hoặc: destination: 10.0.20.0/24
12

AWS Session Manager — Không Cần Bastion Port

AWS Systems Manager Session Manager là phương án thay thế bastion không cần mở port SSH nào trên firewall:

  • Instance chạy SSM Agent và có IAM role với quyền ssm:StartSession.
  • Kết nối đi qua SSM endpoint (HTTPS/443 outbound từ instance) — không cần inbound port 22.
  • Auth qua IAM, có thể yêu cầu MFA.
  • Mọi session được ghi lại vào CloudWatch Logs hoặc S3.

Port forwarding đến Redis qua SSM:

# Mở tunnel: port 6379 local → redis.internal:6379 qua instance i-0abc123
aws ssm start-session \
  --target i-0abc123def456 \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{"host":["redis.prod.internal"],"portNumber":["6379"],"localPortNumber":["6379"]}'

# Kết nối local (tunnel đang mở)
redis-cli -h 127.0.0.1 -p 6379 -a "$REDIS_PASSWORD"

IAM policy tối thiểu cho user cần access Redis qua SSM:

{
  "Effect": "Allow",
  "Action": [
    "ssm:StartSession",
    "ssm:TerminateSession",
    "ssm:ResumeSession"
  ],
  "Resource": [
    "arn:aws:ec2:us-east-1:123456789:instance/i-0abc123def456",
    "arn:aws:ssm:*:*:document/AWS-StartPortForwardingSessionToRemoteHost"
  ]
}

SSM Session Manager loại bỏ bastion host hoàn toàn — ít infrastructure hơn, mặt phẳng tấn công nhỏ hơn. Nhược điểm: instance cần outbound HTTPS đến SSM endpoint (hoặc dùng VPC endpoint cho SSM nếu isolated subnet).

13

PrivateLink & Private Endpoint

VPC Peering kết nối hai VPC toàn bộ — mọi subnet trong VPC A có thể reach mọi subnet trong VPC B (tùy route table và SG). AWS PrivateLink cho phép expose một service cụ thể từ provider VPC vào consumer VPC mà không cần peering toàn bộ.

Consumer VPC                    Provider VPC
  App instance                    Redis service
       |                               |
  [VPC Endpoint]  ←── PrivateLink ──  [NLB]
  (private IP                         |
   in consumer VPC)               Redis EC2/ElastiCache

Lợi ích:

  • Consumer VPC không thấy toàn bộ provider VPC, chỉ thấy service endpoint.
  • Không cần non-overlapping CIDR như VPC Peering yêu cầu.
  • Hỗ trợ cross-account: chia sẻ Redis service cho nhiều account mà không peer toàn bộ VPC.

Tạo VPC Endpoint trong consumer VPC:

aws ec2 create-vpc-endpoint \
  --vpc-endpoint-type Interface \
  --vpc-id vpc-consumer-12345 \
  --service-name com.amazonaws.vpce.us-east-1.vpce-svc-0abc123 \
  --subnet-ids subnet-consumer-a subnet-consumer-b \
  --security-group-ids sg-endpoint

Azure Private Endpoint hoạt động tương tự: expose Azure Cache for Redis vào VNet của bạn với private IP, DNS resolve ra IP trong VNet.

Dùng PrivateLink/Private Endpoint khi:

  • Redis ở account/project riêng (shared services model).
  • Consumer VPC và provider VPC có overlapping CIDR (không peer được).
  • Cần granular control: chỉ expose Redis, không expose toàn bộ provider VPC.
14

Egress Restriction Phía Redis

Redis không cần initiate kết nối ra Internet. Restricted egress là một phần của defense in depth: nếu Redis bị compromise (ví dụ qua lỗ hổng trong module hay script injection), attacker không thể exfiltrate data ra ngoài nếu egress bị chặn.

Những gì Redis cần egress:

  • DNS (UDP/TCP 53) — để resolve hostname trong replica config.
  • NTP (UDP 123) — đồng bộ giờ.
  • Replication: Redis replica kết nối master trên data port (6379). Trong cluster, node kết nối nhau qua cluster bus port.
  • Sentinel: Sentinel node cần giao tiếp với nhau (26379) và với Redis node (6379).

Mọi thứ khác nên bị deny. Security Group egress:

redis_sg:
  egress:
    # DNS
    - protocol: udp
      port: 53
      cidr: 10.0.0.2/32    # VPC DNS resolver
    # NTP
    - protocol: udp
      port: 123
      cidr: 169.254.169.123/32  # AWS Time Sync service
    # Replication: chỉ trong redis_sg
    - protocol: tcp
      port: 6379
      destination_sg: redis_sg
    # Cluster bus (nếu dùng Redis Cluster)
    - protocol: tcp
      port: 16379
      destination_sg: redis_sg

Với isolated subnet không có NAT gateway, egress ra Internet đã bị chặn ở level routing. Security Group egress restriction là lớp bổ sung — bảo vệ trong trường hợp ai đó add nhầm NAT gateway vào route table của subnet đó.

15

Cluster Bus, Sentinel Port & Replication

Redis Cluster và Sentinel dùng thêm port ngoài 6379. Firewall phải cho phép các port này giữa các Redis node.

Redis Cluster

  • Data port: 6379 (hoặc port bạn cấu hình).
  • Cluster bus port: data_port + 10000. Ví dụ port 6379 → cluster bus 16379.
  • Cluster bus dùng cho gossip protocol, cluster state exchange, failover negotiation.
# iptables: cho phép cluster bus giữa các Redis node
iptables -A INPUT -p tcp --dport 16379 -s 10.0.20.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 16379 -j DROP

# ufw tương đương
ufw allow from 10.0.20.0/24 to any port 16379

Redis Sentinel

  • Sentinel port mặc định: 26379.
  • Sentinel instance cần kết nối đến Redis master/replica (6379) và đến các Sentinel khác (26379).
# Security Group cho Sentinel node (sg-sentinel)
sentinel_sg:
  ingress:
    - port: 26379
      source_sg: sentinel_sg    # Sentinel ↔ Sentinel
    - port: 26379
      source_sg: app_sg         # App kết nối Sentinel để discover master
  egress:
    - port: 6379
      destination_sg: redis_sg  # Sentinel → Redis master/replica
    - port: 26379
      destination_sg: sentinel_sg

Replication (primary-replica)

  • Replica kết nối master trên data port (6379).
  • Nếu master và replica ở AZ khác nhau nhưng cùng SG, rule tự động cho phép.
  • Nếu dùng SG riêng cho replica, cần thêm egress rule trên replica SG và ingress rule trên master SG từ replica SG.
16

Internal DNS Strategy

Dùng internal DNS thay vì hardcode IP Redis trong config app:

  • App config: REDIS_URL=redis://redis.prod.internal:6379
  • DNS record redis.prod.internal resolve ra private IP của Redis (hoặc ElastiCache primary endpoint).
  • DNS này chỉ có trong private hosted zone — không có public DNS record.

AWS Route 53 Private Hosted Zone:

resource "aws_route53_zone" "internal" {
  name = "prod.internal"
  vpc { vpc_id = aws_vpc.main.id }
}

resource "aws_route53_record" "redis" {
  zone_id = aws_route53_zone.internal.zone_id
  name    = "redis.prod.internal"
  type    = "CNAME"
  ttl     = 60
  records = [aws_elasticache_replication_group.redis.primary_endpoint_address]
}

Lợi ích internal DNS:

  • Failover: thay đổi DNS record khi master failover (Sentinel-based setup không dùng ElastiCache) thay vì phải deploy lại config.
  • Không có public DNS entry → attacker không biết Redis IP từ bên ngoài.
  • Human-readable: redis.prod.internal dễ đọc hơn IP trong log.

Với Kubernetes, redis.production.svc.cluster.local là DNS nội bộ cluster tự động — không cần config thêm.

17

Xác Minh Cô Lập

Sau khi thiết lập, cần kiểm tra thực tế rằng isolation hoạt động đúng:

Từ bên ngoài VPC — port phải đóng

# Scan từ máy ngoài Internet
nmap -sT -p 6379 <redis-private-ip>
# Nếu IP không routable từ Internet → Host seems down / filtered
# Nếu có public IP nhầm → phải thấy: 6379/tcp filtered hoặc closed

# Với nc
nc -zv <redis-ip> 6379 -w 3
# Phải báo: Connection refused hoặc timeout

Từ app subnet — kết nối được

# Từ EC2 instance trong app subnet
nc -zv redis.prod.internal 6379 -w 3
# Phải báo: Connection to redis.prod.internal 6379 port [tcp/*] succeeded!

redis-cli -h redis.prod.internal -p 6379 -a "$REDIS_PASSWORD" PING
# Phải trả về: PONG

Từ subnet không được phép — bị từ chối

# Từ EC2 trong DB subnet (không phải app subnet)
nc -zv redis.prod.internal 6379 -w 3
# Phải báo: Connection refused hoặc timeout

Audit CLIENT LIST

redis-cli -h redis.prod.internal -a "$REDIS_PASSWORD" CLIENT LIST

# Output mẫu:
# id=1 addr=10.0.10.5:45231 laddr=10.0.20.5:6379 ...
# id=2 addr=10.0.10.7:52100 laddr=10.0.20.5:6379 ...
#
# Tất cả addr phải là IP thuộc app subnet (10.0.10.x, 10.0.11.x)
# Nếu thấy IP lạ: alert ngay

Tự động hóa việc kiểm tra này qua CI/CD hoặc cron job hàng tuần là best practice. Nếu một lần scan thấy port 6379 của Redis accessible từ ngoài CIDR cho phép, đó là sự kiện cần điều tra ngay.

18

Anti-patterns

  • SG ingress source = 0.0.0.0/0: mở Redis cho toàn bộ Internet. Thường xảy ra khi copy-paste template firewall rule cho dev rồi quên đổi source.
  • Redis ở public subnet với public IP: ElastiCache không cho phép điều này, nhưng tự-deploy Redis trên EC2 vào public subnet với EIP là anti-pattern nghiêm trọng.
  • SG ingress source = app subnet CIDR thay vì peer SG: kém chặt hơn peer SG — bất kỳ instance nào vào subnet (kể cả instance dev, test) đều connect được Redis prod.
  • Không có NetworkPolicy trong K8s: mặc định pod trong cluster có thể kết nối Redis. Cần default-deny + explicit allow.
  • Bastion SSH allow từ 0.0.0.0/0: bastion là điểm vào duy nhất, phải restrict nguồn SSH vào bastion.
  • Mix prod và dev Redis trong cùng subnet không có phân tách: developer access dev Redis, nhầm endpoint → connect vào prod.
  • Redis có route Internet trong subnet: nếu subnet có NAT gateway, Redis về lý thuyết có thể outbound ra Internet. Isolated subnet không được có NAT.
  • Không audit CLIENT LIST: chỉ biết source IP connection khi incident xảy ra, lúc đó đã muộn.
  • Không test isolation sau khi thay đổi network: thêm peering, thay đổi route table, thêm rule có thể vô tình open port.
19

Best Practices & Checklist

Checklist thiết lập network isolation cho Redis production:

  • Redis ở isolated subnet — route table chỉ có local, không có Internet Gateway hay NAT.
  • Security Group ingress source = peer SG của app tier và worker tier, không phải CIDR.
  • Security Group egress deny all ngoại trừ DNS (53), NTP (123), replication port.
  • NACL subnet cache: deny all inbound ngoại trừ từ app CIDR; deny all outbound ngoại trừ ephemeral ports về app.
  • iptables/ufw trên Redis host: allow 6379 từ app CIDR, drop the rest — lớp dự phòng.
  • Kubernetes: default-deny NetworkPolicy cho namespace, rồi explicit allow từ web/worker pod.
  • Admin access: bastion host (restricted SSH source) hoặc AWS Session Manager — không mở SSH trực tiếp từ Internet vào Redis host.
  • Internal DNS only: không có public DNS record cho Redis IP.
  • Cross-VPC: dùng VPC Peering (đơn giản, point-to-point) hoặc PrivateLink (granular, cross-account).
  • Cluster bus port (16379) và Sentinel port (26379): mở giữa các node Redis, không mở ra ngoài.
  • Verify định kỳ: nmap từ ngoài app subnet, audit CLIENT LIST source IP.
  • Alert khi CLIENT LIST có IP không thuộc app subnet.

Ví dụ production AWS hoàn chỉnh:

VPC: 10.0.0.0/16

Subnets:
  - public:   10.0.1.0/24  (ALB, NAT GW, Bastion)
  - app-a:    10.0.10.0/24 (EC2 web, asg-web)
  - app-b:    10.0.11.0/24 (EC2 worker, asg-worker)
  - cache-a:  10.0.20.0/24 (ElastiCache primary)
  - cache-b:  10.0.21.0/24 (ElastiCache replica)
  - db-a:     10.0.30.0/24 (RDS primary)
  - db-b:     10.0.31.0/24 (RDS standby)

Route tables:
  - public:   0.0.0.0/0 → igw + local
  - app:      0.0.0.0/0 → nat-gw + local
  - cache:    local only
  - db:       local only

Security Group: sg-redis
  Ingress:
    TCP 6379  source: sg-app-web
    TCP 6379  source: sg-app-worker
  Egress:
    (none — deny all)

ElastiCache cluster:
  subnet_group: [cache-a, cache-b]
  security_group: sg-redis
  at_rest_encryption: true
  transit_encryption: true (TLS)
20

Quiz

  1. Tại sao nên dùng peer SG thay vì CIDR block làm ingress source cho Redis Security Group? Liệt kê ít nhất hai lý do.
  2. Cluster Redis 3 shard × 2 replica, port data 6379. Liệt kê tất cả port và direction cần mở giữa các Redis node trong Security Group.
  3. App bị compromise, attacker có shell trong EC2 thuộc sg-app-tier. Attacker cố exfiltrate Redis data ra Internet. Mô tả từng lớp network nào ngăn điều này, và giả sử attacker vượt qua được layer nào là layer dễ nhất.
  4. Bạn có Redis Cluster trong VPC A và app trong VPC B (CIDR 10.1.0.0/16 và 10.2.0.0/16 không overlap). Bạn cần chọn giữa VPC Peering và PrivateLink. Nếu cần cho ba team khác nhau ở ba account khác nhau đều truy cập Redis này, nên dùng cách nào?
  5. Viết NetworkPolicy K8s cho phép chỉ pod có label app=api trong namespace prod kết nối Redis (label app=redis cùng namespace) trên port 6379.

Đáp án gợi ý

  1. (1) Dynamic membership: Auto Scaling Group thêm instance mới vào SG → tự động được phép kết nối mà không cần update CIDR rule. (2) Role-based: instance trong cùng subnet nhưng thuộc SG khác (vd: SG dev hoặc SG database) không connect được Redis — CIDR-based rule sẽ cho tất cả instance trong subnet.
  2. Trong SG redis: ingress TCP 6379 source sg-redis; ingress TCP 16379 source sg-redis. Egress: TCP 6379 dest sg-redis; TCP 16379 dest sg-redis. (Port 16379 = cluster bus = 6379 + 10000.)
  3. Lớp 1 — Route table cache subnet: không có route Internet → Redis host không reachable từ Internet, và Redis không reach được Internet. Lớp 2 — NACL subnet: deny outbound đến 0.0.0.0/0. Lớp 3 — Security Group egress: deny all. Lớp 4 — iptables/ufw trên host Redis: drop outbound đến non-VPC IP. Layer dễ vượt nhất với attacker có shell trong EC2 app là CIDR-based firewall rule (nếu không dùng peer SG) — nhưng với isolated subnet + deny egress, attacker vẫn không exfil được dù đọc được data từ Redis vì không có đường ra ngoài từ phía Redis. Phải exfil qua app instance.
  4. PrivateLink: expose Redis service dưới dạng VPC Endpoint Service, mỗi account consumer tạo VPC Endpoint trỏ vào service. Không cần VPC Peering giữa ba account, không bị giới hạn CIDR, và provider kiểm soát ai được phép attach (whitelist consumer account). VPC Peering sẽ phức tạp hơn khi có nhiều consumer (nhiều peering connection, quản lý route table).
  5. apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
      name: redis-allow-api
      namespace: prod
    spec:
      podSelector:
        matchLabels:
          app: redis
      policyTypes: [Ingress]
      ingress:
        - from:
            - podSelector:
                matchLabels:
                  app: api
          ports:
            - port: 6379
              protocol: TCP

Bài tiếp theo

Bài 118 đi vào Secrets Management — lưu và rotate Redis password an toàn với HashiCorp Vault, AWS Secrets Manager và Kubernetes Secret, bao gồm dynamic credentials và rotation không downtime.

Tham khảo