Skip to content

Pod & Workload Security — securityContext và Pod Security Standards

Vì sao securityContext quyết định mức độ thiệt hại của một RCE

Giả định bi quan nhưng thực tế: sớm muộn một ứng dụng của bạn sẽ có lỗ hổng RCE (remote code execution). Một dependency bị nhiễm, một endpoint deserialize không an toàn, một thư viện parse lỗi. Khi điều đó xảy ra, câu hỏi không phải "có bị không" mà là "kẻ tấn công làm được gì với quyền có trong container". Và câu trả lời được quyết định gần như hoàn toàn bởi securityContextPod Security Standards.

Một container chạy root, privileged, với mọi capability, ghi được rootfs, và có thể escalate quyền — biến một RCE thành container escape rồi node compromise trong vài bước. Cùng RCE đó, nhưng trong một container chạy non-root, read-only rootfs, drop toàn bộ capability, không escalate được — kẹt lại ở mức một process không đặc quyền trong một filesystem chỉ đọc, gần như không làm gì được ngoài chính nó. Sự khác biệt giữa "sự cố giới hạn" và "thảm họa toàn cluster" thường chính là vài dòng securityContext.

File này đi từ chuẩn cấp cao (Pod Security Standards) xuống từng trường cấu hình cụ thể, và giải thích cách enforce chúng tự động qua Pod Security Admission — vì dựa vào kỷ luật thủ công của developer để đặt securityContext đúng trên mọi workload là một chiến lược chắc chắn thất bại ở quy mô.

Pod Security Standards: ba cấp độ chuẩn hóa

Kubernetes định nghĩa ba Pod Security Standards — ba mức policy chuẩn hóa, từ lỏng đến chặt, theo tài liệu Pod Security Standards:

Privileged — không hạn chế

Mức mở hoàn toàn: cho phép privileged container, hostPath, host namespace, mọi capability. Đây là mức cho các workload hạ tầng đặc quyền thật sự cần (CNI plugin, storage driver, node agent). Với workload ứng dụng thông thường, Privileged là sai — nó vô hiệu hóa mọi cô lập.

Baseline — chặn các escalation rõ ràng

Mức "tối thiểu hợp lý": cấm các đặc quyền nguy hiểm và rõ ràng nhất nhưng vẫn dễ tương thích. Baseline cấm: privileged: true, host namespaces (hostNetwork, hostPID, hostIPC), hostPath volumes, thêm capability ngoài tập mặc định, hostPort không kiểm soát. Đây là sàn an toàn tối thiểu — không workload ứng dụng nào nên ở dưới Baseline.

Restricted — hardening đầy đủ

Mức chặt nhất, hiện thân của best practice. Ngoài mọi thứ Baseline cấm, Restricted bắt buộc:

  • runAsNonRoot: true — container không được chạy với UID 0.
  • allowPrivilegeEscalation: false — chặn leo thang quyền (no-new-privs).
  • capabilities.drop: ["ALL"] — bỏ mọi Linux capability (chỉ được thêm lại NET_BIND_SERVICE nếu cần).
  • seccompProfile.type: RuntimeDefault (hoặc Localhost) — bắt buộc có seccomp profile.
  • Hạn chế volume types về các loại an toàn (configMap, secret, projected, emptyDir, PVC...).

Restricted là mục tiêu mặc định cho mọi workload ứng dụng. Nếu một workload không chạy được dưới Restricted, đó là tín hiệu cần xem lại thiết kế của nó (vì sao cần root? vì sao cần ghi rootfs?), không phải lý do để hạ chuẩn toàn cluster.

Pod Security Admission: enforce tự động theo namespace

Pod Security Standards chỉ là định nghĩa; Pod Security Admission (PSA) là cơ chế enforce chúng. PSA là một admission controller in-tree (thay thế PodSecurityPolicy đã bị loại bỏ), enforce theo nhãn trên namespace. Cơ chế PSA đã được trình bày sâu ở Chương 10, file 05; ở đây ta tập trung góc áp dụng bảo mật.

