Hướng dẫn cấu hình User Namespace Remapping (userns-remap) trong Docker

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

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.

Share: