9. Bảo mật trong Docker
Mục lục
- 1. Bảo mật là chuyện nhiều lớp
- 2. Công nghệ bảo mật cấp Linux
- 3. Bảo mật trong Swarm Mode
- 4. Docker Scout & Docker Content Trust
- 5. Docker secrets
Chương này nhắc tới 1 số sản phẩm gắn với Docker Enterprise Edition (2017) như Universal Control Plane (UCP), Docker Trusted Registry (DTR) — mảng Enterprise của Docker sau này đã được bán lại cho Mirantis, nên tên thương hiệu 1 số tính năng có thể đã đổi khác. Phần khái niệm nền tảng (namespace, cgroup, capability, MAC, seccomp, TLS trong Swarm, Docker secrets) vẫn đúng và vẫn là kiến thức nền tảng của bảo mật container ngày nay.
Từ Phần 3 tới Phần 8, mỗi phần đều nhắc qua ít nhiều tới bảo mật — namespace/cgroup cách ly và giới hạn container (Phần 3), tag/digest chống pull nhầm image (Phần 4), attack surface của image nhỏ (Phần 6), TLS mặc định của swarm (Phần 7), control plane mã hóa của overlay network (Phần 8). Phần cuối cùng này gom tất cả các mảnh rời rạc đó lại thành 1 bức tranh nhiều lớp thống nhất, và bổ sung những gì còn thiếu.
1. Bảo mật là chuyện nhiều lớp
Bảo mật tốt luôn xoay quanh việc có nhiều lớp phòng thủ, và Docker có rất nhiều lớp như vậy — hỗ trợ hầu hết công nghệ bảo mật lớn của Linux, cộng thêm 1 loạt công nghệ bảo mật của riêng mình, phần lớn đơn giản và dễ cấu hình.
💡 Hình dung: giống 1 tòa lâu đài thời trung cổ — không chỉ có 1 bức tường duy nhất, mà có hào nước, tường ngoài, tường trong, rồi tới tháp canh. Kẻ tấn công vượt qua được 1 lớp không có nghĩa là chiếm được cả lâu đài — vẫn còn nhiều lớp phòng thủ khác chặn lại. Bảo mật container cũng vậy: namespace cô lập, cgroup giới hạn, capability chặn quyền, MAC/seccomp giám sát hành vi — thủng 1 lớp chưa chắc đã nguy hiểm, vì các lớp còn lại vẫn đứng vững.
Docker trên Linux tận dụng các công nghệ bảo mật Linux quen thuộc: namespace, control group (cgroup), capability, hệ thống Mandatory Access Control (MAC), và seccomp — mỗi công nghệ đều có sẵn default hợp lý để ra khỏi hộp là tương đối an toàn, nhưng vẫn tùy biến được theo nhu cầu riêng.
Bản thân Docker platform cũng có sẵn 1 loạt công nghệ bảo mật gốc, điểm hay là chúng cực kỳ đơn giản để dùng:
- Swarm Mode an toàn theo mặc định — cryptographic node ID, mutual authentication, tự cấu hình CA, tự xoay vòng certificate, cluster store mã hóa, network mã hóa — tất cả không cần cấu hình gì thêm.
- Docker Content Trust (DCT) cho phép ký (sign) image và xác minh tính toàn vẹn + publisher của image khi pull về.
- Docker Scout phân tích image, phát hiện lỗ hổng đã biết, và trả về báo cáo chi tiết.
- Docker secrets biến "secret" (mật khẩu, chứng chỉ TLS, SSH key...) thành 1 khái niệm hạng nhất (first-class citizen) trong hệ sinh thái Docker — lưu trong cluster store mã hóa, mã hóa khi truyền tới container, và chỉ tồn tại trong filesystem in-memory lúc dùng.
2. Công nghệ bảo mật cấp Linux
Namespace — sự cách ly
Phần 3 đã giới thiệu namespace qua 3 ví dụ (pid, network, mount) để giải thích vì sao /bin/bash trong container luôn là PID 1. Dưới góc nhìn bảo mật, cần nhìn đầy đủ hơn: kernel namespace là trái tim của container — cho phép "cắt" 1 OS thành nhiều OS cô lập nhìn/hoạt động độc lập với nhau. Nhờ đó có thể chạy nhiều web server cùng đòi cổng 443 trên cùng 1 OS (mỗi app trong 1 network namespace riêng, mỗi namespace có IP + dải cổng riêng), hoặc chạy nhiều ứng dụng cần các version thư viện/config khác nhau (mỗi app trong 1 mount namespace riêng, mỗi namespace có bản sao cô lập riêng của bất kỳ thư mục nào trên hệ thống).
Docker trên Linux hiện dùng các namespace: Process ID (pid), Network (net), Filesystem/mount (mnt), Inter-process Communication (ipc), User (user), UTS. Điều quan trọng nhất cần nhớ: 1 Docker container thực chất là 1 tập hợp namespace được tổ chức có trật tự — mỗi container có pid/net/mnt/ipc/uts riêng (và có thể cả user namespace riêng).
- pid: mỗi container có process tree riêng, có PID 1 của riêng nó — không thấy và không truy cập được process tree của container khác hay của host.
- net: mỗi container có network stack cô lập riêng — interface, IP, dải cổng, routing table riêng (ví dụ mỗi container có
eth0riêng với IP/cổng riêng). - mnt: mỗi container có root filesystem
/cô lập riêng — có/etc,/var,/dev... riêng, không truy cập được mount namespace của host hay container khác. - ipc: dùng cho shared memory access trong nội bộ 1 container, đồng thời cô lập container khỏi shared memory bên ngoài.
- user: cho phép map user bên trong container sang 1 user khác trên host Linux — ví dụ map user
roottrong container sang 1 user non-root trên host (tại thời điểm sách viết, tính năng này còn khá mới và là tùy chọn). - uts: cho mỗi container 1 hostname riêng.
Control Groups (cgroup) — giới hạn tài nguyên
Nếu namespace lo về cách ly, cgroup lo về giới hạn — khái niệm đã nói ở Phần 3, và đã thực hành bằng --memory/--cpus ở Phần 5. Dưới góc nhìn bảo mật, cgroup quan trọng không chỉ vì hiệu năng: 1 container không giới hạn tài nguyên có thể bị lợi dụng làm bàn đạp cho tấn công denial-of-service nội bộ — cố tình (hoặc vô tình do bug) ngốn hết CPU/RAM/disk I/O của host, kéo sập luôn mọi container khác đang chạy chung, dù các container đó về mặt namespace vẫn hoàn toàn cách ly nhau.
Capabilities — chọn lọc quyền root
Chạy container bằng root là ý tưởng tồi (root có toàn quyền, rất nguy hiểm), nhưng chạy hẳn non-root nhiều khi lại quá bất tiện (non-root yếu tới mức gần như vô dụng). Cần 1 cách chọn lọc đúng quyền root cần thiết — đó là capabilities.
Bên dưới, tài khoản root của Linux thực chất là 1 danh sách dài các capability, ví dụ: CAP_CHOWN (đổi chủ sở hữu file), CAP_NET_BIND_SERVICE (bind socket vào cổng mạng thấp), CAP_SETUID (nâng quyền tiến trình), CAP_SYS_BOOT (reboot hệ thống)... Docker cho phép chạy container ở dạng root nhưng tước bớt những capability không cần — ví dụ nếu container chỉ cần quyền bind cổng mạng thấp, nên khởi chạy nó với toàn bộ capability root bị gỡ bỏ, rồi chỉ thêm lại đúng CAP_NET_BIND_SERVICE. Docker cũng chặn không cho container tự thêm lại capability đã bị gỡ.
Áp dụng thực tế bằng --cap-drop/--cap-add:
$ docker container run -d --name web \
--cap-drop ALL --cap-add NET_BIND_SERVICE \
nginx
$ docker container inspect --format '{{.HostConfig.CapAdd}} {{.HostConfig.CapDrop}}' web
# Output minh họa
[NET_BIND_SERVICE] [ALL]
(Output minh họa — --cap-drop ALL gỡ sạch toàn bộ capability mặc định trước, rồi --cap-add thêm lại chính xác đúng 1 quyền cần dùng; đây là mẫu hình khuyến nghị cho mọi container production: mặc định từ chối tất cả, chỉ cấp thêm những gì thực sự cần.)
Nếu container không hề cần chạy service ở cổng thấp hay bất kỳ thao tác đặc quyền nào, có thể chạy hẳn --cap-drop ALL mà không cần --cap-add lại gì — an toàn nhất trong các lựa chọn.
Mandatory Access Control (MAC)
Docker tích hợp với các hệ thống MAC lớn của Linux như AppArmor và SELinux. Tùy distro, Docker áp 1 profile AppArmor mặc định cho mọi container mới — theo tài liệu Docker, profile mặc định này "bảo vệ ở mức vừa phải trong khi vẫn tương thích rộng với nhiều ứng dụng". Vẫn có thể khởi chạy container không áp policy nào, hoặc tùy biến policy riêng theo nhu cầu.
seccomp
Docker dùng seccomp (ở filter mode) để giới hạn tập syscall mà 1 container được phép gọi tới kernel của host. Mọi container mới đều nhận 1 seccomp profile mặc định hợp lý — bảo vệ mức vừa phải mà không ảnh hưởng tới khả năng tương thích ứng dụng. Cũng như các công nghệ khác, profile này tùy biến được, hoặc có thể tắt hẳn bằng 1 flag khi khởi chạy container.
Ví dụ dễ thấy nhất: seccomp profile mặc định của Docker chặn sẵn syscall reboot — dù container có chạy bằng root, gọi reboot từ bên trong container cũng không thể khởi động lại host:
$ docker container run --rm alpine reboot
# Output minh họa
reboot: Operation not permitted
(Output minh họa — lỗi Operation not permitted tới từ chính kernel chặn syscall theo seccomp profile, không phải do thiếu quyền Linux thông thường; dù container chạy --privileged, hành vi này vẫn thường bị seccomp chặn trừ khi tường minh dùng --security-opt seccomp=unconfined.)
Tắt hẳn seccomp (không khuyến nghị, chỉ nên dùng khi debug) hoặc dùng 1 profile JSON tùy biến riêng:
$ docker container run --security-opt seccomp=unconfined alpine reboot
$ docker container run --security-opt seccomp=./custom-profile.json myapp
⚠️ Lưu ý:
seccomp=unconfinedgỡ bỏ hoàn toàn lớp phòng thủ này — chỉ nên dùng tạm thời để xác định đúng syscall nào bị chặn gây lỗi ứng dụng, sau đó nên viết 1 profile tùy biến chỉ mở thêm đúng syscall cần thiết, thay vì tắt hẳn.
3. Bảo mật trong Swarm Mode
Mỗi swarm gồm manager (control plane, cấu hình cluster + dispatch việc) và worker (chạy code ứng dụng dưới dạng container). Swarm Mode có sẵn hàng loạt tính năng bảo mật bật mặc định: cryptographic node ID, mutual authentication qua TLS, join token an toàn, tự cấu hình CA kèm tự xoay vòng certificate, cluster store (config DB) mã hóa, network mã hóa.
Dựng 1 swarm an toàn — chỉ với vài lệnh
# Trên node đầu tiên (mgr1)
$ docker swarm init
# Output minh họa
Swarm initialized: current node (d21lyz...c79qzkx) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-49nj1c...1qz1c 10.0.0.1:2377
Chỉ với đúng lệnh này, bạn đã có 1 swarm 1 node an toàn theo mặc định: mgr1 vừa là manager đầu tiên, vừa là root CA (Certificate Authority — "cơ quan cấp chứng chỉ" gốc mà mọi certificate khác trong swarm đều bắt nguồn/được xác thực dựa trên đó, tương tự cách 1 CA công cộng như Let's Encrypt ký chứng chỉ HTTPS cho website); swarm được gán 1 cryptographic ID; mgr1 tự cấp cho chính nó 1 client certificate xác định vai trò manager; chu kỳ xoay vòng certificate mặc định là 90 ngày; cluster config database được cấu hình và mã hóa; và bộ join token an toàn đã sẵn sàng để join thêm manager/worker — tất cả chỉ từ 1 lệnh duy nhất!
Join thêm manager/worker dùng đúng quy trình docker swarm join-token/docker swarm join đã đi chi tiết ở Phần 7 — phần khác biệt duy nhất đáng chú ý dưới góc nhìn bảo mật nằm ở chỗ mỗi node join vào đều tự động nhận về 1 client certificate ký bởi root CA (mgr1), chứ không chỉ đơn thuần "được thêm vào danh sách".
Cấu trúc join token
Mỗi swarm luôn duy trì 2 token riêng biệt — 1 để join manager, 1 để join worker. Vì đây là thứ duy nhất cần có để join vào swarm, phải giữ chúng thật cẩn thận (tuyệt đối không đăng công khai lên GitHub!). Token gồm 4 phần cách nhau bằng dấu -:
PREFIX - VERSION - SWARM_ID - TOKEN
PREFIX luôn là SWMTKN, VERSION là version của swarm, SWARM_ID là hash của certificate swarm — token manager và token worker của cùng 1 swarm giống hệt nhau ở 3 phần đầu, chỉ khác đúng phần TOKEN cuối. Nếu nghi ngờ token bị lộ, thu hồi và cấp lại chỉ với 1 lệnh:
$ docker swarm join-token --rotate manager
# Output minh họa
Successfully rotated manager join token.
To add a manager to this swarm, run the following command:
docker swarm join --token SWMTKN-1-49nj1c...new1 10.0.0.1:2377
(Output minh họa — token cũ ngay lập tức mất hiệu lực, ai đang cầm token cũ sẽ không join được nữa.)
TLS & mutual authentication
Mỗi manager/worker join vào swarm đều được cấp 1 client certificate — dùng cho mutual authentication (2 chiều: không chỉ node mới phải chứng minh nó thuộc swarm, mà manager tiếp nhận cũng phải chứng minh ngược lại nó đúng là 1 manager hợp lệ của chính swarm đó — khác với HTTPS thông thường, nơi chỉ server chứng minh danh tính cho client, client thì không), xác định node đó thuộc swarm nào và giữ vai trò gì (manager/worker). Xem certificate của 1 node (trên Linux):
$ sudo openssl x509 -in /var/lib/docker/swarm/certificates/swarm-node.crt -text
# Output minh họa (rút gọn)
Certificate:
Data:
Subject: O=nqjt1ke0e0jhr6xvzaxu3ok31, OU=swarm-manager, CN=dxn1zf3pgw
Validity:
Not Before: Jan 1 00:00:00 2026 GMT
Not After : Apr 1 00:00:00 2026 GMT
(Output minh họa — rút gọn; trường Not After chính là thời điểm certificate hết hạn, cách Not Before đúng 90 ngày mặc định.)
Trong certificate, trường O (organization) lưu Swarm ID, OU (organizational unit) lưu vai trò của node, CN (canonical name) lưu crypto ID của node. Xem thêm bằng docker system info để đối chiếu các giá trị này.
Cấu hình chu kỳ xoay vòng certificate bằng docker swarm update:
$ docker swarm update --cert-expiry 720h # đổi chu kỳ xoay vòng xuống 30 ngày
# Output minh họa
Swarm updated.
Cũng có thể cấu hình dùng CA bên ngoài (--external-ca khi docker swarm init), hoặc quản lý CA qua subcommand docker swarm ca.
Cluster store
Cluster store là "bộ não" của swarm — nơi lưu config và state của cả cluster, hiện dựa trên etcd, tự động replicate tới mọi manager, và mã hóa mặc định. Ngày càng nhiều tính năng của Docker platform (networking, secrets...) dựa vào cluster store — đây cũng là lý do chạy ở swarm mode ngày càng quan trọng: không chạy swarm mode nghĩa là giới hạn luôn khả năng dùng nhiều tính năng khác của Docker. Việc bảo trì hằng ngày của cluster store được Docker tự lo, nhưng ở môi trường production vẫn nên có giải pháp backup/recovery riêng cho nó.
4. Docker Scout & Docker Content Trust
Docker Scout (trước đây gọi là Docker Security Scanning)
Phát hiện nhanh lỗ hổng trong code là điều sống còn. Docker Scout quét image ở mức binary, đối chiếu phần mềm bên trong với nhiều nguồn database lỗ hổng đã biết (CVE database), rồi trả về báo cáo chi tiết kèm đề xuất fix — tích hợp sẵn trong Docker Desktop, Docker Hub, và Docker CLI (docker scout), không cần cài thêm gì. Mọi image chính thức trên Docker Hub đều được quét sẵn, báo cáo công khai ngay trên trang repo.
Docker Content Trust (DCT)
DCT giúp xác minh tính toàn vẹn và publisher của image khi pull về — đặc biệt quan trọng khi pull qua network không đáng tin cậy như internet. Ở tầm cao, DCT cho phép developer ký (sign) image khi push lên registry, và tự động xác minh chữ ký khi pull về.
Bật DCT trên 1 Docker host chỉ cần export biến môi trường:
$ export DOCKER_CONTENT_TRUST=1
Sau khi bật, bạn sẽ không còn pull/dùng được image chưa ký. DCT cũng ngăn được việc pull nhầm 1 image đã bị can thiệp (tampered) hoặc 1 image đã cũ (stale, đã có bản mới hơn thay thế).
5. Docker secrets
Nhiều ứng dụng cần "secret" — mật khẩu, chứng chỉ TLS, SSH key... Trước Docker 1.13, chưa có cách chuẩn nào để đưa secret vào app 1 cách an toàn — cách phổ biến (và không lý tưởng chút nào) là nhét secret vào biến môi trường dạng plain text.
Docker 1.13 giới thiệu Docker Secrets, biến secret thành khái niệm hạng nhất trong hệ sinh thái Docker, với hẳn 1 subcommand riêng docker secret. Phía sau, secret được mã hóa khi lưu trữ (at rest), mã hóa khi truyền (in-flight), chỉ mount vào filesystem in-memory, và chỉ những service/container được cấp quyền tường minh mới truy cập được.
💡 Hình dung: Docker secret giống 1 tủ ký gửi (safety deposit box) trong ngân hàng — nội dung luôn được khóa kỹ (mã hóa) khi nằm trong kho, chỉ mang ra đúng lúc cần và đúng người có chìa khóa (service được cấp quyền) mới mở được, và ai đó "mượn" xong dùng lại phải trả lại chỗ cũ (bị hủy khi container kết thúc) — không bao giờ để trần trong ngăn kéo bàn làm việc (biến môi trường dạng plain text) cho ai đi ngang qua cũng đọc được.
Luồng hoạt động tổng quát:
- Secret được tạo và post lên swarm.
- Lưu vào cluster store đã mã hóa (mọi manager đều truy cập được).
- Service cần dùng secret được tạo, gắn secret vào nó.
- Secret được mã hóa khi truyền tới container của service đó.
- Secret được mount vào container dưới dạng file chưa mã hóa tại
/run/secrets/— nằm trên 1 filesystem in-memory (tmpfs). - Khi container (task) kết thúc, filesystem in-memory này bị hủy theo.
- Service/container khác không được cấp quyền sẽ không truy cập được secret đó.
Tạo và quản lý secret qua subcommand docker secret, gắn secret vào service bằng cờ --secret khi docker service create:
$ echo "MyS3cretPassw0rd" | docker secret create db_password -
$ docker secret ls
# Output minh họa
ID NAME CREATED UPDATED
p3k9x1c7b2d5e8f0a4b6c8d0 db_password 3 seconds ago 3 seconds ago
$ docker service create --name db-app \
--secret db_password \
myteam/db-app:latest
(Output minh họa — nội dung secret không bao giờ hiện trong docker secret ls, chỉ có metadata; giá trị thật chỉ đọc được từ bên trong container được cấp quyền, tại đúng /run/secrets/db_password.)
Lời kết
Docker hoàn toàn có thể cấu hình để trở nên rất an toàn — hỗ trợ đầy đủ các công nghệ bảo mật lớn của Linux (kernel namespace, cgroup, capability, MAC, seccomp) với default hợp lý sẵn có nhưng vẫn tùy biến/tắt được khi cần. Ngoài ra, bản thân Docker platform còn có cả 1 bộ công nghệ bảo mật riêng: Swarm Mode xây trên nền TLS và cực kỳ đơn giản để cấu hình/tùy biến; Docker Scout quét image ở mức binary và báo cáo lỗ hổng đã biết; Content Trust cho phép ký và xác minh nội dung; và secret giờ đã là công dân hạng nhất trong hệ sinh thái Docker. Cuối cùng, môi trường Docker của bạn an toàn hay không hoàn toàn phụ thuộc vào cách bạn cấu hình nó.
Nguồn tham khảo
Nguồn gốc: Bài viết lấy cảm hứng từ sách "Docker Deep Dive" — tác giả Nigel Poulton (bản phát hành version 4, 3/10/2017), Part 2: The Technical Stuff — chapter 11 "Security in Docker" và chapter 12 "What next". Nội dung được viết lại theo cách hiểu riêng, không sao chép nguyên văn hay dịch sát nguyên bản. Muốn đọc bản đầy đủ hoặc ủng hộ tác giả, có thể mua sách tại leanpub.com/dockerdeepdive.
Đính chính/cập nhật (fact-check):
- "Docker Security Scanning" đã được thay thế bằng Docker Scout — tích hợp sẵn trong Docker Desktop/Hub/CLI (
docker scout), không còn giới hạn ở private repository hay riêng bản Enterprise như tính năng cũ — Docker — Docker Scout product page
Đã verify qua research (Mode R) — vẫn đúng với Docker hiện tại, không cần đính chính:
- Chu kỳ xoay vòng certificate mặc định của swarm vẫn là 90 ngày — Docker Docs — docker swarm ca