Module 27 Security 5 labs

Kubernetes Security và Policy-as-Code

Nắm vững các lớp bảo mật Kubernetes: Pod Security Standards, RBAC least-privilege, policy engine (OPA Gatekeeper / Kyverno), NetworkPolicy, image scanning admission, và runtime threat detection với Falco — áp dụng Policy-as-Code để enforce security một cách declarative và kiểm toán được.

Công cụ thực hành kubectl, Helm, kind/minikube, OPA, Kyverno, Falco, Trivy
Nền tảng Kubernetes (kind/minikube/EKS/AKS), Linux (WSL2)
Thời điểm phát hành 23/05/2026
Ngày biên soạn 23/05/2026
Người biên soạn Trần Văn Hòa — Microsoft Certified Trainer (MCT)

Mục tiêu học tập

1. Lý thuyết cốt lõi

1.1. The 4Cs of Cloud Native Security

Mô hình bảo mật phân lớp của CNCF: Cloud (IAM, VPC, firewall của nhà cung cấp) → Cluster (API server auth, RBAC, network policy, etcd encryption) → Container (image scanning, securityContext, read-only filesystem) → Code (SAST, dependency audit, secret management). Mỗi lớp phải độc lập bảo mật; vi phạm lớp trong không được leo thang lên lớp ngoài.

1.2. Pod Security Standards (PSS) — kể từ Kubernetes 1.25+

PSS thay thế PodSecurityPolicy (bị xóa trong K8s 1.25). Ba mức:

MứcMô tảÁp dụng cho
PrivilegedKhông hạn chế, cho phép mọi thứSystem workload (kube-system, CNI)
BaselineNgăn privilege escalation rõ ràngApp thông thường không cần root
RestrictedHardened tối đa, drop ALL capabilitiesSecurity-sensitive workload

Enforce qua namespace label: pod-security.kubernetes.io/enforce: restricted. Có thể dùng warnaudit song song để chuyển đổi dần.

1.3. RBAC — Role-Based Access Control

RBAC trong Kubernetes hoạt động theo nguyên tắc default deny: subject không có binding nào thì không có quyền gì. Các thành phần:

Nguyên tắc RBAC Least-Privilege

  • Tránh dùng cluster-admin cho workload — chỉ dùng cho bootstrap/emergency.
  • Không dùng * trong resources hoặc verbs.
  • Kiểm tra quyền thực tế: kubectl auth can-i --list --as=system:serviceaccount:ns:sa.
  • Dùng kubectl-who-can hoặc rakkess để audit toàn cluster.

1.4. Policy-as-Code: OPA Gatekeeper vs Kyverno

Cả hai đều là Admission Webhook — intercepting mọi request đến API server trước khi persist vào etcd.

Tiêu chíOPA GatekeeperKyverno
Ngôn ngữ policyRego (học curve cao hơn)YAML-native (dễ học)
MutationAssign (qua Mutation webhook)ClusterPolicy mutate
Generate resourceKhông (cần External Data)Có (generate NetworkPolicy...)
Audit modeCó (constraint.spec.enforcementAction: audit)Có (validationFailureAction: audit)

1.5. NetworkPolicy — Zero-Trust Network trong Cluster

Mặc định, mọi pod có thể giao tiếp tự do trong cluster. NetworkPolicy (CNI phải hỗ trợ: Calico, Cilium, Weave) cho phép white-list ingress/egress theo podSelector, namespaceSelector, ipBlock. Pattern chuẩn: default-deny-all rồi mở từng luồng cần thiết.

1.6. Image Security & Admission — Supply Chain Defense

Trivy scan image tìm CVE, misconfiguration, secret lộ trong layer. Tích hợp vào admission: Kyverno ClusterPolicy verify images hoặc Cosign signature verification ngăn image không được ký deploy vào production. Kết hợp với SBOM (Module 26) tạo chuỗi cung ứng kiểm toán đầy đủ.

1.7. Falco — Runtime Threat Detection

