Gia Cố Bảo Mật Container Trên Linux: Docker, Kubernetes, Seccomp, Falco Và Thực Hành 2026

Hướng dẫn gia cố bảo mật container Linux năm 2026: rootless Docker/Podman, seccomp profile, AppArmor/SELinux, Kubernetes RBAC, Pod Security Standards, Network Policy và giám sát runtime với Falco. Kèm phân tích các lỗ hổng runC 2025.

Container Trên Linux Năm 2026: Bề Mặt Tấn Công Đang Mở Rộng

Container đã thay đổi hoàn toàn cách chúng ta triển khai và vận hành phần mềm. Docker, Kubernetes, Podman — giờ thì những cái tên này xuất hiện ở khắp nơi, từ startup nhỏ đến enterprise lớn. Nhưng nói thật, đi kèm với sự tiện lợi đó là một thực tế mà nhiều người vẫn chưa nghiêm túc đối mặt: bề mặt tấn công đang mở rộng nhanh chóng.

Năm 2025 chứng kiến hàng loạt lỗ hổng container escape nghiêm trọng. Và năm 2026? Tình hình không hề giảm nhiệt.

Theo các báo cáo bảo mật gần đây, hơn 85% container image trong môi trường production chứa lỗ hổng ở mức cao hoặc nghiêm trọng. Hơn 65% các vụ vi phạm bảo mật container khai thác quyền root bên trong container. Những con số này không phải để hù dọa — chúng là lời cảnh tỉnh thực sự.

Trong bài viết này, mình sẽ hướng dẫn bạn gia cố container trên Linux một cách toàn diện — từ xây dựng image an toàn, chạy container không cần root, áp dụng seccomp và AppArmor/SELinux, cho đến giám sát runtime với Falco. Tất cả đều kèm ví dụ thực hành cụ thể mà bạn có thể áp dụng ngay trong môi trường của mình.

Các Lỗ Hổng Container Nghiêm Trọng Năm 2025 — Bài Học Xương Máu

Ba lỗ hổng runC cho phép thoát khỏi container (tháng 11/2025)

Vào ngày 5 tháng 11 năm 2025, ba lỗ hổng nghiêm trọng trong runC — runtime OCI được sử dụng bởi Docker, Kubernetes, containerd và CRI-O — đã được công bố. Đây là loại lỗ hổng mà khi đọc mô tả xong, bạn sẽ muốn kiểm tra hệ thống ngay lập tức. Các lỗ hổng này cho phép kẻ tấn công phá vỡ cơ chế cách ly container và ghi file lên hệ thống host với quyền root.

  • CVE-2025-31133: Kẻ tấn công khai thác tính năng maskedPaths bằng cách thay thế /dev/null bằng symlink trong quá trình khởi tạo container, khiến runC bind-mount đường dẫn host tùy ý — cho phép ghi vào các file quan trọng như /proc/sys/kernel/core_pattern.
  • CVE-2025-52565: Lỗ hổng tương tự nhưng khai thác bind-mount của /dev/console. Bằng cách thay thế /dev/pts/$n bằng symlink, kẻ tấn công có thể đọc-ghi các file procfs nhạy cảm.
  • CVE-2025-52881: Cái này tinh vi nhất — vượt qua kiểm tra LSM (Linux Security Modules) để chuyển hướng ghi vào các target nguy hiểm như /proc/sysrq-trigger (crash host) hoặc /proc/sys/kernel/core_pattern (container breakout hoàn toàn).

Các lỗ hổng đã được vá trong runC phiên bản 1.2.8, 1.3.3 và 1.4.0-rc.3. Nếu bạn chưa cập nhật — hãy làm ngay, đừng chờ thêm:

# Kiểm tra phiên bản runC hiện tại
runc --version

# Trên Ubuntu/Debian — cập nhật runc
sudo apt update && sudo apt install -y runc

# Trên RHEL/Fedora — cập nhật runc
sudo dnf update -y runc

# Xác nhận phiên bản sau khi cập nhật
runc --version

CVE-2025-9074 — Container escape qua Docker API (CVSS 9.3)

