Secure Ubuntu chạy Docker: Firewall, User Namespace, Secrets

14/05/2026 · P T P · Chung

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ỳ.

Checklist nền:

sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg ufw

Kiểm tra Docker:

docker version
docker info

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.

Với Docker Compose:

services:
  db:
    image: postgres:16
    ports:
      - "127.0.0.1:5432:5432"

Nếu app cùng network nội bộ Docker, thường không cần ports, dùng expose hoặc chỉ service name:

services:
  app:
    image: myapp
    depends_on:
      - db

db: image: postgres:16 expose: - "5432"

Cấu hình UFW cơ bản

Allow SSH trước:

sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable
sudo ufw status verbose

Allow HTTP/HTTPS nếu server chạy reverse proxy:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Không allow DB/cache/internal service trừ khi thật sự cần.

Chặn Docker published ports bằng DOCKER-USER chain

Docker cung cấp chain DOCKER-USER: rule ở đây được apply trước rule Docker. Đây là nơi phù hợp để enforce firewall.

Ví dụ: chỉ cho IP 203.0.113.10 truy cập port 8080 container-published:

sudo iptables -I DOCKER-USER -i eth0 -p tcp --dport 8080 ! -s 203.0.113.10 -j DROP

Cho phép established traffic:

sudo iptables -I DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

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.

Ví dụ Compose:

services:
  proxy:
    image: caddy:2
    ports:
      - "80:80"
      - "443:443"
    networks:
      - frontend
      - backend

app: image: myapp networks: - backend

db: image: postgres:16 networks: - backend

networks: frontend: backend: internal: true

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.

Thêm hardening:

security_opt:
  - no-new-privileges:true
read_only: true
tmpfs:
  - /tmp

no-new-privileges chặn process gain privilege qua setuid/setgid. read_only giảm persistence nếu attacker ghi file.

Tránh privileged mode

Tránh:

privileged: true

privileged gần như phá nhiều lớp isolation. Chỉ dùng cho case đặc biệt, hiểu rõ risk.


4. Secret Management: không để mật khẩu trôi nổi

Secret không nên nằm ở đâu?

Không đặt secret trong:

– Dockerfile:

ENV DB_PASSWORD=supersecret

– Image layer.
– Git repo.
.env commit nhầm.
– Command line:

docker run -e DB_PASSWORD=supersecret app

– Log app.
– CI output.

Vì có thể lộ qua:

docker inspect container_name
docker history image_name
history
journalctl

Dùng Docker secrets khi có Swarm

Docker secrets hoạt động tốt trong Swarm. Secret được mount dạng file trong container, thường tại /run/secrets/....

Khởi tạo Swarm:

docker swarm init

Tạo secret:

echo "strong-password" | docker secret create db_password -

Compose cho Swarm:

version: "3.8"

services: app: image: myapp secrets: - db_password environment: DB_PASSWORD_FILE: /run/secrets/db_password

secrets: db_password: external: true

Deploy:

docker stack deploy -c docker-compose.yml mystack

App nên đọc secret từ file, không từ env trực tiếp.

Compose không Swarm: dùng file secret cẩn thận

Docker Compose hỗ trợ secrets, nhưng không bảo mật mạnh như Swarm; thường là bind mount file. Vẫn tốt hơn env nếu permission đúng.

services:
  app:
    image: myapp
    secrets:
      - db_password

secrets: db_password: file: ./secrets/db_password.txt

Set permission:

mkdir -p secrets
chmod 700 secrets
chmod 600 secrets/db_password.txt

Thêm .gitignore:

secrets/
.env

Dùng external secret manager

Với production nghiêm túc, dùng:

– HashiCorp Vault
– AWS Secrets Manager
– GCP Secret Manager
– Azure Key Vault
– SOPS + age
– Doppler, 1Password Secrets Automation

Pattern tốt:

1. CI/CD lấy secret từ manager.
2. Runtime inject qua file tạm hoặc secret backend.
3. Secret rotate được.
4. App reload hoặc restart an toàn.

Rotate secret

Secret không bất tử. Cần quy trình rotate:

– Tạo secret mới.
– Deploy app đọc secret mới.
– Update DB/API credential.
– Thu hồi secret cũ.
– Audit log access.

Ví dụ PostgreSQL:

ALTER USER app_user WITH PASSWORD 'new-strong-password';

Sau đó update Docker secret/secret manager, redeploy.

Giảm rò secret qua log

App phải mask dữ liệu nhạy cảm:

– Password
– Token
– Authorization header
– Cookie
– Private key
– Connection string

Không log full config. Không dump env ở startup.


5. Hardening bổ sung cho Ubuntu + Docker

Giới hạn quyền Docker group

User thuộc group docker có quyền rất cao:

sudo usermod -aG docker username

Chỉ cấp cho admin thật sự cần. Với automation, ưu tiên deploy qua CI runner hạn chế quyền, hoặc SSH command giới hạn.

Bật auto security updates

sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades

Dùng image nhỏ, cập nhật, scan định kỳ

Ưu tiên image official, minimal:

alpine
distroless
slim

Scan:

docker scout cves image_name

Hoặc:

trivy image image_name

Resource limit

Ngăn container lỗi/attack ăn hết RAM/CPU:

services:
  app:
    image: myapp
    mem_limit: 512m
    cpus: "1.0"
    pids_limit: 200

Logging hợp lý

Giới hạn log Docker:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

File:

sudo nano /etc/docker/daemon.json
sudo systemctl restart docker

6. Checklist cấu hình thực tế

Firewall

– 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ộ.

User namespace

– Bật userns-remap.
– Container chạy non-root.
– Drop capabilities.
no-new-privileges:true.
– Tránh privileged:true.
– Dùng read-only filesystem nếu khả thi.

Secret

– 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.

#chay #docker #firewall #secure #ubuntu
Chia sẻ:
← Trước
Chặn Hack Ubuntu Server: Giám Sát Đăng Nhập Lạ Với auditd Logwatch

Bài viết tương tự

Bình luận

Chưa có bình luận. Hãy là người đầu tiên!