Falco (CNCF graduated) dùng eBPF/kernel module để stream syscall events, so sánh với rules engine. Phát hiện: shell exec trong container, read /etc/shadow, mount sensitive path, network outbound bất thường, privilege escalation. Output: stdout, syslog, gRPC, HTTP endpoint (tích hợp SIEM/Slack). Rules viết bằng YAML, có thể custom và kế thừa từ default rule set.

2. Thực hành (Labs)

LAB-131

RBAC Least-Privilege cho Application ServiceAccount

kubectl · kind

🎯 Mục tiêu: Tạo ServiceAccount riêng cho một app, gán Role tối thiểu chỉ đọc ConfigMap trong namespace của nó; xác nhận app không thể đọc Secret hoặc tài nguyên namespace khác.

🧰 Công cụ / nền tảng: kubectl, kind (local cluster), terminal Linux/WSL2.

📦 Chuẩn bị: Có cluster kind đang chạy (kind create cluster --name sec-lab); kubectl context trỏ đúng.

▶️ Các bước:

# 1. Tạo namespace và ServiceAccount
kubectl create namespace app-ns
kubectl create serviceaccount app-reader -n app-ns

# 2. Tạo Role chỉ cho phép get/list ConfigMap
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: configmap-reader
  namespace: app-ns
rules:
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list"]
EOF

# 3. Bind Role vào ServiceAccount
cat <<EOF | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: app-reader-binding
  namespace: app-ns
subjects:
- kind: ServiceAccount
  name: app-reader
  namespace: app-ns
roleRef:
  kind: Role
  name: configmap-reader
  apiGroup: rbac.authorization.k8s.io
EOF

# 4. Kiểm tra quyền: ĐƯỢC đọc ConfigMap
kubectl auth can-i get configmaps \
  --as=system:serviceaccount:app-ns:app-reader \
  -n app-ns
# Expected: yes

# 5. Kiểm tra quyền: KHÔNG được đọc Secret
kubectl auth can-i get secrets \
  --as=system:serviceaccount:app-ns:app-reader \
  -n app-ns
# Expected: no

# 6. Kiểm tra KHÔNG có quyền ở namespace khác
kubectl auth can-i get configmaps \
  --as=system:serviceaccount:app-ns:app-reader \
  -n default
# Expected: no

# 7. List toàn bộ quyền của SA này
kubectl auth can-i --list \
  --as=system:serviceaccount:app-ns:app-reader \
  -n app-ns

✅ Kết quả mong đợi: Bước 4 trả về yes; bước 5 và 6 trả về no. Output của bước 7 chỉ liệt kê configmaps với verbs get/list.

🧹 Cleanup: kubectl delete namespace app-ns

LAB-132

Pod Security Standards — Enforce Restricted Mode

kubectl · kind

🎯 Mục tiêu: Label namespace với PSS restricted; xác nhận pod chạy root bị từ chối, pod tuân thủ được chấp nhận.

🧰 Công cụ / nền tảng: kubectl, kind cluster (K8s ≥ 1.25).

📦 Chuẩn bị: Cluster kind K8s 1.28+ (kind create cluster --image kindest/node:v1.28.0).

▶️ Các bước:

# 1. Tạo namespace, bật warn trước khi enforce để không vỡ workload hiện tại
kubectl create namespace secure-ns
kubectl label namespace secure-ns \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/warn-version=latest

# 2. Enforce restricted
kubectl label namespace secure-ns \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/audit-version=latest

# 3. Thử deploy pod vi phạm (chạy root, privileged)
cat <<EOF | kubectl apply -n secure-ns -f -
apiVersion: v1
kind: Pod
metadata:
  name: bad-pod
spec:
  containers:
  - name: nginx
    image: nginx:alpine
    securityContext:
      runAsUser: 0        # root
      privileged: true
EOF
# Expected: Error from server (Forbidden): pods "bad-pod" is forbidden:
#   violates PodSecurity "restricted:latest": ...