Tháng 8/2025, Docker vá một lỗ hổng cực kỳ nghiêm trọng: một container độc hại chạy trên Docker Desktop có thể truy cập Docker Engine và khởi chạy thêm container mà không cần mount Docker socket. Nghe có vẻ đơn giản, nhưng hệ quả thì rất lớn — kẻ tấn công có thể truy cập trái phép vào file trên hệ thống host. Điểm CVSS 9.3 nói lên tất cả.

Bài học rút ra: luôn cập nhật Docker Engine lên phiên bản mới nhất, và đừng bao giờ coi container isolation là tuyệt đối. Nó chưa bao giờ là tuyệt đối.

Xây Dựng Container Image An Toàn — Nền Tảng Của Mọi Thứ

Sử dụng base image tối thiểu

Nguyên tắc đơn giản nhưng nhiều người bỏ qua: mỗi package trong image là một bề mặt tấn công tiềm năng. Image càng nhỏ, lỗ hổng càng ít. Distroless image có thể giảm bề mặt tấn công lên đến 95% so với image đầy đủ, vì chúng loại bỏ hoàn toàn shell, package manager và các tiện ích mà kẻ tấn công thường khai thác.

So sánh hai cách tiếp cận:

# KHÔNG NÊN — image quá lớn, nhiều package không cần thiết
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3 python3-pip
COPY app.py /app/
CMD ["python3", "/app/app.py"]

# NÊN — multi-stage build với distroless image
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --target=/app/deps -r requirements.txt

FROM gcr.io/distroless/python3-debian12
WORKDIR /app
COPY --from=builder /app/deps /app/deps
COPY app.py .
ENV PYTHONPATH=/app/deps
CMD ["app.py"]

Ghim phiên bản image cụ thể

Đây là sai lầm mình thấy rất nhiều trong thực tế: dùng tag :latest. Nó có thể kéo về image chứa breaking change hoặc lỗ hổng mới mà bạn không hay biết. Luôn ghim phiên bản cụ thể, và tốt hơn nữa là sử dụng digest:

# Ghim phiên bản cụ thể
FROM python:3.12.8-slim-bookworm

# Tốt hơn — ghim bằng digest (SHA256)
FROM python:3.12.8-slim-bookworm@sha256:abc123def456...

# Kiểm tra digest của image
docker inspect --format='{{index .RepoDigests 0}}' python:3.12.8-slim-bookworm

Quét lỗ hổng image với Trivy

Trivy là công cụ quét lỗ hổng container hàng đầu hiện nay — nhanh, chính xác và tích hợp CI/CD rất dễ dàng. Mình khuyến khích bạn chạy Trivy như một bước bắt buộc trong pipeline:

# Cài đặt Trivy
sudo apt-get install -y trivy

# Quét image để tìm lỗ hổng
trivy image --severity HIGH,CRITICAL myapp:latest

# Quét và xuất kết quả dạng JSON
trivy image --format json --output results.json myapp:latest

# Quét Dockerfile để phát hiện cấu hình sai
trivy config Dockerfile

# Tạo SBOM (Software Bill of Materials) cho image
trivy image --format spdx-json --output sbom.json myapp:latest

Ký và xác minh image với Cosign

Để đảm bảo image không bị thay đổi từ lúc build đến lúc deploy (supply chain attack là mối đe dọa thực sự), hãy sử dụng Cosign thuộc dự án Sigstore để ký và xác minh:

# Cài đặt Cosign
go install github.com/sigstore/cosign/v2/cmd/cosign@latest

# Ký image
cosign sign --key cosign.key myregistry.io/myapp:v1.0

# Xác minh image trước khi deploy
cosign verify --key cosign.pub myregistry.io/myapp:v1.0

# Ký keyless (dùng OIDC identity — khuyến nghị cho CI/CD)
cosign sign myregistry.io/myapp:v1.0
cosign verify [email protected] \
  --certificate-oidc-issuer=https://accounts.google.com \
  myregistry.io/myapp:v1.0

Chạy Container Không Cần Root — Rootless Mode