PSA có ba mode, áp độc lập, mỗi mode trỏ tới một level (privileged/baseline/restricted):

  • enforce: từ chối Pod vi phạm. Đây là mode thực sự chặn.
  • audit: cho qua nhưng ghi vi phạm vào audit log. Dùng để đánh giá tác động trước khi enforce.
  • warn: cho qua nhưng trả cảnh báo cho người dùng (hiển thị khi kubectl apply). Dùng để cảnh báo developer sớm.
yaml
apiVersion: v1
kind: Namespace
metadata:
  name: team-a
  labels:
    # enforce Restricted — chặn mọi Pod không đạt chuẩn
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    # đồng thời audit + warn ở mức restricted để có dấu vết và cảnh báo
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

Pattern triển khai an toàn (tránh làm vỡ workload đang chạy): bắt đầu bằng warn + audit ở mức restricted để thu thập danh sách vi phạm mà không chặn; sửa workload vi phạm; rồi mới bật enforce: restricted. Đây là cách rollout PSA mà không gây outage — chuyển từ "quan sát" sang "chặn" có kiểm soát.

Lưu ý GKE: PSA enforce ở cấp namespace với granularity là level + mode; với policy phức tạp hơn (ví dụ ngoại lệ theo từng workload, hoặc rule tùy biến vượt ngoài ba chuẩn), dùng Policy Controller/Gatekeeper hoặc Kyverno (file 08). PSA là sàn; policy engine là phần mở rộng.

securityContext: từng trường và ý nghĩa bảo mật

securityContext đặt ở cấp Pod (spec.securityContext) và/hoặc cấp container (spec.containers[].securityContext); cấp container ghi đè cấp Pod. Dưới đây là các trường quan trọng nhất và vì sao mỗi trường quan trọng.

yaml
apiVersion: v1
kind: Pod
metadata:
  name: hardened-app
spec:
  securityContext:
    runAsNonRoot: true            # cả Pod: cấm chạy root
    runAsUser: 10001              # UID phi-root cụ thể
    runAsGroup: 10001
    fsGroup: 10001                # group sở hữu volume mount
    seccompProfile:
      type: RuntimeDefault        # seccomp profile mặc định của runtime
  containers:
    - name: app
      image: registry.example.com/app@sha256:...   # pin digest, không 'latest'
      securityContext:
        allowPrivilegeEscalation: false  # no-new-privs: cấm leo thang qua setuid
        readOnlyRootFilesystem: true     # rootfs chỉ đọc
        privileged: false                # không bao giờ true cho app
        capabilities:
          drop: ["ALL"]                  # bỏ mọi capability
          # add: ["NET_BIND_SERVICE"]    # chỉ thêm lại nếu thật sự cần bind <1024
      volumeMounts:
        - name: tmp
          mountPath: /tmp                # nơi ghi tạm, vì rootfs read-only
  volumes:
    - name: tmp
      emptyDir: {}

Phân tích từng trường:

  • runAsNonRoot: true + runAsUser: chặn container chạy với UID 0. Root trong container, dù bị namespace cô lập, vẫn là điểm khởi đầu thuận lợi cho escape (nhiều kỹ thuật escape cần root trong container). Chạy non-root làm escape khó hơn nhiều. Lưu ý: image phải được build để chạy non-root (USER trong Dockerfile) thì trường này mới không làm Pod crash.

  • allowPrivilegeEscalation: false: đặt cờ no_new_privs của kernel, chặn process con giành thêm quyền qua binary setuid. Không có lý do chính đáng để một app cần escalate quyền — luôn đặt false.

  • readOnlyRootFilesystem: true: mount rootfs của container read-only. Kẻ tấn công không ghi được binary, không thả tool, không sửa cấu hình. Ứng dụng cần ghi thì mount emptyDir vào đúng path (/tmp, cache dir). Đây là một trong những biện pháp hiệu quả nhất chống persistence và tool-dropping sau RCE.

  • privileged: false: privileged: true cho container gần như mọi quyền của host (mọi device, mọi capability, bypass nhiều cô lập) — nó xóa bỏ ranh giới container/host. Không workload ứng dụng nào được privileged. Chỉ một số node agent hạ tầng cần, và chúng nên chạy trên node pool riêng có kiểm soát.

  • capabilities.drop: ["ALL"]: Linux capabilities chia nhỏ quyền root thành các mảnh (NET_ADMIN, SYS_ADMIN, ...). Mặc định container có một tập capability; drop ALL rồi chỉ add lại đúng cái cần (thường không cần gì) tuân thủ least-privilege ở tầng kernel. SYS_ADMIN đặc biệt nguy hiểm — gần tương đương root.

  • automountServiceAccountToken: false (cấp Pod/SA): nếu Pod không gọi Kubernetes API, đừng mount token KSA (file 02). Mỗi token mount là một credential có thể bị đọc sau RCE.

