Hướng dẫn cấu hình Linux Capabilities (cap-drop và cap-add) trong Docker để siết chặt an ninh

Docker tutorial - IT technology blog
Docker tutorial - IT technology blog

Bối cảnh: Tại sao cần siết quyền Linux Capabilities?

Đúng 2 giờ sáng, hệ thống SOC rung chuông báo động đỏ. Một web service Node.js bị khai thác lỗ hổng RCE từ thư viện npm bên thứ ba, cho phép kẻ tấn công chèn webshell. Ngay lập tức, chúng cố quét mạng nội bộ, sửa routing table và dump gói tin của máy chủ.

Rất may kịch bản tồi tệ nhất — thoát khỏi container để kiểm soát server mẹ — đã không xảy ra. Lý do rất đơn giản: container này đã bị tước sạch quyền can thiệp kernel ngay từ khi khởi chạy.

Theo mặc định, Docker không cấp toàn quyền root máy chủ cho container. Dù vậy, nó vẫn gán sẵn 14 Linux Capabilities (như CAP_NET_RAW, CAP_CHOWN, CAP_MKNOD, CAP_SYS_CHROOT…).

Nếu hacker chiếm được quyền root trong container, chúng sẽ tận dụng ngay các capability dư thừa này. Chúng có thể sniff gói tin mạng nội bộ, giả mạo ARP cache hoặc khai thác lỗi kernel chưa vá để leo thang đặc quyền ra máy chủ host.

Nguyên tắc vàng trên production là Đặc quyền tối thiểu (Least Privilege). Cách an toàn nhất: tước sạch toàn bộ quyền bằng --cap-drop=ALL, sau đó chỉ cấp lại đúng 1–2 quyền mà service thực sự cần qua --cap-add.

Chuẩn bị công cụ kiểm tra Capabilities

Để biết chính xác container đang giữ những quyền gì, bạn cần cài đặt bộ công cụ libcap trên máy Linux.

Chạy lệnh cài đặt trên Ubuntu/Debian:

sudo apt-get update && sudo apt-get install -y libcap2-bin libcap-ng-utils

Giờ hãy chạy thử một container Alpine để xem Docker mặc định cấp những gì:

# Khởi chạy container alpine thử nghiệm
docker run -d --name test-cap alpine sleep 3600

# Lấy PID của process container trên host
PID=$(docker inspect --format '{{.State.Pid}}' test-cap)

# Xem capabilities mà process đang giữ
getpcaps $PID

# Dọn dẹp sau khi kiểm tra
docker rm -f test-cap

Kết quả trả về sẽ gồm nhiều quyền như cap_chown, cap_net_raw, cap_sys_chroot. Một API microservice thông thường hoàn toàn không cần tới CAP_NET_RAW hay CAP_MKNOD.

Cấu hình thực tế: Kết hợp cap-drop và cap-add

Quy tắc thực chiến chuẩn nhất là whitelist: Drop sạch trước, Add từng quyền sau.

1. Cấu hình trực tiếp bằng Docker CLI

Xét ví dụ container Nginx cần lắng nghe cổng 80/443 và phân quyền file khi khởi động. Bạn chỉ cần cấp đúng 3 quyền: NET_BIND_SERVICE, SETUID và SETGID:

docker run -d \
  --name secure-web \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --cap-add=SETUID \
  --cap-add=SETGID \
  -p 80:80 \
  nginx:alpine

Khi đã khóa quyền, hacker dù có shell cũng không thể chạy ping hay nmap quét mạng vì mất CAP_NET_RAW. Chúng cũng không thể tạo block device ảo để chọc xuống ổ cứng host do thiếu CAP_MKNOD.

2. Chuẩn hóa cấu hình với Docker Compose

Khi quản lý hàng chục microservices, việc gõ lệnh CLI thủ công rất dễ sót cờ bảo mật. Toàn bộ thiết lập cần được đồng bộ trực tiếp vào file docker-compose.yml:

version: '3.8'

services:
  api-backend:
    image: node:20-alpine
    container_name: production-api
    working_dir: /app
    command: node server.js
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    security_opt:
      - no-new-privileges:true
    read_only: true
    tmpfs:
      - /tmp
    ports:
      - "80:80"
    restart: always

Cấu hình no-new-privileges:true là chốt chặn quan trọng. Nó ngăn chặn process con tự động nâng quyền thông qua các binary có cờ setuid/setgid như sudo hay suid.

Bảng tra cứu Linux Capabilities quan trọng

  • CAP_NET_BIND_SERVICE: Cho phép bind port dưới 1024 (80, 443). Bắt buộc phải có nếu chạy web server non-root mở port đặc quyền.
  • CAP_NET_RAW: Dùng để tạo raw packet hoặc chạy lệnh ICMP (ping). Nên DROP trên toàn bộ container web/API để chặn quét cổng và giả mạo ARP.
  • CAP_SYS_ADMIN: Quyền tương đương 90% full root. Tuyệt đối không bật cờ này trên production, trừ khi chạy Docker-in-Docker hoặc trace kernel.
  • CAP_CHOWN: Cho phép thay đổi UID/GID sở hữu file. Chỉ cấp nếu script entrypoint cần chown lại thư mục dữ liệu lúc khởi động.

Kiểm tra và giám sát đặc quyền sau khi deploy

Sau khi siết quyền, bạn cần kiểm chứng lại xem container có chạy ổn định không và quyền đã thực sự bị thu hồi hay chưa.

Kiểm tra quyền trực tiếp từ bên trong container

Đọc trạng thái process qua file /proc/1/status:

docker exec -it secure-web sh -c "grep Cap /proc/1/status"

Hệ thống trả về các chuỗi hex đại diện cho bitmap capability:

CapInh: 0000000000000000
CapPrm: 00000000000004c0
CapEff: 00000000000004c0
CapBnd: 00000000000004c0

Giải mã chuỗi hex này trên máy host bằng lệnh capsh:

capsh --decode=00000000000004c0

Màn hình hiển thị: cap_setgid,cap_setuid,cap_net_bind_service. Toàn bộ các capability thừa thãi đã bị loại bỏ hoàn toàn.

Xử lý sự cố khi ứng dụng báo lỗi Permission Denied

Nếu ứng dụng bị crash sau khi thêm --cap-drop=ALL, đừng vội bật lại quyền bừa bãi. Hãy tra cứu log container và kernel audit log trên host:

# Kiểm tra log ứng dụng
docker logs --tail 50 secure-web

# Tìm capability bị kernel chặn
sudo dmesg -T | grep -i "apparmor\|audit\|denied"

Khi thấy thông báo Operation not permitted, hãy xác định thao tác gây lỗi rồi bổ sung đúng capability đó vào cap_add. Giữ vững kỷ luật này sẽ bảo vệ toàn bộ hạ tầng Docker của bạn trước các đòn tấn công container escape.

Share: