13进阶:容器安全

第 13 关 容器安全
13.1 容器不是沙箱
第 0 关说过一句要展开的话:容器不是安全沙箱。它共享宿主机内核,namespace 管「看得见什么」、cgroups 管「用多少」——这些是行为边界,不是安全边界。内核一个漏洞,隔离就可能被绕过。
所以容器安全不靠「容器」本身,靠你在外面加的那几道限制。这一关就是那几道。
13.2 密码别写进环境变量
第 2 关起 mysql 时,root 密码是这么给的:
docker run -d --name db -e MYSQL_ROOT_PASSWORD=hecloud mysql当时说「正经的密码管理留到第 13 关」。现在给完整解释。
为什么 -e 不行:那个密码会在三个地方明文躺着——
- 终端历史;
docker inspect <容器>的Config.Env(第 12 关)——它会一直跟着这条容器;- 容器里所有进程的环境变量,
docker exec db env就能看见。
而且环境变量是在容器创建那一刻抄进配置的:想改,只能删掉重建。
换成挂一个文件。 mysql 支持让密码从文件里读:
echo 'hecloud' > ./db_passwordchmod 600 ./db_passworddocker run -d --name db \ -e MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db_password \ -v ./db_password:/run/secrets/db_password:ro \ mysql-e 里现在只有路径,没有密码——inspect、env、终端历史里都看不到它。
更一般的界线:
| 放环境变量 | 挂文件 | |
|---|---|---|
| 适合 | 不敏感、启动时读一次的配置(端口、模式、日志级别) | 密码、证书、密钥 |
| 改的代价 | 重建容器 | 改文件内容即可,挂载没变 |
会不会被 inspect 看见 | 会 | 不会 |
像第 2 关那个 nginx.conf 这样的配置文件,改了只要重启,容器不用重建——因为挂载没变,变的只是文件里的字。
mysql 的 root 密码是个特例:它只在第一次初始化时读,之后就不看了。所以改它还是得改数据库或重建;换成文件解决的是泄露面,不是「随时可改」。
compose 里有专门的 secrets:(第 18 关的字段表里),效果一样:以文件形式挂在 /run/secrets/<名字>,不进 Env。
13.3 别用 root 跑
镜像不写 USER,容器里默认就是 root。容器里的 root 不等于宿主机 root(有 namespace 隔开),但一旦逃逸、或者挂了宿主机目录,破坏力会放大。
两个地方改:
RUN useradd -r appUSER appdocker run --user 1000:1000 <镜像>易踩的坑:换了用户之后,写挂载目录的权限可能不对(容器里的 uid 和宿主机上的对不上)——第 4 关的绑定挂载会立刻现形,表现为「权限被拒」。
13.4 收走用不着的权力
Linux 把 root 的权力拆成了几十份,叫 capabilities(NET_ADMIN、SYS_ADMIN、CHOWN……)。Docker 默认只给其中一部分,不是全部——但还能再收回一部分:
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx只留真正需要的。反过来最该避免的是 --privileged:它把能给的都给了,等于亲手把笼子拆了。
禁止提权:
docker run --security-opt no-new-privileges nginx它挡住容器里的进程通过 setuid 程序升权。
系统调用与 seccomp
程序想用内核的能力——读文件、开网络、创建进程——都得经过一组固定的入口向内核「打申请」。这些入口叫系统调用(syscall)。
seccomp 是 Linux 给进程装的一张白名单:名单上有的才放行,不在名单上的直接拒绝(通常连进程一起杀掉)。
Docker 默认就给每个容器装了一张,挡掉一批容器用不着、却很适合搞破坏的调用——加载内核模块、重启系统、改系统时间之类。所以你什么都不做,这层已经在挡了。
一般不用动。真要收得更窄,才需要自己写一份:
docker run --security-opt seccomp=<配置文件> <镜像>看它有没有生效:docker info 的 Security Options 里有一行 seccomp,下面写着 Profile: builtin——那就是默认白名单。
它和 capabilities 的分界:capabilities 管「你手里有哪几把钥匙」,seccomp 管「允许你敲哪几扇门」。有钥匙,门也可能被 seccomp 封着。
13.5 少开一扇门
第 5 关讲过:一个容器可以挂多张网。安全上就是三条。
- 不该露的端口,不写
-p。数据库没有理由让宿主机访问。 - 要露的,绑到本机回环:
docker run -p 127.0.0.1:8080:80 nginx不写 127.0.0.1 时,端口绑在 0.0.0.0 上——同一网段的人都能连。
- 给数据库一张内部网:
docker network create --internal db-net--internal 建的网没有出口,外面也进不来,只有接在上面的容器互相可见。
13.6 一份最小清单
- 密码、证书、密钥 → 挂文件,别放
-e; - 能不用 root 就不用(
USER/--user); --cap-drop ALL再按需--cap-add,别用--privileged;--security-opt no-new-privileges;- 不发布的端口不写
-p,发布时绑127.0.0.1; - 数据库放
--internal网。
一句话:容器给的隔离是底线,不是保险。 上面每一条,都是在笼子上再加一道锁。
下一关处理另一类边界——不是「谁能不能进来」,而是「它能用掉多少 CPU 和内存」。
本关命令速查
安全相关的运行参数,见 命令速查表 · 安全。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!





