Giới thiệu vấn đề: Ảo tưởng an toàn khi dùng container
Tưởng tượng bạn cho khách thuê một phòng nhỏ trong nhà. Bạn đưa chìa khóa phòng riêng, nhưng ổ khóa đó lại mở được cả cửa chính lẫn phòng ngủ của gia đình. Khách có ý đồ xấu hoặc bị kẻ trộm khống chế, cả căn nhà coi như mất trắng.
Mặc định trên Docker, điều này xảy ra mỗi ngày mà nhiều anh em ít để ý. Nếu bạn không chỉ định user lúc chạy container, tiến trình mặc định sẽ dùng tài khoản root (UID 0). Vấn đề cốt lõi nằm ở Linux kernel: kernel được dùng chung giữa container và host. Root bên trong container cũng chính là root ngoài máy chủ.
Hậu quả rất rõ ràng. Giả sử web app dính lỗi RCE hay container escape qua lỗ hổng kernel (như CVE-2024-21626 trên runc), hacker lập tức kiểm soát toàn bộ host. Họ có thể xóa sạch ổ đĩa, cắm tool đào coin hoặc dump database chỉ sau vài lệnh.
Khái niệm cốt lõi: User Namespace Remapping là gì?
Linux hỗ trợ cơ chế User Namespace nhằm ánh xạ danh tính người dùng giữa hai không gian độc lập.
Khi bạn bật userns-remap trên Docker:
- Bên trong container, tiến trình vẫn tưởng mình là root (UID 0). Service hay package manager vẫn chạy trơn tru, không gặp lỗi Permission Denied.
- Ngoài máy host, kernel chỉ coi tiến trình đó là một user thường không có quyền admin, chẳng hạn UID 165536.
Hacker có thoát khỏi container cũng chỉ kẹt lại ở một user vô danh. Họ không thể sửa file cấu hình trong /etc, không thể đọc shadow password, càng không thể chạm vào các tiến trình hệ thống.
Thực hành chi tiết: Bật userns-remap từng bước
Đợt vừa rồi mình audit lại cụm 12 server nội bộ chạy Ubuntu 22.04. Dù đã chốt tường lửa kỹ, mình vẫn quyết định kích hoạt userns-remap để bịt hẳn cửa container breakout. Toàn bộ quy trình chỉ mất chưa đầy 5 phút:
Bước 1: Kiểm tra dải UID/GID phụ trên hệ thống
Linux dùng hai file /etc/subuid và /etc/subgid để cấp dải ID phụ cho các user. Trước tiên, kiểm tra xem hai file này đã có chưa:
cat /etc/subuid
cat /etc/subgid
Nếu file chưa có hoặc đang trống, hãy thêm cấu hình cho user dockremap:
echo "dockremap:165536:65536" | sudo tee -a /etc/subuid
echo "dockremap:165536:65536" | sudo tee -a /etc/subgid
Cấu hình này cấp cho dockremap 65.536 ID phụ, từ 165536 đến 231071. Lúc này, UID 0 trong container sẽ tương ứng đúng UID 165536 trên host.
Bước 2: Cấu hình daemon Docker
Mở file /etc/docker/daemon.json (tạo mới nếu chưa có):
sudo nano /etc/docker/daemon.json
Thêm tùy chọn remap:
{
"userns-remap": "default"
}
Giá trị "default" bảo Docker tự bắt cặp với user hệ thống dockremap vừa tạo ở Bước 1.
Bước 3: Khởi động lại Docker daemon
Áp dụng thay đổi bằng lệnh restart:
sudo systemctl restart docker
Lưu ý quan trọng: Docker sẽ chuyển vùng lưu trữ image sang thư mục riêng theo UID mapping (ví dụ /var/lib/docker/165536.165536/). Các container cũ chạy trước đó sẽ không hiện trong docker ps. Đừng hoảng, bạn chỉ cần pull hoặc build lại image trong không gian mới này.
Bước 4: Kiểm chứng thực tế
Chạy thử một container Alpine ở chế độ nền:
docker run -d --name test-remap alpine sleep 3600
Xem user thực tế bên trong container:
docker exec -it test-remap id
Output bên trong vẫn là root chuẩn:
uid=0(root) gid=0(root) groups=0(root)
Bây giờ, đứng từ máy host và grep tiến trình xem sao:
ps aux | grep "sleep 3600"
Kết quả hiển thị trên host:
165536 18924 0.0 0.0 1596 4 ? Ss 10:15 0:00 sleep 3600
Tiến trình bên ngoài gắn cờ UID 165536 thay vì root. Mục tiêu cô lập hoàn toàn đạt yêu cầu.
Kết luận
User Namespace Remapping hạ cấp quyền lực của root container xuống mức user thường ngoài host mà không làm hỏng app. Chỉ mất 5 phút sửa file cấu hình, bạn đã dựng thêm một lớp phòng thủ chiều sâu vững chắc cho toàn bộ hạ tầng.