seccomp và AppArmor: thu hẹp syscall surface

Ngoài securityContext, hai cơ chế kernel-level thu hẹp thêm bề mặt tấn công:

seccomp (secure computing mode) lọc syscall mà container được phép gọi. Profile RuntimeDefault (do container runtime cung cấp) chặn một tập syscall nguy hiểm/không cần thiết — đủ tốt cho hầu hết ứng dụng và là khuyến nghị mặc định. Restricted Pod Security bắt buộc có seccomp profile. Với workload nhạy cảm, có thể viết profile Localhost tùy chỉnh chỉ cho phép đúng syscall app dùng (chặt hơn nhưng tốn công duy trì). Trên GKE/COS, RuntimeDefault được hỗ trợ tốt; chỉ cần khai báo seccompProfile.type: RuntimeDefault.

AppArmor áp một security profile mô tả container được truy cập file/capability/network nào, ở tầng Linux Security Module. GKE với COS hỗ trợ AppArmor; profile mặc định của runtime áp một mức hạn chế cơ bản. AppArmor và seccomp bù trừ: seccomp lọc syscall, AppArmor kiểm soát truy cập tài nguyên — dùng cả hai cho defense-in-depth ở tầng kernel.

Lưu ý tương tác với gVisor (file 05): gVisor thay thế lớp kernel nên seccomp/AppArmor truyền thống không áp dụng theo cách thông thường cho Pod gVisor — bản thân gVisor đã là một lớp lọc syscall mạnh hơn. Hai cách tiếp cận (gVisor vs seccomp+AppArmor) là hai chiến lược cô lập khác nhau, không xếp chồng.

Production architecture patterns

Pattern: Restricted làm mặc định toàn cluster, ngoại lệ tường minh

Áp enforce: restricted cho mọi namespace ứng dụng làm mặc định, và chỉ tạo ngoại lệ tường minh (namespace privileged hoặc baseline) cho các workload hạ tầng thực sự cần, với review bắt buộc. Cách này đảo ngược gánh nặng: thay vì developer phải nhớ hardening, hệ thống chặn mặc định và việc nới lỏng mới cần biện minh. Đây là nguyên tắc "secure by default" ở tầng workload.

Pattern: base image và template hardened sẵn

Cung cấp cho developer các base image distroless/non-rootPod template đã có securityContext đúng (non-root, read-only rootfs, drop ALL, seccomp RuntimeDefault). Khi đường dễ nhất cũng là đường an toàn nhất, developer tự nhiên đi đúng. Kết hợp với PSA enforce, đây là mô hình "paved road" cho workload security: template đúng + admission chặn sai.

Pattern: tách writable path ra emptyDir

readOnlyRootFilesystem: true là mục tiêu, mọi nhu cầu ghi (temp file, cache, PID file) được route sang emptyDir mount vào đúng path. Điều này buộc ứng dụng khai báo tường minh nó ghi ở đâu — một bài tập tốt cho việc hiểu và kiểm soát hành vi I/O của workload, đồng thời giữ rootfs bất biến.

Common mistakes / anti-patterns

