Network Policy Security — Default-Deny và Microsegmentation
Vì sao network phẳng là món quà cho kẻ tấn công
Mặc định của Kubernetes là một mạng phẳng, allow-all: mọi Pod nói chuyện được với mọi Pod khác trong cluster, trên mọi cổng, và thường tự do gọi ra Internet. Đây là một quyết định thiết kế ưu tiên tính dễ dùng — và nó là một trong những default nguy hiểm nhất với bảo mật production.
Hậu quả cụ thể: khi một Pod bị chiếm (qua RCE, qua image độc hại, qua supply chain), kẻ tấn công đứng trên một mạng phẳng nơi mọi thứ đều với tới được. Họ quét cluster, tìm database không xác thực, gọi internal API, đọc metadata service, và quan trọng nhất — gọi ra C2 server bên ngoài để exfiltrate dữ liệu hoặc nhận lệnh. Một Pod compromise trên mạng phẳng nhanh chóng trở thành recon và pivot toàn cluster. Đây chính là lateral movement trong threat model (file 01), và Network Policy là cơ chế chính chống lại nó.
Network Policy biến mạng cluster từ "phẳng và tin tưởng" thành microsegmented và zero-trust: mỗi luồng traffic phải được cho phép tường minh, và mọi thứ không được phép thì bị chặn. Mục tiêu không phải là chặn traffic hợp lệ, mà là đảm bảo một Pod bị chiếm chỉ nói chuyện được với đúng những gì nó cần — cắt đứt cả lateral movement (đông-tây) lẫn exfiltration (bắc-nam).
Internal model: Network Policy hoạt động thế nào
NetworkPolicy là một Kubernetes resource namespace-scoped, chọn Pod qua podSelector và định nghĩa rule ingress (traffic vào) và/hoặc egress (traffic ra). Vài tính chất ngữ nghĩa quan trọng quyết định cách tư duy:
Additive và allow-only: Network Policy chỉ cho phép, không có "deny rule" tường minh. Cơ chế "deny" đến từ việc: ngay khi một Pod bị chọn bởi ít nhất một NetworkPolicy cho một chiều (ingress hoặc egress), mọi traffic chiều đó không khớp rule nào sẽ bị chặn. Pod không bị policy nào chọn thì vẫn allow-all (mặc định cũ).
Ingress và egress độc lập: một policy có thể kiểm soát chỉ ingress, chỉ egress, hoặc cả hai. Đây là cơ sở của pattern default-deny: tạo một policy chọn mọi Pod nhưng không cho phép gì → chặn sạch → rồi mở theo whitelist.
Selector theo nhãn, không theo IP: rule chọn nguồn/đích qua
podSelectorvànamespaceSelector(nhãn), phản ánh đúng bản chất động của Pod (IP thay đổi liên tục). Cũng cóipBlockcho CIDR cố định.
Trên GKE, Network Policy enforcement phải được bật — hoặc qua add-on Calico (mô hình cũ), hoặc native qua Dataplane V2 (khuyến nghị). Nếu không bật, các NetworkPolicy bạn viết không được enforce — chúng tồn tại như object nhưng vô tác dụng, một cái bẫy im lặng nguy hiểm.
Pattern default-deny: nền tảng của microsegmentation
Bước đầu tiên và quan trọng nhất là thiết lập default-deny cho mỗi namespace ứng dụng. Một policy chọn mọi Pod (podSelector: {}) và khai báo cả hai policyTypes nhưng không cho phép gì:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: team-a
spec:
podSelector: {} # chọn MỌI Pod trong namespace
policyTypes:
- Ingress
- Egress
# không có ingress/egress rule => chặn sạch cả hai chiềuSau khi đặt default-deny, mọi traffic phải được mở tường minh. Đây là điểm chuyển từ allow-all sang zero-trust. Tiếp theo whitelist đúng những gì cần — và đừng quên hai luồng hạ tầng thiết yếu mà default-deny sẽ chặn:
# Cho phép Pod resolve DNS (nếu không, mọi thứ vỡ vì không resolve được tên)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: team-a
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# Cho phép frontend gọi backend (whitelist đông-tây cụ thể)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
namespace: team-a
spec:
podSelector:
matchLabels:
app: backend
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080Cảnh báo thực hành quan trọng: đặt default-deny egress mà quên cho phép DNS (port 53 tới kube-dns) sẽ làm vỡ gần như mọi workload — chúng không resolve được tên service. Đây là lỗi phổ biến nhất khi triển khai default-deny. Luôn whitelist DNS đầu tiên. Tương tự, cân nhắc traffic tới control plane, metadata (nếu cần), và health check.
Dataplane V2: nền tảng eBPF cho Network Policy nâng cao
GKE Dataplane V2 là datapath thế hệ mới dựa trên Cilium và eBPF, thay thế mô hình kube-proxy/iptables truyền thống. Theo tài liệu GKE Dataplane V2, nó mang lại ba lợi ích trực tiếp cho bảo mật:
Enforcement hiệu năng cao: eBPF chèn logic vào kernel datapath, enforce Network Policy mà không cần chuỗi iptables dài (vốn scale kém khi số rule/service tăng). Ở cluster lớn, iptables-based enforcement trở thành bottleneck; eBPF thì không.
Network Policy logging: Dataplane V2 cho phép log các connection bị allow/deny bởi Network Policy — khả năng quan sát mà mô hình iptables thiếu. Đây là công cụ vàng để debug policy ("vì sao connection này bị chặn?") và để detect ("Pod này đang cố gọi ra đâu?").
Native enforcement, không cần add-on riêng: Dataplane V2 tích hợp sẵn enforcement, không cần cài Calico riêng. Bật khi tạo cluster (
--enable-dataplane-v2); trên Autopilot, Dataplane V2 là mặc định.
Quan trọng: nhiều tính năng bảo mật mạng nâng cao của GKE chỉ khả dụng trên Dataplane V2 — đáng chú ý nhất là FQDN-based policy (phần dưới). Vì vậy với cluster mới, bật Dataplane V2 là lựa chọn mặc định đúng.
FQDN-based egress: kiểm soát egress theo tên miền
Network Policy chuẩn kiểm soát egress theo Pod/namespace selector hoặc ipBlock (CIDR). Nhưng với traffic ra ngoài cluster (gọi API bên thứ ba, registry, dịch vụ SaaS), bạn thường biết tên miền chứ không phải IP — và IP của các dịch vụ đó thay đổi liên tục (CDN, load balancer). ipBlock trở nên bất khả thi.
Trên Dataplane V2, GKE hỗ trợ FQDN Network Policy qua custom resource (FQDNNetworkPolicy), cho phép định nghĩa egress rule theo tên miền (FQDN) thay vì IP:
apiVersion: networking.gke.io/v1alpha3
kind: FQDNNetworkPolicy
metadata:
name: allow-egress-to-apis
namespace: team-a
spec:
podSelector:
matchLabels:
app: payment-service
egress:
- matchName:
- api.stripe.com
- storage.googleapis.com
ports:
- protocol: TCP
port: 443Cơ chế: Dataplane V2 theo dõi DNS resolution và ánh xạ FQDN được phép sang IP thực tế một cách động, rồi enforce egress dựa trên mapping đó. Điều này cho phép một chính sách bảo mật cực mạnh: một payment service chỉ được gọi ra đúng api.stripe.com và không gì khác — kể cả khi bị chiếm, nó không exfiltrate được dữ liệu ra C2 server tùy ý vì egress tới mọi domain khác bị chặn.
Giới hạn cần biết: FQDN policy dựa trên DNS, nên nó có các ràng buộc của cơ chế DNS-based (phụ thuộc Pod dùng đúng DNS resolver của cluster, có matchName cho tên chính xác và matchPattern cho wildcard với hỗ trợ giới hạn). Nó không phải là tường lửa L7 đầy đủ — với kiểm soát L7 sâu (path, header, method), cần service mesh (Istio/Cloud Service Mesh) hoặc egress gateway. Nhưng cho mục tiêu "chỉ cho gọi ra các domain whitelist", FQDN policy là công cụ đúng và đủ.
Production architecture patterns
Pattern: default-deny mọi namespace, whitelist tối thiểu
Chuẩn nền tảng cho mọi cluster production: mỗi namespace ứng dụng có một default-deny ingress+egress, sau đó whitelist đúng các luồng cần (DNS, các service-to-service cụ thể, các FQDN egress cần thiết). Đây là zero-trust mạng: không có traffic nào "ngầm hiểu là OK". Quản lý các policy này như code (GitOps) cùng với manifest của workload, để mỗi service tự khai báo các luồng nó cần — biến network policy thành một phần của contract của service.
Pattern: namespace isolation cho multi-tenant
Với multi-tenant theo namespace, tạo policy chặn cross-namespace traffic mặc định: Pod trong namespace tenant A chỉ nhận ingress từ chính namespace A (và từ ingress controller dùng chung). Điều này biến namespace từ một soft boundary (chỉ tên + RBAC) thành một boundary có cả cô lập mạng — một tenant bị chiếm không quét được Pod của tenant khác. (Lưu ý: namespace vẫn không cô lập kernel; với multi-tenant nhạy cảm, kết hợp node pool riêng + gVisor như file 05.)
Pattern: FQDN egress cho workload xử lý dữ liệu nhạy cảm
Với workload chạm dữ liệu nhạy cảm (payment, PII), áp FQDN egress whitelist chặt: chỉ cho gọi ra đúng các API/dịch vụ cần. Đây là biện pháp anti-exfiltration mạnh nhất ở tầng mạng — kể cả khi workload bị chiếm hoàn toàn, dữ liệu không có đường ra ngoài các domain đã duyệt. Kết hợp với Network Policy logging để alert khi có nỗ lực gọi ra domain ngoài whitelist (dấu hiệu compromise).
Common mistakes / anti-patterns
1. Không bật Network Policy enforcement. Viết NetworkPolicy nhưng cluster không bật Dataplane V2/Calico. Vì sao xảy ra: tưởng policy tự enforce. Hệ quả: policy là object vô tác dụng, mạng vẫn phẳng allow-all, cảm giác an toàn giả. Phòng tránh: bật Dataplane V2; kiểm chứng enforcement thực sự bằng cách test một connection bị chặn.
2. Default-deny egress mà quên DNS. Vì sao xảy ra: chỉ nghĩ tới traffic app, quên DNS là hạ tầng. Hệ quả: mọi Pod không resolve được tên → vỡ hàng loạt, tưởng nhầm là lỗi khác. Phòng tránh: luôn whitelist egress UDP/TCP 53 tới kube-dns trước khi đặt default-deny.
3. Để mạng phẳng hoàn toàn (không policy nào). Vì sao xảy ra: "phức tạp, để sau". Hệ quả: lateral movement tự do, một Pod compromise = recon toàn cluster, exfiltration không bị chặn. Phòng tránh: ít nhất đặt default-deny + whitelist cơ bản cho mọi namespace production — đây là baseline, không phải nâng cao.
4. Whitelist egress bằng ipBlock cho dịch vụ động. Vì sao xảy ra: cố kiểm soát egress ra API bên thứ ba bằng CIDR. Hệ quả: IP đổi → policy hỏng hoặc phải mở CIDR rộng (vô nghĩa về bảo mật). Phòng tránh: dùng FQDN policy trên Dataplane V2 cho egress theo tên miền.
5. Bỏ qua Network Policy logging. Vì sao xảy ra: bật policy rồi quên quan sát. Hệ quả: không biết policy có chặn nhầm traffic hợp lệ không, không phát hiện được Pod đang cố gọi ra đâu bất thường. Phòng tránh: bật Network Policy logging của Dataplane V2; dùng nó để cả debug lẫn detect.
GCP-native implementation guidance
# Bật Dataplane V2 khi tạo cluster (native Network Policy + FQDN + logging)
gcloud container clusters create CLUSTER \
--region REGION \
--enable-dataplane-v2 \
--enable-ip-alias
# Bật Network Policy logging (Dataplane V2)
kubectl apply -f - <<'EOF'
apiVersion: networking.gke.io/v1alpha1
kind: NetworkLogging
metadata:
name: default
spec:
cluster:
allow:
log: true
delegate: false
deny:
log: true # log mọi connection bị chặn — vàng cho detection
delegate: false
EOF
# Kiểm chứng default-deny thực sự enforce: từ một Pod test, thử gọi Pod khác
kubectl run test --rm -it --image=nicolaka/netshoot -n team-a -- \
curl -m 3 http://backend.team-a:8080
# Kỳ vọng: timeout/refused nếu chưa whitelist => enforcement đang hoạt độngKiểm chứng enforcement bằng test thực tế là bước không thể bỏ: một default-deny "trông đúng" trên YAML nhưng không enforce (vì Dataplane V2 chưa bật) là một trong những lỗ hổng âm thầm phổ biến nhất. Luôn xác nhận một connection đáng lẽ bị chặn thực sự bị chặn.
Operational implications
Network Policy có một đặc tính vận hành đáng lưu ý: nó là lớp phòng thủ mà rủi ro chính không phải bị bỏ qua, mà là cấu hình quá chặt gây outage. Khác với pod security (dễ bị quên), network policy thường được nhớ tới — nhưng một default-deny thiếu DNS, hay một whitelist sót một luồng service-to-service, làm vỡ ứng dụng một cách khó debug (timeout không rõ nguyên nhân). Đây là lý do Network Policy logging và pattern rollout từng bước (default-deny ở môi trường test trước, quan sát log, rồi production) quan trọng — bảo mật mạng là nơi trade-off security/reliability rõ rệt nhất, và quan sát là cách cân bằng nó.
Hệ quả thứ hai: vì policy theo nhãn, kỷ luật gắn nhãn (label) trở thành một vấn đề bảo mật. Một Pod thiếu nhãn app: frontend sẽ không khớp rule whitelist và bị chặn; ngược lại, một nhãn đặt sai có thể vô tình cấp quyền truy cập. Network policy biến label hygiene từ vấn đề tổ chức thành vấn đề an ninh — và là một lý do nữa để quản lý policy + workload + nhãn cùng nhau như code. Kết hợp với pod security (file 06) chống escape và node security (file 05) chống credential theft, network policy hoàn tất việc cô lập runtime: ngay cả khi một Pod bị chiếm và không escape được, nó cũng không lan ngang hay exfiltrate được.