# 4. Deploy pod tuân thủ restricted
cat <<EOF | kubectl apply -n secure-ns -f -
apiVersion: v1
kind: Pod
metadata:
  name: good-pod
spec:
  securityContext:
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: nginxinc/nginx-unprivileged:alpine
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]
      runAsUser: 101
EOF

# 5. Xác nhận pod running
kubectl get pod good-pod -n secure-ns
# Expected: good-pod   1/1   Running

✅ Kết quả mong đợi: bad-pod bị từ chối với thông báo vi phạm PSS cụ thể; good-pod Running bình thường.

🧹 Cleanup: kubectl delete namespace secure-ns

LAB-133

NetworkPolicy Default-Deny và Mở Rule Tối Thiểu

kubectl · kind + Calico / Cilium

🎯 Mục tiêu: Áp dụng default-deny cho cả ingress và egress trong namespace; mở đúng rule cho frontend → backend và backend → database.

🧰 Công cụ / nền tảng: kind cluster với CNI hỗ trợ NetworkPolicy (Calico hoặc Cilium).

📦 Chuẩn bị:

# Tạo kind cluster với Calico CNI
cat <<EOF > kind-calico.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  disableDefaultCNI: true
  podSubnet: "192.168.0.0/16"
EOF
kind create cluster --name netpol --config kind-calico.yaml
# Cài Calico
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
kubectl wait -n kube-system deployment/calico-kube-controllers --for=condition=Available --timeout=120s

▶️ Các bước:

# 1. Tạo namespace và workload giả lập 3-tier
kubectl create namespace shop

# Deploy frontend, backend, db pods với label
kubectl run frontend --image=nginx:alpine -n shop --labels="tier=frontend"
kubectl run backend  --image=nginx:alpine -n shop --labels="tier=backend"
kubectl run db       --image=nginx:alpine -n shop --labels="tier=db"

# 2. Expose internal services
kubectl expose pod backend -n shop --port=80 --name=backend-svc
kubectl expose pod db      -n shop --port=80 --name=db-svc

# 3. Xác nhận frontend CÓ THỂ curl backend (chưa có policy)
kubectl exec frontend -n shop -- wget -qO- http://backend-svc
# Expected: nginx welcome page

# 4. Áp dụng Default-Deny ALL (ingress + egress)
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: shop
spec:
  podSelector: {}      # chọn tất cả pod
  policyTypes:
  - Ingress
  - Egress
EOF

# 5. Xác nhận bị chặn sau deny-all
kubectl exec frontend -n shop -- wget -qO- --timeout=3 http://backend-svc
# Expected: wget: download timed out

# 6. Mở frontend → backend (ingress tới backend từ frontend)
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: shop
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: frontend
    ports:
    - protocol: TCP
      port: 80
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-egress-to-db
  namespace: shop
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
  - Egress
  egress:
  - to:
    - podSelector:
        matchLabels:
          tier: db
    ports:
    - protocol: TCP
      port: 80
  # Cho phép DNS
  - ports:
    - protocol: UDP
      port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-egress
  namespace: shop
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes:
  - Egress
  egress:
  - to:
    - podSelector:
        matchLabels:
          tier: backend
    ports:
    - protocol: TCP
      port: 80
  - ports:
    - protocol: UDP
      port: 53
EOF

# 7. Xác nhận frontend → backend OK, frontend → db FAIL
kubectl exec frontend -n shop -- wget -qO- --timeout=3 http://backend-svc
# Expected: nginx welcome page

kubectl exec frontend -n shop -- wget -qO- --timeout=3 http://db-svc
# Expected: wget: download timed out

✅ Kết quả mong đợi: Sau default-deny, mọi traffic bị chặn. Sau khi thêm allow rules, chỉ đúng luồng frontend→backend và backend→db hoạt động; frontend không kết nối được db trực tiếp.

🧹 Cleanup: kind delete cluster --name netpol

LAB-134

Kyverno Policy — Enforce Trusted Registry và Auto-add Labels

kubectl · Helm · Kyverno

