Debian服务器跑容器,安全加固的核心不是装个防火墙就完事,而是要把“容器逃逸”和“内核参数滥用”这两条路先堵死。多数人把精力放在镜像漏洞扫描上,却忽略了宿主机的默认配置本身就不适合承载多租户容器。下面直接讲具体要改什么、怎么改、为什么这么改。
先锁定容器运行的内核参数
Debian 12默认内核参数对容器并不友好。容器里如果允许修改网络参数、挂载文件系统、访问宿主机IPC资源,等于把半个root权限交出去了。最有效的做法是在宿主机上通过sysctl强制覆盖,而不是指望容器启动命令里逐个禁用。
编辑/etc/sysctl.d/99-container-hardening.conf,写入以下内容:
# 禁止容器内修改内核网络参数 net.ipv4.ip_forward = 0 net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.default.send_redirects = 0 # 限制容器访问宿主机IPC资源 kernel.msgmax = 65536 kernel.msgmnb = 65536 kernel.sem = 250 32000 100 128 # 防止容器通过ptrace干扰宿主机进程 kernel.yama.ptrace_scope = 2 # 关闭未使用的网络协议,减少攻击面 net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1
执行sysctl --system立即生效。这里把ip_forward设为0,是因为宿主机如果不是专门做路由或NAT网关,容器网络走桥接或macvlan根本不需要开启转发。很多教程让你开转发方便容器互通,但生产环境里容器间通信应该显式配置网络策略,而不是靠宿主机的全局转发兜底。
用户命名空间必须强制启用
Debian默认的Docker或containerd配置里,容器root用户映射到宿主机上依然是root,只是受限于capabilities。一旦某个capability被错误授予,或者内核出现漏洞,容器就能直接操作宿主机文件系统。启用用户命名空间后,容器内的root在宿主机上只是一个普通UID,即使逃逸出来也拿不到宿主机root权限。
对于Docker,编辑/etc/docker/daemon.json:
{
"userns-remap": "default",
"no-new-privileges": true,
"live-restore": false
}
重启Docker服务后,所有容器会自动运行在独立的用户命名空间里。注意userns-remap会改变容器内文件在宿主机上的属主,如果你之前有持久化卷,需要先迁移权限。containerd用户则需要在/etc/containerd/config.toml中启用enable_keychain和user namespace相关配置,具体取决于你用的是runc还是kata。
no-new-privileges这个参数经常被忽略。它确保容器启动后,即使内部进程尝试执行带setuid位的程序,也无法获得额外权限。很多提权攻击依赖的就是setuid二进制文件,这个开关直接封死这条路。
Seccomp和AppArmor要叠加使用
Debian自带的Docker默认会启用一个seccomp配置文件,但这个默认配置允许的系统调用还是偏多。建议自己维护一份更严格的seccomp profile,重点拦截mount、umount、reboot、kexec_load、bpf等高风险调用。容器内跑常规Web服务、数据库、消息队列,根本不需要这些调用。
AppArmor方面,Debian默认对容器只加载了docker-default策略,这个策略比较宽松。你可以为不同业务容器编写独立的AppArmor profile,限制文件访问路径、网络socket类型、capabilities范围。比如一个纯计算型容器,直接禁止它监听端口和访问/etc/passwd以外的系统文件。
在容器启动命令中同时指定:
docker run --security-opt seccomp=/path/to/custom-seccomp.json \
--security-opt apparmor=my-container-profile \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
your-image
cap-drop=ALL是基础,不要用默认的capabilities集合。默认集合里有CAP_SYS_ADMIN、CAP_SYS_PTRACE这类危险权限,绝大多数业务容器用不上。只加回必要的capability,比如绑定低端口需要NET_BIND_SERVICE,调整系统时间需要SYS_TIME,其余一律不给。
文件系统只读和挂载限制
容器根文件系统应该默认只读。业务需要写日志或临时文件时,单独挂载tmpfs或指定的持久化卷。这样即使容器被攻破,攻击者也无法修改容器内的二进制文件或植入后门。
docker run --read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=256m \
--tmpfs /var/log:rw,noexec,nosuid,size=128m \
--mount type=bind,source=/data/app,target=/app/data \
your-image
注意tmpfs挂载时加上noexec和nosuid,防止攻击者在内存文件系统里放置并执行恶意程序。持久化卷的宿主机目录权限要单独收紧,不要让容器以root身份写宿主机目录,结合前面提到的用户命名空间,容器内的root在宿主机上只是普通用户,这样即使写入恶意文件也无法设置setuid位或修改系统关键路径。
容器网络隔离与防火墙策略
Debian宿主机上不要用默认的docker0网桥直接暴露容器端口。每个业务组使用独立的bridge网络,或者直接用macvlan让容器获得独立内网IP。容器之间的通信默认是互通的,除非你手动配置iptables规则或使用网络策略插件。
在宿主机上用iptables限制容器网段访问宿主机管理端口:
iptables -A INPUT -s 172.17.0.0/16 -d 10.0.0.10 -p tcp --dport 22 -j DROP iptables -A INPUT -s 172.17.0.0/16 -d 10.0.0.10 -p tcp --dport 2375 -j DROP iptables -A INPUT -s 172.17.0.0/16 -d 10.0.0.10 -p tcp --dport 2376 -j DROP
这里假设宿主机管理IP是10.0.0.10,容器网段是172.17.0.0/16。SSH端口和Docker API端口必须对容器网段关闭,否则容器内一旦拿到宿主机内网访问能力,就能直接尝试登录宿主机或操作Docker守护进程。
如果容器需要对外提供服务,不要在宿主机上直接做端口映射。更好的做法是前置一层反向代理或负载均衡,容器只监听内网地址,由代理统一处理TLS终止和访问控制。这样容器本身不直接暴露在公网,攻击面大幅缩小。
镜像供应链和运行时监控
基础镜像不要用latest标签,固定到具体版本号。每次构建时用debsecan或trivy扫描Debian基础镜像的已知漏洞,发现高危漏洞立即重建。容器运行后,用falco或sysdig监控异常系统调用、文件访问和网络行为。特别是容器内出现mount操作、访问/etc/shadow、尝试加载内核模块等行为,要立即告警并隔离容器。
Debian的unattended-upgrades在宿主机上要开启,但容器内的软件更新不能依赖宿主机机制。容器应该采用不可变基础设施思路,更新软件就是重新构建镜像,而不是在运行中的容器里apt upgrade。运行中的容器一旦被修改,就失去了可重复性和审计能力。
审计和日志落盘
容器日志不要只输出到stdout然后依赖Docker的json-file驱动。生产环境应该把日志通过syslog或journald转发到集中日志系统,同时宿主机上保留一份本地落盘。Docker的json-file驱动默认不限制日志文件大小,容器产生大量日志时可能撑爆宿主机磁盘。
在daemon.json中配置日志轮转:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
同时开启auditd对宿主机关键路径的监控,特别是/var/lib/docker、/etc/docker、/run/containerd这些目录。任何对容器配置、镜像存储、运行时状态的修改都要记录来源进程和用户。
Debian服务器承载容器时,安全加固不是一次性任务。内核参数、用户命名空间、seccomp/AppArmor、文件系统只读、网络隔离、镜像扫描、运行时监控,这七层缺一不可。每多一层限制,攻击者从容器逃逸到宿主机的难度就上升一个量级。实际部署时按照这个顺序逐项落地,比装一堆花哨的安全产品更有效。