1. Chạy container as root. Vì sao xảy ra: image mặc định chạy root, developer không đặt USER, không ai enforce. Hệ quả: RCE → root trong container → nền tảng thuận lợi cho escape. Phòng tránh: runAsNonRoot: true, build image non-root, enforce qua PSA Restricted.

2. privileged: true "cho nó chạy". Vì sao xảy ra: app báo lỗi permission, privileged làm lỗi biến mất nhanh. Hệ quả: xóa bỏ ranh giới container/host hoàn toàn — RCE = node compromise tức thì. Phòng tránh: không bao giờ privileged cho app; tìm capability cụ thể cần thiết và add đúng cái đó; nếu thật sự cần privileged, đó là workload hạ tầng phải chạy node pool riêng có kiểm soát.

3. Bỏ qua readOnlyRootFilesystem. Vì sao xảy ra: app ghi vào rootfs (log, temp), bật read-only làm crash, nên bỏ luôn. Hệ quả: kẻ tấn công ghi tool, sửa binary, persist sau RCE. Phòng tránh: bật read-only + mount emptyDir cho path cần ghi; sửa app ghi đúng chỗ.

4. Mount hostPath nhạy cảm. Vì sao xảy ra: cần truy cập docker socket, log node, hoặc filesystem host. Hệ quả: mount /var/run/docker.sock hay / của host = container escape trực tiếp; mount hostPath nhạy cảm là một trong những con đường escape phổ biến nhất. Phòng tránh: tránh hostPath; nếu thực sự cần, dùng CSI/API thay thế; Baseline+ PSA đã cấm hostPath.

5. Mount token KSA vào Pod không cần. Vì sao xảy ra: mặc định automount bật. Hệ quả: RCE đọc được token → có credential gọi Kubernetes API với quyền của KSA. Phòng tránh: automountServiceAccountToken: false cho Pod không gọi API (file 02).

GCP-native implementation guidance

bash
# Bật PSA chế độ quan sát trước khi enforce (an toàn rollout)
kubectl label namespace team-a \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/audit=restricted

# Sau khi sửa hết vi phạm, chuyển sang enforce
kubectl label --overwrite namespace team-a \
  pod-security.kubernetes.io/enforce=restricted

# Kiểm tra Pod đang chạy có vi phạm Restricted không (dry-run đánh giá)
kubectl label --dry-run=server --overwrite namespace team-a \
  pod-security.kubernetes.io/enforce=restricted
# Output liệt kê các Pod sẽ bị chặn nếu enforce — danh sách cần sửa

Lệnh --dry-run=server với label enforce là công cụ quan trọng nhất khi rollout PSA: nó cho biết chính xác workload nào sẽ vỡ trước khi bạn thực sự enforce, biến một thay đổi rủi ro thành một thay đổi có kế hoạch.

Operational implications

Pod security có một nghịch lý vận hành: nó là lớp phòng thủ có tỷ lệ lợi ích/chi phí cao nhất (vài dòng YAML giảm blast radius drastically) nhưng cũng là lớp dễ bị bỏ qua nhất vì nó nằm trên mọi workload, mọi lần deploy, và phụ thuộc vào kỷ luật của hàng chục developer. Con người sẽ quên; áp lực thời hạn sẽ thắng; và một workload chạy root lọt qua sẽ không gây lỗi gì cho đến ngày bị tấn công. Đây chính xác là lý do enforcement tự động (PSA + policy engine) là bắt buộc, không phải tùy chọn: bạn không thể bảo mật hàng nghìn workload bằng code review thủ công.

Hệ quả thiết kế: đầu tư vào enforcement và "paved road" (base image non-root, template hardened, PSA enforce Restricted) trả lãi kép. Mỗi workload mới tự động an toàn mà không cần ai nhớ làm gì, và việc vi phạm trở thành thứ phải biện minh tường minh — đúng tinh thần secure-by-default. Khi kết hợp với node security (file 05) và Network Policy (file 07), pod security hoàn thiện bộ ba cô lập runtime: Pod không chạy root (file này), Pod không escape được node dễ dàng (file 05), và Pod bị chiếm không lan ngang được (file 07).

References