🎯 Mục tiêu: Cài Kyverno; viết ClusterPolicy (1) chặn image từ registry không phải gcr.io hoặc ghcr.io; (2) tự động thêm label managed-by: kyverno vào mọi pod mới.

🧰 Công cụ / nền tảng: kubectl, Helm 3, kind cluster.

📦 Chuẩn bị: kind cluster running, Helm đã cài.

▶️ Các bước:

# 1. Cài Kyverno qua Helm
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update
helm install kyverno kyverno/kyverno \
  --namespace kyverno --create-namespace \
  --set admissionController.replicas=1

kubectl wait deployment kyverno-admission-controller \
  -n kyverno --for=condition=Available --timeout=120s

# 2. Policy Validate: chỉ cho phép trusted registry
cat <<EOF | kubectl apply -f -
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-image-registries
  annotations:
    policies.kyverno.io/title: Restrict Image Registries
    policies.kyverno.io/description: Only gcr.io and ghcr.io images allowed
spec:
  validationFailureAction: Enforce
  background: true
  rules:
  - name: validate-registries
    match:
      any:
      - resources:
          kinds: [Pod]
    validate:
      message: "Image must come from gcr.io or ghcr.io"
      pattern:
        spec:
          containers:
          - image: "gcr.io/* | ghcr.io/*"
EOF

# 3. Policy Mutate: tự động thêm label
cat <<EOF | kubectl apply -f -
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-managed-by-label
spec:
  rules:
  - name: add-label
    match:
      any:
      - resources:
          kinds: [Pod]
    mutate:
      patchStrategicMerge:
        metadata:
          labels:
            managed-by: kyverno
EOF

# 4. Test: deploy image từ Docker Hub (bị từ chối)
kubectl run test-bad --image=nginx:alpine -n default
# Expected: Error: admission webhook "validate.kyverno.svc" denied...
#   Image must come from gcr.io or ghcr.io

# 5. Test: deploy image từ gcr.io (được chấp nhận)
kubectl run test-good \
  --image=gcr.io/google-containers/pause:3.9 \
  -n default

# 6. Kiểm tra label tự động được thêm
kubectl get pod test-good -n default \
  -o jsonpath='{.metadata.labels.managed-by}'
# Expected: kyverno

# 7. Xem policy report
kubectl get policyreport -A

✅ Kết quả mong đợi: Pod từ Docker Hub bị từ chối với message rõ ràng; pod từ gcr.io được tạo và có label managed-by=kyverno tự động; kubectl get policyreport hiện kết quả audit.

🧹 Cleanup: kubectl delete pod test-good; helm uninstall kyverno -n kyverno

LAB-135

OPA Gatekeeper — Chặn Privileged Pod và Falco Runtime Detection

kubectl · Helm · OPA Gatekeeper · Falco

🎯 Mục tiêu: Cài OPA Gatekeeper; viết ConstraintTemplate + Constraint chặn privileged: true; sau đó cài Falco và trigger một rule để thấy alert real-time khi exec vào container.

🧰 Công cụ / nền tảng: kubectl, Helm, kind cluster.

📦 Chuẩn bị: kind cluster (với --privileged nếu dùng Falco kernel module), Helm 3.

▶️ Phần A: OPA Gatekeeper

# 1. Cài Gatekeeper
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper gatekeeper/gatekeeper \
  --namespace gatekeeper-system --create-namespace
kubectl wait deployment gatekeeper-controller-manager \
  -n gatekeeper-system --for=condition=Available --timeout=120s

# 2. Tạo ConstraintTemplate định nghĩa "no privileged containers"
cat <<EOF | kubectl apply -f -
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8snoprivileged
spec:
  crd:
    spec:
      names:
        kind: K8sNoPrivileged
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package k8snoprivileged
      violation[{"msg": msg}] {
        c := input.review.object.spec.containers[_]
        c.securityContext.privileged == true
        msg := sprintf("Container '%v' must not be privileged", [c.name])
      }
      violation[{"msg": msg}] {
        c := input.review.object.spec.initContainers[_]
        c.securityContext.privileged == true
        msg := sprintf("InitContainer '%v' must not be privileged", [c.name])
      }