Tại sao rootless container quan trọng?

Đây là câu hỏi mà mình nghĩ mọi sysadmin cần trả lời được: khi container chạy với quyền root, nếu xảy ra container escape, kẻ tấn công sẽ có quyền root trên host. Game over.

Rootless mode giải quyết vấn đề này bằng cách chạy cả Docker daemon và container dưới quyền người dùng thường. Ngay cả khi có container escape, kẻ tấn công chỉ có quyền hạn của user thường — giảm bề mặt tấn công từ 60% đến 80%.

Thiết lập Docker rootless mode

# Bước 1: Cài đặt gói uidmap (cần sudo lần cuối cùng)
sudo apt-get install -y uidmap dbus-user-session

# Bước 2: Dừng Docker daemon chạy với root (nếu có)
sudo systemctl disable --now docker.service docker.socket

# Bước 3: Chạy script thiết lập rootless (KHÔNG dùng sudo)
dockerd-rootless-setuptool.sh install

# Bước 4: Thêm biến môi trường vào ~/.bashrc
echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc
echo 'export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock' >> ~/.bashrc
source ~/.bashrc

# Bước 5: Khởi động Docker rootless
systemctl --user start docker

# Bước 6: Xác nhận đang chạy rootless
docker info 2>&1 | grep -i "rootless\|context\|security"
# Kết quả mong đợi: Context: rootless
#                    Security Options: seccomp, rootless, cgroupns

Podman — Rootless mặc định từ đầu

Podman được thiết kế rootless từ đầu, không cần daemon, và tích hợp sâu với SELinux. Nếu bạn đang chạy trên Fedora, RHEL hoặc CentOS Stream thì Podman là lựa chọn tự nhiên hơn Docker rất nhiều:

# Cài đặt Podman
sudo dnf install -y podman

# Chạy container rootless (không cần sudo, không cần daemon)
podman run --rm -it alpine:3.20 /bin/sh

# So sánh capabilities mặc định
# Docker: ~14 capabilities
# Podman: ~11 capabilities (ít hơn = an toàn hơn)

# Kiểm tra container đang chạy rootless
podman info | grep rootless
# Kết quả: rootless: true

Một điểm đáng chú ý: kể từ đầu năm 2025, Podman sử dụng pasta thay thế slirp4netns cho networking trong rootless mode. Cải tiến này mang lại hiệu suất mạng gần như native nhờ tap interface và zero-copy splice — rootless container giờ không còn phải hy sinh hiệu suất mạng nữa.

Seccomp — Lọc System Call Để Thu Hẹp Bề Mặt Tấn Công

Seccomp là gì và tại sao cần quan tâm?

Seccomp (Secure Computing Mode) là tính năng kernel Linux cho phép giới hạn các system call mà tiến trình trong container có thể thực hiện. Profile mặc định của Docker chặn khoảng 44 syscall trong tổng số hơn 300 — đó là mức bảo vệ cơ bản, nhưng chưa đủ.

Với custom profile, bạn có thể chặn tới 80% syscall không cần thiết, chỉ cho phép khoảng 73 syscall thực sự cần cho ứng dụng. Nghe ấn tượng phải không?

Kiểm tra hỗ trợ seccomp trên hệ thống

# Kiểm tra kernel có hỗ trợ seccomp không
grep SECCOMP /boot/config-$(uname -r)
# Kết quả mong đợi:
# CONFIG_SECCOMP=y
# CONFIG_SECCOMP_FILTER=y

# Xác nhận Docker đang sử dụng seccomp
docker info | grep seccomp
# Kết quả: seccomp Profile: builtin

Tạo custom seccomp profile

Profile mặc định là điểm xuất phát tốt, nhưng custom profile phù hợp với ứng dụng cụ thể sẽ an toàn hơn nhiều. Quy trình gồm ba bước: phân tích syscall ứng dụng thực sự sử dụng, tạo profile chỉ cho phép những syscall đó, rồi test lại để đảm bảo ứng dụng vẫn chạy bình thường.

