Seccomp, AppArmor và Capabilities

K
Kai··4 min read

Bài 54 dừng ở mức chính sách: restricted đòi runAsNonRoot, drop: ["ALL"], seccompProfile, allowPrivilegeEscalation: false. Bốn trường đó không phải khái niệm của Kubernetes — chúng là cơ chế của kernel Linux mà container runtime bật lên cho từng container. Bài này xuống xem chúng thật sự làm gì, bằng cách so hai pod cạnh nhau: một pod mặc định và một pod đã siết, đọc thẳng /proc/self/status để thấy khác biệt ở tầng kernel.

Ba lớp phòng thủ trong securityContext

securityContext của pod/container điều khiển ba cơ chế kernel khác nhau:

   capabilities  ── chia quyền của root thành ~40 mảnh; drop bớt để root cũng không
                    làm được thao tác nguy hiểm (mount, chown, bind port thấp...)

   seccomp       ── lọc syscall: bộ lọc của runtime chặn các syscall hiếm dùng mà
                    nguy hiểm (keyctl, ptrace tới tiến trình khác...)

   AppArmor      ── (trên Ubuntu của node) hồ sơ giới hạn file/đường dẫn/thao tác
                    một tiến trình được phép chạm, áp ở mức LSM của kernel

Ba lớp này độc lập và cộng dồn. Tạo hai pod để so — một pod không khai gì, một pod siết đủ:

# pod hardened
spec:
  securityContext:
    seccompProfile: {type: RuntimeDefault}
    appArmorProfile: {type: RuntimeDefault}
  containers:
  - name: c
    image: busybox:1.36
    command: ["sleep", "100000"]
    securityContext:
      allowPrivilegeEscalation: false
      capabilities: {drop: ["ALL"]}

Đọc /proc/self/status của hai pod

Kernel phơi trạng thái bảo mật của tiến trình ở /proc/self/status. So pod mặc định và pod hardened:

kubectl -n sec-demo exec def      -- sh -c 'grep -E "^(CapEff|Seccomp|NoNewPrivs)" /proc/self/status; cat /proc/self/attr/current'
kubectl -n sec-demo exec hardened -- sh -c 'grep -E "^(CapEff|Seccomp|NoNewPrivs)" /proc/self/status; cat /proc/self/attr/current'
# pod def (mặc định)
CapEff:     00000000a80425fb
NoNewPrivs: 0
Seccomp:    0
AppArmor:   cri-containerd.apparmor.d (enforce)

# pod hardened
CapEff:     0000000000000000
NoNewPrivs: 1
Seccomp:    2
AppArmor:   cri-containerd.apparmor.d (enforce)

Đọc từng dòng:

  • CapEff (capabilities hiệu lực): pod mặc định có a80425fb — đây là ~14 capability mà containerd cấp sẵn cho mọi container (chown, net_bind_service, setuid...), không phải toàn bộ quyền root nhưng vẫn nhiều. Pod hardened có 0 — sạch hết, không capability nào.
  • Seccomp: 0 là tắt (mọi syscall đi qua), 2 là chế độ filter (bộ lọc của runtime đang chặn). Pod mặc định không có seccomp; pod hardened bật RuntimeDefault.
  • NoNewPrivs: 1 ở pod hardened do allowPrivilegeEscalation: false — tiến trình con không thể giành thêm quyền (vô hiệu hóa setuid leo thang).
  • AppArmor: cả hai cùng cri-containerd.apparmor.d (enforce). Đây là điểm dễ tưởng nhầm: containerd áp hồ sơ AppArmor mặc định cho mọi container trên node Ubuntu này, kể cả pod không khai gì. Khai appArmorProfile: RuntimeDefault chỉ là nói rõ điều đang xảy ra sẵn.

Capability chặn được gì: thử chown

Con số CapEff: 0 nghe trừu tượng, nên thử một thao tác cụ thể. chown cần CAP_CHOWN. Cả hai pod đều chạy bằng root (busybox mặc định uid 0), nhưng pod hardened đã drop: ["ALL"]:

kubectl -n sec-demo exec def      -- sh -c 'touch /tmp/f && chown 1000 /tmp/f && echo OK; whoami'
kubectl -n sec-demo exec hardened -- sh -c 'touch /tmp/f && chown 1000 /tmp/f; id -u'
# pod def
OK
root

# pod hardened
chown: /tmp/f: Operation not permitted
0

Pod mặc định chown được vì còn CAP_CHOWN. Pod hardened chown bị Operation not permitted id -u vẫn là 0 — vẫn là root, nhưng đã bỏ CAP_CHOWN nên kernel chặn. Đây là điểm cốt lõi của capabilities: chúng tách nhỏ quyền của root, nên một container chạy root mà drop hết capability thì không còn làm được phần lớn thao tác nguy hiểm. Cần lại một quyền cụ thể thì add đúng cái đó (capabilities.add: ["NET_BIND_SERVICE"] để bind port <1024) thay vì mở toàn bộ.

🧹 Dọn dẹp

kubectl delete namespace sec-demo

Bài này chỉ tạo hai pod, không đụng cấu hình node hay runtime. AppArmor mặc định của containerd vẫn ở đó cho mọi pod. Manifest ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 55-seccomp-apparmor-capabilities.

Tổng kết

Bốn trường mà restricted đòi ở Bài 54 ánh xạ xuống ba cơ chế kernel độc lập. So /proc/self/status hai pod cho thấy rõ: pod mặc định có CapEff a80425fb (~14 capability containerd cấp sẵn), Seccomp 0 (tắt), NoNewPrivs 0; pod hardened có CapEff 0 (drop ALL), Seccomp 2 (filter của RuntimeDefault), NoNewPrivs 1 (allowPrivilegeEscalation=false). AppArmor thì containerd đã áp hồ sơ mặc định cri-containerd.apparmor.d cho cả hai, nên appArmorProfile: RuntimeDefault chỉ là khai cho rõ. Hậu quả thật của drop capability: chown bị Operation not permitted ngay cả khi container chạy root — capabilities tách nhỏ quyền root, drop hết rồi add lại đúng cái cần là cách siết gọn. Đây là tầng dưới của chính sách Pod Security ở Bài 54: PSA bắt khai, kernel thực thi.

Phần lớn Part XI tới giờ siết quyền và siết container. Bài 56 khép lại bằng bí mật và những lỗ hổng còn sót: Secret nằm ở đâu, ai đọc được, lối vòng "ai tạo được pod là đọc được Secret", và một bảng các bước siết cụm tự dựng còn thiếu.