Khi cluster Kubernetes trở thành mục tiêu tấn công
Hồi năm ngoái mình có một bài học đau. Server bị brute-force SSH lúc nửa đêm, phải thức dậy xử lý gấp trong khi đầu óc vẫn còn mơ màng. Từ đó mình có thói quen: bất cứ hạ tầng nào triển khai, bảo mật phải được setup ngay từ ngày đầu — không phải sau khi có vấn đề mới nghĩ đến.
Kubernetes cũng không ngoại lệ. Nhiều team dựng cluster xong, ứng dụng chạy được là mừng, deploy production luôn — bảo mật để sau. Và “sau” thường không bao giờ đến cho đến khi có sự cố.
Đây không phải lý thuyết. Năm 2018, team RedLock phát hiện cluster Kubernetes của Tesla expose API server ra internet không có authentication — kẻ tấn công dùng luôn để đào tiền điện tử. Container chạy với quyền root, pod giao tiếp tự do, secret lưu dạng base64 không mã hóa, API server không giới hạn truy cập. Đây là thực trạng của nhiều cluster production mình từng review, không chỉ Tesla.
Vì sao Kubernetes mặc định không an toàn?
K8s được thiết kế để linh hoạt và dễ dùng trước. Bảo mật là lớp bạn tự thêm vào, không phải có sẵn. Cụ thể:
- RBAC không được bật mặc định trên một số phiên bản cũ hoặc managed cluster khi mới tạo.
- Pod chạy với quyền root nếu bạn không chỉ định SecurityContext.
- Network Policy không tồn tại cho đến khi bạn tự viết — mọi pod trong cluster thoải mái nói chuyện với nhau.
- Secret chỉ là base64: ai có quyền đọc etcd hoặc Secret resource là đọc được toàn bộ.
- Service Account mặc định được mount vào mọi pod và có thể gọi Kubernetes API.
Ghép những điểm này lại: một container bị compromise là đủ để kẻ tấn công leo thang đặc quyền lên toàn cluster. Mình từng thử trên lab — mất chưa đến 10 phút từ một pod bị chiếm đến khi có quyền cluster-admin.
Các lớp bảo mật cần thiết lập
1. RBAC — Kiểm soát quyền truy cập
Nền tảng của mọi thứ là least privilege: ai cần gì thì có quyền đó, không hơn một chút.
# Tạo Role chỉ cho phép đọc Pod trong namespace "app"
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: app
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: app
subjects:
- kind: ServiceAccount
name: my-service-account
namespace: app
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Cần đặc biệt tránh ClusterRole với verbs: ["*"] hoặc resources: ["*"] — đây là lỗi mình thấy ở hầu hết cluster lần đầu được setup vội. Audit định kỳ để phát hiện quyền thừa:
# Kiểm tra quyền của service account
kubectl auth can-i --list --as=system:serviceaccount:app:my-service-account
# Tìm ClusterRoleBinding gán cho anonymous user
kubectl get clusterrolebindings -o json | jq '.items[] | select(.subjects[]?.name=="system:anonymous")'
2. Pod Security — Giới hạn những gì container có thể làm
Từ Kubernetes 1.25, Pod Security Admission thay thế PodSecurityPolicy (đã bị deprecated từ 1.21). Enforce policy ở cấp namespace:
# Gán policy "restricted" cho namespace production
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
Trong spec của Pod/Deployment, luôn khai báo SecurityContext rõ ràng:
securityContext:
runAsNonRoot: true
runAsUser: 1000
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
Container chạy với UID 1000, filesystem read-only, drop hết Linux capabilities. Ngay cả khi container bị chiếm, kẻ tấn công cũng không làm được nhiều — họ mắc kẹt trong một sandbox chật hẹp.
3. Network Policy — Tường lửa giữa các Pod
Mặc định, pod A có thể gọi thẳng pod B dù chẳng có lý do gì để làm vậy. Network Policy cho phép bạn định nghĩa chính xác traffic nào được phép đi qua:
# Chỉ cho phép frontend gọi backend, không gì khác
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: backend-policy
namespace: app
spec:
podSelector:
matchLabels:
role: backend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
role: database
ports:
- protocol: TCP
port: 5432
Một lưu ý quan trọng: Network Policy cần CNI plugin hỗ trợ — Calico, Cilium, hoặc Weave đều được. Nếu đang dùng Flannel thì cẩn thận: policy được tạo ra nhưng hoàn toàn không có tác dụng, không có cảnh báo gì hết.
4. Secret Management — Đừng tin Kubernetes Secret mặc định
Base64 không phải mã hóa. Ai có quyền đọc etcd hoặc kubectl get secret là lấy được plaintext ngay. Hai hướng xử lý:
Hướng 1 — Bật encryption at rest cho etcd (cần quyền control plane):
# Tạo EncryptionConfiguration
cat > /etc/kubernetes/encryption-config.yaml <<EOF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: $(head -c 32 /dev/urandom | base64)
- identity: {}
EOF
# Thêm vào kube-apiserver: --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
Hướng 2 (khuyên dùng hơn) — External Secrets Operator kết nối với HashiCorp Vault hoặc AWS Secrets Manager. Secret không bao giờ lưu trong etcd, chỉ được inject vào pod lúc runtime:
# Cài External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace
5. Audit Logging và Runtime Security
Phát hiện sớm hành vi bất thường quan trọng không kém việc phòng thủ. Bật audit log ở API server:
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: RequestResponse
verbs: ["delete", "create"]
resources:
- group: ""
resources: ["pods"]
Falco bổ sung thêm lớp runtime detection — phát hiện shell spawning trong container, file access bất thường, network connection lạ, tất cả theo thời gian thực. Trên production thì đây là thứ không thể thiếu:
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco --namespace falco --create-namespace \
--set falco.grpc.enabled=true \
--set falco.grpcOutput.enabled=true
Checklist theo thứ tự ưu tiên
NSA và CISA từng ra advisory chung về K8s security, kết luận rằng không có silver bullet — chỉ có nhiều lớp phòng thủ chồng lên nhau mới hiệu quả. Một lớp bị qua thì lớp tiếp theo chặn lại. Thứ tự ưu tiên thực tế:
- Không expose API server ra internet — dùng VPN hoặc bastion host. Đây là lỗi nghiêm trọng nhất và dễ fix nhất.
- Cập nhật Kubernetes thường xuyên — K8s vá CVE theo chu kỳ release, phiên bản cũ có lỗ hổng đã biết công khai.
- RBAC với least privilege — review định kỳ, xóa quyền không còn dùng.
- SecurityContext cho mọi Pod — đặt trong Helm chart hoặc template mặc định để không bị bỏ sót.
- Network Policy — bắt đầu bằng deny-all, mở dần theo nhu cầu thực tế.
- Scan image trước khi deploy — Trivy hoặc Grype tích hợp thẳng vào CI/CD pipeline.
- Secret management đúng cách — tối thiểu encryption at rest, lý tưởng nhất là external vault.
- Audit log + runtime monitoring — Falco cho production, không thương lượng.
# Scan image với Trivy trước khi push
trivy image --severity HIGH,CRITICAL your-image:tag
# Kiểm tra misconfiguration trong YAML manifest
trivy config ./k8s-manifests/
# Audit cluster theo CIS Kubernetes Benchmark
docker run --pid=host --userns=host --rm -v /etc:/etc:ro \
-v /var:/var:ro -v /usr/lib/systemd:/usr/lib/systemd:ro \
aquasec/kube-bench:latest
Điều mình hay nhấn mạnh với team: bảo mật phải là một phần của quy trình, không phải task riêng lẻ. Tích hợp Trivy vào CI pipeline, enforce Pod Security tại admission controller, đưa RBAC review vào sprint retro — khi nó thành thói quen thì không còn cảm giác nặng nề nữa.
Cluster Kubernetes không bao giờ đạt trạng thái “an toàn tuyệt đối”. Nhưng với những bước trên, bạn đã đẩy rủi ro xuống mức chấp nhận được — và quan trọng hơn, bạn sẽ biết khi có chuyện bất thường xảy ra, thay vì phát hiện ra sau khi thiệt hại đã xong.

