Chuyển đến nội dung chính

Bài 15: Container tưởng đã cách ly

root trong container là root thật, cho tới khi bật user namespace. Và NetworkPolicy có thể im lặng không chặn gì.

Xem bản video

Container không phải máy ảo

Nó không có nhân riêng. Nó dùng chung đúng cái nhân của máy chủ, chỉ được cho nhìn thấy một góc hẹp hơn.

Máy ảoContainer
nhânriêngcủa máy chủ — dùng chung

Hệ quả đáng sợ

Nếu tiến trình trong container chạy bằng root thì cái root đó, ở góc nhìn của nhân, chính là uid 0 — cùng một con số với root của máy chủ. Chỉ cần một lỗ hổng cho phép thoát ra khỏi container là có ngay quyền root trên chính cái máy đó. Không phải root giả. Root thật.

Cách chữa gốc rễ: user namespace

Vừa lên ổn định ở 1.36. Ý tưởng rất gọn — ánh xạ lại mã người dùng: bên trong container vẫn là 0, vẫn là root, mọi thứ chạy bình thường; nhưng ở ngoài, nhân nhìn thấy nó là một mã người dùng thường không có quyền gì.

spec:
  hostUsers: false

Tầng chặn phía trên: Pod Security Admission

PodSecurityPolicy đã bị gỡ khỏi Kubernetes từ lâu. Thay vào đó là nhãn trên namespace:

metadata:
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/audit: restricted

Ba mức: privileged (không chặn gì) · baseline (chặn những thứ nguy hiểm rõ ràng) · restricted (siết chặt, buộc chạy không phải root).

Ba chế độ: enforce (chặn thật) · audit (ghi lại) · warn (cảnh báo lúc apply).

Mẹo dùng: bật warn và audit ở mức restricted trước, xem log vài tuần, rồi mới bật enforce.

Còn mạng thì sao

Mặc định trong Kubernetes, mọi pod nói chuyện được với mọi pod — không chỉ trong cùng namespace mà là toàn bộ cụm. Cái pod nhỏ xíu chạy công việc định kỳ, về mặt mạng, gọi thẳng được vào cơ sở dữ liệu của hệ thống thanh toán.

NetworkPolicy hoạt động hơi ngược trực giác

  • Chưa có policy nào chọn tới một pod → pod đó mở toang
  • Có một policy chọn nó → pod đó lập tức từ chối mặc định cho chiều được chọn

Nghĩa là viết policy đầu tiên cho một pod là một hành động khá lớn — nó lật cả trạng thái mặc định của pod đó, chứ không phải thêm một luật nhỏ.

Cái bẫy lớn nhất

NetworkPolicy chỉ là một bản mô tả. Thứ thi hành nó là plugin mạng của cụm. Nếu plugin không hỗ trợ thì policy vẫn tạo được:

$ kubectl apply -f deny-all.yaml
networkpolicy.networking.k8s.io/deny-all created

…và nó không chặn một gói tin nào. Không lỗi, không cảnh báo, hoàn toàn im lặng.

Việc bắt buộc sau khi viết policy đầu tiên: mở một pod, thử gọi sang chỗ lẽ ra phải bị chặn, xem nó có bị chặn thật không. Kiểm bằng cách thử, đừng đọc tài liệu.

Năm dòng nên là mặc định

securityContext:
  runAsNonRoot: true
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  capabilities:
    drop: ["ALL"]
  seccompProfile:
    type: RuntimeDefault

Năm dòng này chặn phần lớn những đường tấn công phổ thông, và với ứng dụng viết tử tế thì gần như không phải sửa gì.

Nguồn: Kubernetes v1.36 “Haru”