# Bước 1: Chạy container với strace để ghi lại syscall
docker run --rm --security-opt seccomp=unconfined \
  strace -c -f -S name myapp:latest 2>&1 | tail -50

# Bước 2: Tải profile mặc định làm nền tảng
wget -O custom-seccomp.json \
  https://raw.githubusercontent.com/moby/moby/master/profiles/seccomp/default.json

# Bước 3: Chạy container với custom profile
docker run --rm -d \
  --security-opt seccomp=custom-seccomp.json \
  --name myapp-secure myapp:latest

# Bước 4: Kiểm tra profile đang được áp dụng
docker inspect myapp-secure | grep -A5 SecurityOpt

Dưới đây là ví dụ custom seccomp profile cho web application — chỉ cho phép những syscall tối thiểu cần thiết:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "defaultErrnoRet": 1,
  "archMap": [
    {
      "architecture": "SCMP_ARCH_X86_64",
      "subArchitectures": ["SCMP_ARCH_X86", "SCMP_ARCH_X32"]
    }
  ],
  "syscalls": [
    {
      "names": [
        "accept", "accept4", "bind", "brk", "clone", "close",
        "connect", "dup", "dup2", "epoll_create1", "epoll_ctl",
        "epoll_wait", "execve", "exit", "exit_group", "fcntl",
        "fstat", "futex", "getdents64", "getpid", "getsockname",
        "getsockopt", "ioctl", "listen", "lseek", "mmap",
        "mprotect", "munmap", "nanosleep", "openat", "pipe2",
        "poll", "read", "recvfrom", "rt_sigaction",
        "rt_sigprocmask", "sendto", "setsockopt", "shutdown",
        "socket", "stat", "write", "writev"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

Với profile tùy chỉnh như trên, container chỉ có thể thực hiện khoảng 43 syscall — so với hơn 300 syscall mà kernel hỗ trợ. Bất kỳ syscall nào ngoài danh sách sẽ bị từ chối ngay, ngăn chặn hiệu quả rất nhiều kỹ thuật tấn công phổ biến.

AppArmor Và SELinux — Kiểm Soát Truy Cập Bắt Buộc Cho Container

AppArmor profile cho Docker container

AppArmor (Application Armor) là hệ thống MAC (Mandatory Access Control) phổ biến trên Ubuntu và Debian. Docker tự động áp dụng AppArmor profile mặc định cho mỗi container, nhưng thực tế thì bạn nên tạo custom profile cho từng ứng dụng để kiểm soát chặt chẽ hơn.

Đặc biệt chú ý phần deny shell execution ở cuối — đây là biện pháp cực kỳ hiệu quả để ngăn reverse shell:

# Kiểm tra AppArmor đang hoạt động
sudo aa-status

# Tạo custom AppArmor profile cho web container
sudo tee /etc/apparmor.d/docker-webapp << 'EOF'
#include 

profile docker-webapp flags=(attach_disconnected,mediate_deleted) {
  #include 
  #include 

  # Mạng — chỉ cho phép TCP/UDP
  network inet tcp,
  network inet udp,
  network inet6 tcp,
  network inet6 udp,

  # Từ chối truy cập các đường dẫn nhạy cảm
  deny /etc/shadow r,
  deny /etc/passwd w,
  deny /proc/sys/** w,
  deny /sys/** w,

  # Cho phép đọc thư mục ứng dụng
  /app/** r,
  /app/tmp/** rw,

  # Cho phép thực thi binary cần thiết
  /usr/bin/python3 ix,
  /usr/bin/node ix,

  # Từ chối thực thi shell (ngăn reverse shell)
  deny /bin/sh x,
  deny /bin/bash x,
  deny /bin/dash x,
  deny /usr/bin/wget x,
  deny /usr/bin/curl x,
}
EOF

# Tải profile vào kernel
sudo apparmor_parser -r /etc/apparmor.d/docker-webapp

# Chạy container với custom AppArmor profile
docker run --rm -d \
  --security-opt apparmor=docker-webapp \
  --name webapp myapp:latest

SELinux cho container trên RHEL/Fedora

Nếu bạn đang ở hệ sinh thái RHEL, Fedora hoặc CentOS Stream thì SELinux là lựa chọn tự nhiên hơn. Podman tích hợp sâu với SELinux, tự động gán label phù hợp cho container — tiện hơn Docker khá nhiều trong khía cạnh này:

# Kiểm tra SELinux đang ở chế độ Enforcing
getenforce
# Kết quả mong đợi: Enforcing

# Xem context SELinux của container đang chạy
podman inspect --format='{{.ProcessLabel}}' mycontainer
# Kết quả ví dụ: system_u:system_r:container_t:s0:c123,c456

# Chạy container với SELinux label cụ thể
podman run --rm -d \
  --security-opt label=type:container_t \
  --security-opt label=level:s0:c200,c300 \
  myapp:latest

# Mount volume với SELinux label tự động
# :Z cho private (chỉ container này truy cập)
# :z cho shared (nhiều container có thể truy cập)
podman run --rm -d \
  -v /data/app:/app:Z \
  myapp:latest

# Kiểm tra audit log nếu container bị SELinux chặn
sudo ausearch -m AVC -ts recent | grep container

Gia Cố Container Runtime — Các Biện Pháp Thiết Yếu

Loại bỏ capabilities không cần thiết

Docker mặc định cấp khoảng 14 Linux capabilities cho container. Podman thì an toàn hơn với chỉ 11. Nhưng nguyên tắc tốt nhất vẫn là: loại bỏ tất cả, rồi chỉ thêm lại đúng những gì thực sự cần.

# TUYỆT ĐỐI KHÔNG chạy container với --privileged
# docker run --privileged ...  # Cấp TẤT CẢ capabilities!

# Loại bỏ tất cả capabilities, chỉ thêm lại những gì cần
docker run --rm -d \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --name webapp nginx:alpine

# Xem capabilities thực tế của container
docker exec webapp cat /proc/1/status | grep Cap

# Giải mã capabilities bằng capsh
docker exec webapp capsh --decode=00000000a80425fb

Ngăn chặn leo thang đặc quyền và filesystem read-only

Đây là combo gia cố mà mình luôn áp dụng cho mọi container production. Mỗi flag đều có mục đích rõ ràng:

# Container được gia cố hoàn toàn
docker run --rm -d \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=100m \
  --tmpfs /var/run:rw,noexec,nosuid,size=10m \
  --memory=512m \
  --memory-swap=512m \
  --cpus=1.0 \
  --pids-limit=100 \
  --security-opt no-new-privileges:true \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --name webapp-hardened myapp:latest

# Xác nhận cấu hình
docker inspect webapp-hardened | jq '{
  ReadonlyRootfs: .HostConfig.ReadonlyRootfs,
  Memory: .HostConfig.Memory,
  NanoCpus: .HostConfig.NanoCpus,
  PidsLimit: .HostConfig.PidsLimit,
  SecurityOpt: .HostConfig.SecurityOpt,
  CapDrop: .HostConfig.CapDrop
}'

Giải thích nhanh các tham số quan trọng:

  • --read-only: Filesystem của container chỉ đọc — kẻ tấn công không thể ghi malware vào container
  • --tmpfs /tmp:rw,noexec,nosuid: Thư mục tạm có thể ghi nhưng không thể thực thi file — ngăn việc tải và chạy mã độc
  • --memory=512m --memory-swap=512m: Giới hạn RAM cứng, ngăn tấn công tiêu hao tài nguyên
  • --pids-limit=100: Giới hạn số process — ngăn fork bomb
  • --security-opt no-new-privileges:true: Ngăn container lấy thêm quyền qua setuid/setgid

Bảo Mật Kubernetes — RBAC, Pod Security Và Network Policy

Cấu hình RBAC theo nguyên tắc đặc quyền tối thiểu

Kubernetes RBAC mạnh mẽ nhưng cũng rất dễ cấu hình sai. Sai lầm phổ biến nhất mà mình thấy là cấp quyền cluster-admin cho service account — điều này cho phép kẻ tấn công di chuyển ngang hàng trong toàn bộ cluster nếu chiếm được một pod. Đừng làm vậy.

# Tạo Role giới hạn cho namespace cụ thể
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: app-reader
rules:
- apiGroups: [""]
  resources: ["pods", "services", "configmaps"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch"]

---
# Bind Role cho ServiceAccount cụ thể
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: app-reader-binding
  namespace: production
subjects:
- kind: ServiceAccount
  name: myapp-sa
  namespace: production
roleRef:
  kind: Role
  name: app-reader
  apiGroup: rbac.authorization.k8s.io

Nguyên tắc vàng: luôn dùng Role (giới hạn trong namespace) thay vì ClusterRole khi có thể. Chỉ cấp đúng những verb cần thiết — getlist thay vì *. Và tuyệt đối không bao giờ gán cluster-admin cho service account của ứng dụng.

Pod Security Standards — thay thế PodSecurityPolicy

Kể từ Kubernetes 1.25, PodSecurityPolicy đã bị loại bỏ hoàn toàn. Thay vào đó, Pod Security Standards (PSS) với ba mức — Privileged, Baseline và Restricted — được sử dụng thông qua Pod Security Admission. Nếu bạn vẫn đang dùng PSP thì đã đến lúc chuyển đổi:

# Áp dụng Pod Security Standards cho namespace
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    # Enforce mức Restricted — an toàn nhất
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    # Cảnh báo nếu vi phạm Restricted
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: latest

---
# Pod tuân thủ mức Restricted
apiVersion: v1
kind: Pod
metadata:
  name: secure-app
  namespace: production
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: myapp:v1.0
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
          - ALL
    resources:
      limits:
        memory: "512Mi"
        cpu: "500m"
      requests:
        memory: "256Mi"
        cpu: "250m"
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir:
      sizeLimit: 100Mi

Network Policy — phân đoạn mạng cho container

Mặc định, mọi pod trong Kubernetes có thể giao tiếp tự do với nhau. Nghe thì tiện, nhưng đây thực ra là rủi ro lớn. Nếu một pod bị chiếm, kẻ tấn công có thể di chuyển ngang hàng tự do trong toàn bộ cluster.

NetworkPolicy giải quyết vấn đề này: deny-all trước, rồi chỉ mở đúng traffic cần thiết.

# Bước 1: Deny-all mặc định — chặn tất cả traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

---
# Bước 2: Cho phép traffic cụ thể cho webapp
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-webapp-traffic
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: webapp
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-nginx
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: postgresql
    ports:
    - protocol: TCP
      port: 5432
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: UDP
      port: 53

Giám Sát Runtime Với Falco — Phát Hiện Mối Đe Dọa Theo Thời Gian Thực

Tại sao cần giám sát runtime?

Dù image sạch, cấu hình Kubernetes hoàn hảo, seccomp và AppArmor đều đã áp dụng — bạn vẫn chưa an toàn hoàn toàn. Thực tế là lỗ hổng zero-day hoặc các cuộc tấn công tinh vi vẫn có thể vượt qua mọi hàng phòng thủ tĩnh.

Đây là lúc giám sát runtime phát huy vai trò — phát hiện hành vi bất thường trong container đang chạy, theo thời gian thực. Nghĩ đơn giản thì nó giống camera an ninh cho container của bạn vậy.

Triển khai Falco trên Kubernetes

Falco là công cụ bảo mật runtime hàng đầu cho Kubernetes — dự án đã tốt nghiệp (Graduated) từ CNCF. Falco sử dụng eBPF để hook vào kernel Linux và giám sát system call với overhead cực thấp, thường dưới 5% CPU mỗi node.

# Cài đặt Falco trên Kubernetes bằng Helm
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm repo update

# Triển khai Falco với modern eBPF driver (kernel 5.8+)
helm install falco falcosecurity/falco \
  --namespace falco --create-namespace \
  --set driver.kind=modern_ebpf \
  --set falcosidekick.enabled=true \
  --set falcosidekick.config.slack.webhookurl="https://hooks.slack.com/services/xxx"

# Kiểm tra Falco đang chạy
kubectl get pods -n falco
kubectl logs -n falco -l app.kubernetes.io/name=falco --tail=20

Falco được triển khai dưới dạng DaemonSet, đảm bảo mỗi node trong cluster đều được giám sát. Cảnh báo được làm giàu thêm metadata từ Kubernetes — tên pod, namespace, container image — giúp bạn nhanh chóng xác định nguồn gốc vấn đề.

Viết custom Falco rule

Falco đi kèm thư viện rule mặc định khá phong phú, phát hiện các mối đe dọa phổ biến như shell spawn trong container, đọc file nhạy cảm, hoặc kết nối mạng bất thường. Nhưng mình luôn khuyến khích tạo thêm rule phù hợp với môi trường cụ thể của bạn:

# /etc/falco/rules.d/custom-rules.yaml

# Phát hiện shell được mở trong container
- rule: Shell Spawned in Container
  desc: Phát hiện khi shell được khởi chạy trong container
  condition: >
    spawned_process and container and
    proc.name in (bash, sh, zsh, dash, csh, ksh) and
    not proc.pname in (crond, sshd)
  output: >
    Shell spawned in container
    (user=%user.name container=%container.name
    image=%container.image.repository
    shell=%proc.name parent=%proc.pname
    namespace=%k8s.ns.name pod=%k8s.pod.name)
  priority: WARNING
  tags: [container, shell, mitre_execution]

# Phát hiện đọc file nhạy cảm
- rule: Read Sensitive File in Container
  desc: Phát hiện khi container đọc file nhạy cảm
  condition: >
    open_read and container and
    fd.name in (/etc/shadow, /etc/sudoers,
                /root/.ssh/authorized_keys,
                /root/.bash_history)
  output: >
    Sensitive file read in container
    (user=%user.name file=%fd.name
    container=%container.name
    image=%container.image.repository
    namespace=%k8s.ns.name pod=%k8s.pod.name)
  priority: CRITICAL
  tags: [container, filesystem, mitre_credential_access]

# Phát hiện kết nối mạng bất thường
- rule: Unexpected Outbound Connection
  desc: Container tạo kết nối ra ngoài đến port không mong đợi
  condition: >
    outbound and container and
    not fd.sport in (80, 443, 53, 5432, 6379, 3306) and
    not k8s.ns.name = kube-system
  output: >
    Unexpected outbound connection from container
    (command=%proc.cmdline connection=%fd.name
    container=%container.name
    image=%container.image.repository
    namespace=%k8s.ns.name pod=%k8s.pod.name)
  priority: NOTICE
  tags: [container, network, mitre_command_and_control]

Tích Hợp Bảo Mật Vào CI/CD Pipeline — Shift Left

Pipeline bảo mật container hoàn chỉnh

Bảo mật container hiệu quả nhất khi được tích hợp từ sớm vào CI/CD pipeline — đừng chờ đến lúc deploy mới bắt đầu kiểm tra. Dưới đây là ví dụ pipeline với GitLab CI, bao gồm lint Dockerfile, quét lỗ hổng, ký image và deploy có xác minh:

# .gitlab-ci.yml — Pipeline bảo mật container
stages:
  - build
  - scan
  - sign
  - deploy

# Quét Dockerfile để phát hiện cấu hình sai
dockerfile-lint:
  stage: scan
  image: hadolint/hadolint:latest
  script:
    - hadolint Dockerfile --format json > hadolint-report.json
  artifacts:
    reports:
      codequality: hadolint-report.json

# Build image
build-image:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

# Quét lỗ hổng với Trivy
vulnerability-scan:
  stage: scan
  needs: [build-image]
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL
        --ignore-unfixed $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - trivy image --format spdx-json
        --output sbom.json $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  artifacts:
    paths:
      - sbom.json

# Ký image với Cosign
sign-image:
  stage: sign
  needs: [vulnerability-scan]
  script:
    - cosign sign --key $COSIGN_KEY $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

# Deploy với xác minh chữ ký
deploy:
  stage: deploy
  needs: [sign-image]
  script:
    - cosign verify --key $COSIGN_PUB $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - kubectl set image deployment/myapp
        app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

Policy-as-Code với Kyverno

Kyverno là policy engine dành riêng cho Kubernetes. Điểm mạnh lớn nhất của nó so với OPA Gatekeeper? Không cần học ngôn ngữ mới — mọi thứ đều là YAML quen thuộc:

# Kyverno policy: Chặn container chạy với quyền root
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-non-root
spec:
  validationFailureAction: Enforce
  rules:
  - name: check-runAsNonRoot
    match:
      any:
      - resources:
          kinds:
          - Pod
    validate:
      message: "Container phải chạy với user không phải root"
      pattern:
        spec:
          containers:
          - securityContext:
              runAsNonRoot: true

---
# Kyverno policy: Yêu cầu image phải được ký
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signature
spec:
  validationFailureAction: Enforce
  rules:
  - name: verify-cosign-signature
    match:
      any:
      - resources:
          kinds:
          - Pod
    verifyImages:
    - imageReferences:
      - "myregistry.io/*"
      attestors:
      - entries:
        - keys:
            publicKeys: |-
              -----BEGIN PUBLIC KEY-----
              MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
              -----END PUBLIC KEY-----

Checklist Gia Cố Container — Tổng Hợp Thực Hành

Nói nhiều rồi, giờ tổng hợp lại. Dưới đây là bảng tất cả các biện pháp bảo mật container đã đề cập, kèm theo công cụ và mức độ ưu tiên để bạn biết nên bắt đầu từ đâu:

Biện pháp Công cụ / Phương pháp Ưu tiên
Cập nhật runC lên phiên bản mới nhất apt/dnf update runc Khẩn cấp
Sử dụng distroless/minimal image Multi-stage build, gcr.io/distroless Cao
Ghim phiên bản base image Tag cụ thể + digest SHA256 Cao
Quét lỗ hổng image Trivy, Grype, Snyk Cao
Ký và xác minh image Cosign (Sigstore) Trung bình
Chạy rootless container Docker rootless, Podman Cao
Loại bỏ capabilities --cap-drop ALL + --cap-add Cao
Ngăn leo thang đặc quyền no-new-privileges Cao
Áp dụng seccomp profile Custom seccomp JSON Cao
Cấu hình AppArmor/SELinux Custom profile Cao
Filesystem read-only --read-only + tmpfs Trung bình
RBAC đặc quyền tối thiểu Kubernetes Role/RoleBinding Cao
Pod Security Standards PSA enforce: restricted Cao
Network Policy Default deny + whitelist Cao
Giám sát runtime Falco + eBPF Cao
Policy-as-Code Kyverno, OPA Gatekeeper Trung bình
Tích hợp quét vào CI/CD Trivy, Hadolint trong pipeline Cao

Kết Luận — Bảo Mật Container Là Hành Trình Liên Tục

Bảo mật container trong năm 2026 không phải là một checkbox để đánh dấu xong rồi quên đi. Đó là một hành trình liên tục đòi hỏi cách tiếp cận toàn diện — từ build time (image an toàn, quét lỗ hổng, ký image) qua deploy time (RBAC, Pod Security, Network Policy) cho đến runtime (Falco, eBPF monitoring).

Các lỗ hổng runC nghiêm trọng trong năm 2025 nhắc nhở chúng ta rằng container isolation không bao giờ là tuyệt đối. Chiến lược phòng thủ chiều sâu — kết hợp nhiều lớp bảo mật từ rootless container, seccomp, AppArmor/SELinux đến runtime monitoring — là cách duy nhất để giảm thiểu rủi ro hiệu quả.

Hãy bắt đầu từ những bước cơ bản nhất: cập nhật runC, chạy rootless, loại bỏ capabilities không cần thiết. Sau đó tiến dần đến custom seccomp profile, AppArmor/SELinux, Falco và policy-as-code. Mỗi lớp bảo mật bạn thêm vào đều thu hẹp bề mặt tấn công — và thực tế, đó chính là mục tiêu cuối cùng của tất cả chúng ta.

Về Tác Giả Editorial Team

Our team of expert writers and editors.