EOF

# 3. Tạo Constraint áp dụng policy cho toàn cluster
cat <<EOF | kubectl apply -f -
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoPrivileged
metadata:
  name: no-privileged-pods
spec:
  enforcementAction: deny
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
EOF

# 4. Test pod privileged bị từ chối
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: priv-test
spec:
  containers:
  - name: priv
    image: nginx:alpine
    securityContext:
      privileged: true
EOF
# Expected: Error: admission webhook "validation.gatekeeper.sh" denied...
#   Container 'priv' must not be privileged

# 5. Xem violations trong audit
kubectl get k8snoprivileged no-privileged-pods \
  -o jsonpath='{.status.violations}' | python3 -m json.tool

▶️ Phần B: Falco Runtime Detection

# 6. Cài Falco qua Helm (dùng eBPF driver — không cần kernel module trên kind)
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco \
  --namespace falco --create-namespace \
  --set driver.kind=ebpf \
  --set falco.json_output=true \
  --set falco.log_stderr=true

kubectl wait daemonset/falco -n falco --for=jsonpath='{.status.numberReady}'=1 --timeout=120s

# 7. Deploy một pod để test
kubectl run falco-test --image=nginx:alpine -n default

# 8. Trigger alert: exec shell trong container
kubectl exec falco-test -- /bin/sh -c "id"

# 9. Xem Falco alert trong log
kubectl logs -n falco -l app.kubernetes.io/name=falco --tail=20 | grep "shell"
# Expected: JSON event như:
# {"output":"Notice A shell was spawned in a container
#   (user=root container=falco-test image=nginx:alpine shell=sh ...)","priority":"Notice",
#  "rule":"Terminal shell in container","time":"..."}

# 10. Xem tất cả rules hiện có
kubectl exec -n falco daemonset/falco -- falco --list | grep "rule:" | head -20

✅ Kết quả mong đợi: Gatekeeper từ chối pod privileged với message Rego; Falco log xuất hiện alert "Terminal shell in container" sau khi exec. Audit status của Constraint hiển thị violations nếu có pod vi phạm đang tồn tại.

🧹 Cleanup: helm uninstall falco -n falco; helm uninstall gatekeeper -n gatekeeper-system; kubectl delete pod falco-test priv-test 2>/dev/null; kind delete cluster

3. Tình huống doanh nghiệp thực tế

Bối cảnh

Một fintech vận hành 40 microservice trên EKS. Audit PCI-DSS phát hiện: nhiều pod chạy root, không có NetworkPolicy, image pull từ Docker Hub public không qua scan. Yêu cầu: đạt compliance trong 30 ngày mà không downtime.

Giải pháp theo lớp

  • Ngày 1–5 (Audit): Chạy kubectl-who-cankube-bench để baseline. Bật Kyverno audit mode, không enforce — thu thập PolicyReport để biết vi phạm.
  • Ngày 6–15 (Hardening theo sóng): Namespace dev → staging → prod lần lượt bật PSS warn rồi enforce=baseline. Fix workload từng service dựa vào report.
  • Ngày 16–20 (Network): Deploy default-deny + allow rules theo service mesh topology đã vẽ. Dùng cilium monitor để debug connection bị chặn.
  • Ngày 21–25 (Supply chain): Chuyển image về private ECR, thêm Trivy scan trong CI, Kyverno ClusterPolicy enforce registries: ["<account>.dkr.ecr.*.amazonaws.com"].
  • Ngày 26–30 (Runtime): Cài Falco; export alert ra SIEM (Splunk) bằng falco-exporter. Viết custom rule phát hiện process lạ trong payment service.
  • Kết quả: Zero downtime nhờ audit-before-enforce; 100% pod non-root; PCI DSS 8.6, 10.3, 11.5 passed.

📚 Nguồn tham khảo

Module 26: Software Supply Chain Module 28: Cloud Security
Zalo