Secure Ubuntu Server Cho Docker: Firewall, User Namespace, Secret Management
Docker giúp triển khai ứng dụng nhanh, nhất quán, dễ mở rộng. Nhưng Docker cũng mở thêm bề mặt tấn công: daemon quyền cao, container thoát ly chưa đủ, port public quá rộng, secret nằm lộ trong image/env/log. Một server Ubuntu chạy Docker “mặc định” thường chạy được, nhưng chưa chắc an toàn.
Bài viết này tập trung 3 lớp quan trọng: firewall, user namespace, secret management. Mục tiêu: giảm quyền, giảm lộ port, giảm rò secret → nếu app/container bị compromise, thiệt hại vẫn bị giới hạn.
1. Tư duy bảo mật Docker trên Ubuntu
Trước khi cấu hình, cần hiểu vài nguyên tắc:
– Docker daemon rất quyền lực: user thuộc group docker gần như có quyền root trên host.
– Container không phải VM: dùng chung kernel host → kernel attack surface quan trọng.
– Port published = public exposure nếu firewall không chặn.
– Secret trong image/env dễ rò qua docker inspect, log, CI/CD, layer cache.
– Defense-in-depth: firewall + least privilege + secret isolation + update định kỳ.
2. Firewall: kiểm soát traffic trước khi tới container
Vì sao UFW chưa đủ với Docker?
Docker tự thêm rule iptables để publish port. Vì vậy, nhiều người bật UFW nhưng container vẫn public port ngoài ý muốn.
Ví dụ:
docker run -p 8080:80 nginx
Port 8080 có thể mở ra Internet, dù UFW không allow rõ ràng. Lý do: Docker thao tác chain iptables riêng, traffic forwarded có thể đi qua rule Docker trước.
Nguyên tắc publish port an toàn
Không publish mọi thứ ra 0.0.0.0 nếu không cần.
Sai:
docker run -p 5432:5432 postgres
Đúng hơn, chỉ bind localhost:
docker run -p 127.0.0.1:5432:5432 postgres
Khi đó database chỉ accessible từ host, không public Internet.
Chặn toàn bộ inbound vào Docker từ interface public, trừ port cần thiết:
sudo iptables -I DOCKER-USER -i eth0 -j DROP
Nhưng cẩn thận: rule này có thể làm app mất public access. Nên test trong maintenance window.
Để persist iptables:
sudo apt install -y iptables-persistent
sudo netfilter-persistent save
Reverse proxy pattern
Pattern an toàn hơn:
– Chỉ expose 80/443 public.
– App container không publish public port.
– Nginx/Traefik/Caddy reverse proxy route nội bộ.
– DB/cache chỉ nằm trong Docker network private.
internal: true giúp network không có external connectivity trực tiếp.
3. User Namespace: giảm tác hại nếu container bị chiếm
Vấn đề root trong container
Mặc định, root trong container thường map tới root trên host namespace liên quan. Dù Docker có isolation, nhiều lỗ hổng container escape từng khai thác quyền này. Vì vậy: container root nên map sang unprivileged user trên host.
Bật user namespace remapping
Docker hỗ trợ userns-remap. Khi bật, UID 0 trong container được map sang UID không đặc quyền trên host, ví dụ dockremap.
Tạo hoặc dùng user mặc định:
sudo dockerd --userns-remap=default
Cách chuẩn: cấu hình daemon.
Tạo file:
sudo nano /etc/docker/daemon.json
Nội dung:
{
"userns-remap": "default"
}
Restart Docker:
sudo systemctl restart docker
Kiểm tra:
docker info | grep -i userns
Docker sẽ tạo user/group dockremap, dùng /etc/subuid, /etc/subgid.
Kiểm tra:
grep dockremap /etc/subuid /etc/subgid
Lưu ý khi bật userns-remap
Bật userns-remap có thể ảnh hưởng:
– Volume bind mount bị permission denied.
– Container cần quyền đặc biệt có thể lỗi.
– Image/app chạy root nhưng ghi file host mount sẽ gặp mismatch UID.
– Existing containers/images có thể cần recreate.
Ví dụ fix permission cho volume path:
sudo chown -R 100000:100000 /srv/myapp/data
UID thực tế phụ thuộc /etc/subuid. Kiểm tra trước khi chown.
Chạy container bằng non-root user
User namespace tốt, nhưng vẫn nên chạy process trong container bằng user thường.
Dockerfile:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN addgroup -S app && adduser -S app -G app
USER app
CMD ["node", "server.js"]
Compose:
services:
app:
image: myapp
user: "1000:1000"
Giảm capability
Container không cần mọi Linux capability.
Compose:
services:
app:
image: myapp
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
Nếu không cần bind port thấp, bỏ cả NET_BIND_SERVICE.
– UFW deny incoming.
– Chỉ allow 22, 80, 443.
– DB/cache bind 127.0.0.1 hoặc internal network.
– Dùng DOCKER-USER chain để chặn traffic ngoài ý muốn.
– Không publish port nếu service chỉ dùng nội bộ.
– Không commit .env, secrets/.
– Không bake secret vào image.
– Không truyền secret qua command line.
– Ưu tiên Docker secrets/secret manager.
– Đọc secret từ file.
– Rotate định kỳ.
– Mask log.
Kết luận: an toàn hơn bằng vài quyết định đúng
Secure Docker trên Ubuntu không phải cài một tool rồi xong. Đó là tập hợp quyết định nhỏ nhưng quan trọng: chỉ mở port cần thiết, không để container root quá mạnh, không để secret nằm lộ thiên.
Nếu cần ưu tiên, hãy làm theo thứ tự:
1. Khóa firewall, chỉ public reverse proxy.
2. Bind service nội bộ vào 127.0.0.1 hoặc Docker network private.
3. Bật userns-remap, chạy app non-root.
4. Drop capability, tránh privileged.
5. Chuyển secret khỏi env/image sang file secret hoặc secret manager.
6. Cập nhật, scan image, rotate secret định kỳ.
Kết quả: khi một container bị lỗi hoặc bị khai thác, attacker khó pivot ra host, khó truy cập service nội bộ, khó lấy credential lâu dài. Đây chính là mục tiêu thực tế của hardening: không hứa “bất khả xâm phạm”, nhưng giảm mạnh blast radius.