Vấn đề: Cái giá của việc lạm dụng quyền Root
Hồi mới làm Sysadmin, mình có một thói quen rất tệ: hễ dịch vụ nào lỗi permission là mình tương ngay sudo hoặc chạy thẳng bằng user root. Lúc đó mình chỉ nghĩ đơn giản là miễn nó chạy được là xong. Cho đến một ngày, một container Docker bị dính lỗ hổng thực thi mã từ xa. Vì mình chạy Docker với quyền root mặc định, kẻ tấn công đã thoát khỏi container và chiếm luôn quyền kiểm soát con server vật lý.
Đây là minh chứng rõ nhất cho việc vi phạm nguyên tắc Least Privilege (Quyền hạn tối thiểu). Khi bạn chạy Nginx, Database hay Container bằng root, bạn đang mở toang cửa hệ thống. Chỉ cần một bug nhỏ trong code, hacker sẽ có ngay quyền cao nhất của hệ điều hành để xóa sạch dữ liệu hoặc cài mã độc đào tiền ảo.
Tại sao quyền Root truyền thống lại nguy hiểm?
Kernel Linux mặc định không phân biệt được “root bên trong container” và “root bên ngoài host”. Khi một tiến trình mang User ID (UID) là 0, kernel hiểu rằng nó có toàn quyền can thiệp vào phần cứng và các syscall nhạy cảm.
Các kỹ thuật cô lập như chroot chỉ thay đổi tầm nhìn về file hệ thống chứ không thay đổi bản chất quyền hạn của user. Nếu tiến trình vẫn là UID 0, nó có thể dễ dàng thực hiện các cuộc tấn công “Container Escape”. Thực tế, các lỗ hổng như CVE-2019-5736 từng cho phép hacker ghi đè file thực thi của host ngay từ bên trong container nhờ quyền root này.
Giải pháp: User Namespaces (UserNS) hoạt động thế nào?
User Namespaces cho phép chúng ta ánh xạ (mapping) một dải UID từ môi trường bên ngoài vào một dải UID khác bên trong namespace.
Hãy tưởng tượng bạn là “vua” (UID 0) trong vương quốc nhỏ của mình. Tuy nhiên, khi bước ra thế giới thực, bạn chỉ là một công dân bình thường (UID 100000). Nếu vương quốc của bạn bị đảo chính, kẻ phản loạn cũng chỉ có quyền hành trong phạm vi nhỏ đó. Chúng không thể gây hấn với hệ thống chính vì bên ngoài chúng không có đặc quyền gì cả.
Kiểm tra tính sẵn sàng của hệ thống
Các bản phân phối như Ubuntu 22.04, CentOS 8+ hay AlmaLinux thường bật sẵn tính năng này. Bạn hãy thử gõ lệnh sau:
unshare --user --map-root-user --whoami
Nếu màn hình hiện root mà bạn không cần dùng sudo, hệ thống của bạn đã sẵn sàng cho UserNS.
Cấu hình User Namespaces thủ công
Để hiểu rõ bản chất, chúng ta sẽ tự tay thiết lập một môi trường cô lập mà không cần dùng đến các công cụ tự động.
1. Thiết lập dải UID phụ (Subordinate UIDs)
Linux quản lý việc ánh xạ qua hai file cấu hình: /etc/subuid và /etc/subgid. Mỗi dòng tại đây sẽ cấp một dải ID riêng cho từng user.
# Cấp dải ID cho user 'vinh'
sudo usermod --add-subuids 100000-165535 vinh
sudo usermod --add-subgids 100000-165535 vinh
Ở ví dụ này, user vinh được phép quản lý 65,536 ID bên ngoài, bắt đầu từ ID 100,000. Đây là con số tiêu chuẩn để đảm bảo không bị trùng lặp với các user hệ thống khác.
2. Khởi tạo không gian cô lập bằng unshare
Bây giờ, hãy tạo một shell mới mà ở đó bạn có quyền root ảo:
unshare --user --map-root-user --mount --bash
Kiểm tra danh tính bên trong shell bằng lệnh id:
uid=0(root) gid=0(root) groups=0(root)
Đừng để con số 0 đánh lừa. Hãy thử tạo một file trong /tmp, sau đó dùng một terminal khác bên ngoài để kiểm tra. Bạn sẽ thấy owner của file đó thực chất là UID 1000 hoặc 100000, hoàn toàn không phải root của hệ thống.
Ứng dụng: Chạy Rootless Docker và Podman
Trong thực tế, mình luôn khuyến khích anh em chuyển sang Rootless Mode cho các dịch vụ container để tăng tối đa bảo mật.
Podman: Lựa chọn tối ưu cho UserNS
Podman hỗ trợ UserNS mượt mà hơn Docker nhờ kiến trúc không phụ thuộc vào daemon. Khi bạn chạy một container Nginx bằng Podman dưới quyền user thường:
podman run -d --name web-secure -p 8080:80 nginx
Tiến trình Nginx cần quyền root để bind vào port 80 bên trong container. Nhờ UserNS, nó có quyền đó nhưng thực tế trên máy host, nó chỉ là một tiến trình của user thường. Hacker nếu chiếm được container cũng không thể đọc file /etc/shadow hay can thiệp vào kernel.
Kinh nghiệm thực chiến và lưu ý
1. Xử lý lỗi phân quyền Volume
Lỗi phổ biến nhất khi dùng UserNS là mount volume bị sai permission. File trên host thuộc UID 1000 nhưng bên trong container lại cần UID 0 ảo để đọc ghi.
Mẹo: Với Podman, hãy thêm hậu tố :U vào tham số mount. Nó sẽ tự động thực hiện chown dải ID trong namespace cho bạn:
podman run -v ./data:/data:U nginx
2. Khắc phục lỗi newuidmap
Nếu gặp thông báo newuidmap: write to uid_map failed, hãy kiểm tra ngay file /etc/subuid. Lỗi này thường do bạn chưa khai báo dải ID hoặc dải ID của các user đang bị chồng lấn lên nhau.
3. Đừng quên giới hạn tài nguyên
UserNS chỉ lo bảo mật về quyền hạn. Một tiến trình bị hack vẫn có thể ngốn sạch RAM của server. Bạn nên kết hợp UserNS với Cgroups để giới hạn tài nguyên. Một bên lo chặn quyền truy cập, một bên lo chặn việc vắt kiệt phần cứng.
Lời kết
Việc làm quen với User Namespaces có thể hơi rắc rối lúc đầu do khái niệm ánh xạ ID. Tuy nhiên, đây là lớp giáp cực kỳ vững chắc cho server. Hãy bắt đầu bằng việc chuyển các container sang chạy chế độ Rootless. Bạn sẽ thấy yên tâm hơn rất nhiều khi biết rằng dù ứng dụng có lỗi, hệ thống chính vẫn luôn an toàn.

