Ubuntu服务器上的Docker环境默认配置离生产级安全还有明显距离。最常见的问题不是内核漏洞,而是权限配置过宽、守护进程暴露、镜像来源不受控和容器运行时缺乏约束。加固的核心思路是:收紧Docker守护进程的访问面、限制容器对宿主机的权限、控制镜像供应链、以及让容器运行在最小能力集下。下面直接讲具体做法。

1. 限制Docker守护进程的访问面

默认情况下,Docker守护进程通过Unix Socket(/var/run/docker.sock)通信,这本身是相对安全的,因为只有root用户和docker组成员能访问。但很多人为了远程管理方便,会开启TCP端口2375或2376,这是非常危险的操作。如果必须远程访问,务必使用TLS加密的2376端口,并配置客户端证书认证。修改/lib/systemd/system/docker.service中的ExecStart行,加入-H参数绑定到受控地址,同时设置--tlsverify和--tlscacert、--tlscert、--tlskey指向你的证书文件。重启后验证:没有证书的客户端应该无法连接。另一个细节是:不要把普通用户随意加入docker组。docker组成员等效于拥有root权限,因为可以通过挂载宿主机根目录逃逸。如果开发人员需要构建镜像,建议使用独立的CI环境或Podman等无守护进程方案。

2. 启用User Namespace隔离

Docker默认的容器root用户与宿主机root用户是同一个UID 0。一旦容器内进程突破限制,宿主机可能被完全控制。启用user namespace后,容器内的root会被映射到宿主机上的一个非特权用户(通常从100000开始),即使发生逃逸,攻击者拿到的也只是高UID的普通权限。在/etc/docker/daemon.json中添加:

{
  "userns-remap": "default"
}

这会自动创建一个dockremap用户和命名空间映射。启用后,容器内看到的文件属主会显示为映射后的UID,但这不影响应用运行。需要注意的是,启用userns后,某些依赖特定UID的功能(如NFS挂载、部分卷权限)需要额外调整。对于新部署的环境,强烈建议一开始就开启,因为后续再开启会导致已有容器和镜像的权限错乱。

3. 强制镜像来源和内容信任

不要直接使用docker pull从公共仓库拉取未经验证的镜像。在企业内部,应该搭建私有镜像仓库(如Harbor),并配置Docker守护进程只允许从该仓库拉取。同时开启Docker Content Trust(DCT),对镜像进行签名验证。在客户端设置环境变量DOCKER_CONTENT_TRUST=1,这样只有经过签名的镜像才能被拉取和运行。对于基础镜像,定期扫描漏洞是必须的。可以用Trivy或Clair对镜像做静态扫描,重点检查高危CVE和恶意软件。扫描结果要集成到CI流程中,阻断含高危漏洞的镜像进入生产。另外,镜像标签不要使用latest,固定到具体版本或摘要(digest),防止上游镜像被篡改后影响你的部署。

4. 容器运行时安全配置

运行容器时,不要使用--privileged标志。这个标志会赋予容器几乎所有宿主机的内核能力,等于把root权限直接交给容器。如果某个容器确实需要特定能力,使用--cap-add精确添加,例如--cap-add=NET_ADMIN只给网络管理权限,而不是全给。同时,用--cap-drop=ALL先移除所有默认能力,再按需添加。对于大多数Web应用、API服务,只需要NET_BIND_SERVICE和CHOWN等少数能力。另一个常被忽略的是--security-opt。可以设置--security-opt=no-new-privileges,禁止容器内进程通过setuid等方式提升权限。还可以配合AppArmor或Seccomp配置文件进一步限制系统调用。Ubuntu默认带有AppArmor,Docker会自动应用docker-default配置文件,但你可以编写更严格的策略。例如,限制容器只能使用白名单内的系统调用,阻断ptrace、mount等危险调用。

5. 文件系统与存储限制

容器文件系统默认是可写的,攻击者可以在容器内写入恶意文件或消耗磁盘空间。使用--read-only标志将容器根文件系统挂载为只读,对于需要写入的目录(如/tmp、日志目录),通过tmpfs或挂载独立卷来提供。这样即使容器被攻破,攻击者也难以持久化。同时,设置存储配额,防止单个容器写满宿主机磁盘。在daemon.json中可以配置storage-driver的选项,例如使用overlay2时设置size限制。对于日志,一定要配置日志轮转,否则容器日志会无限增长。在daemon.json中添加:

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

这样每个容器的日志最多保留3个10MB文件,避免磁盘被日志占满。

6. 网络隔离与防火墙策略

Docker默认创建的bridge网络允许容器之间互相通信,这在多租户环境下是风险点。可以为每个应用创建独立的用户自定义网络,不同网络之间默认隔离。使用docker network create创建网络时,加上--internal参数可以禁止该网络访问外部,只允许容器间通信。对于需要暴露到宿主机的端口,不要使用-P随机映射,明确指定-p 127.0.0.1:8080:80这样的绑定,只监听本地回环地址,避免直接暴露到公网。如果容器需要访问外部服务,通过宿主机防火墙(UFW或iptables)控制出站流量。Ubuntu的UFW默认不管理Docker的iptables规则,需要手动添加规则。一个实用做法是:在宿主机上启用UFW,只允许必要的入站端口,Docker容器的出站流量默认放行,但通过iptables限制容器网段的访问范围。

7. 资源限制防止DoS

没有资源限制的容器可以耗尽宿主机的CPU和内存,影响其他服务。运行容器时,使用--cpus和--memory参数设置硬限制。例如--cpus=1.5限制最多使用1.5个CPU核心,--memory=512m限制内存上限。同时设置--memory-swap,如果不希望容器使用交换分区,将其设为与--memory相同值。对于有状态服务,还要设置--pids-limit限制进程数,防止fork炸弹。在daemon.json中可以设置默认的资源限制,但更推荐在docker run或compose文件中显式声明。Kubernetes环境下,这些限制对应Pod的resources字段,原理相同。

8. 守护进程自身的加固

Docker守护进程以root运行,其配置文件/etc/docker/daemon.json的权限应设为600,仅root可读写。定期检查/var/lib/docker目录的权限,确保没有意外暴露。Docker的API版本更新频繁,保持Docker Engine更新到最新稳定版,及时修复已知漏洞。Ubuntu的默认仓库中Docker版本可能较旧,建议使用Docker官方仓库安装,以便获得及时的安全更新。同时,监控Docker守护进程的日志,/var/log/syslog中会记录API调用和错误信息。对于生产环境,可以部署Auditd审计规则,监控/usr/bin/docker和/var/lib/docker的访问行为,发现异常操作及时告警。

9. 镜像构建阶段的安全实践

安全加固不只是运行时的事,构建阶段同样重要。使用多阶段构建减小镜像体积,减少攻击面。基础镜像选择distroless或Alpine等精简发行版,避免携带不必要的工具(如curl、wget、shell)。如果应用需要调试,可以临时使用带工具的镜像,生产环境切换到精简版。在Dockerfile中,不要以root用户运行应用。创建专用用户并设置USER指令:

RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

这样即使容器被攻破,攻击者拿到的也是非root权限。另外,在Dockerfile中避免使用ADD指令,用COPY代替,因为ADD会自动解压和下载远程文件,存在安全隐患。构建时传入的敏感信息(如API密钥)不要写进镜像层,使用BuildKit的--secret功能在构建时挂载秘密,构建完成后不留在镜像中。

10. 定期审计与自动化检查

安全加固不是一次性工作。使用Docker Bench for Security这个官方开源工具,定期对宿主机和容器配置进行合规检查。它会根据CIS Docker Benchmark的标准,检查守护进程配置、容器运行时选项、镜像安全等方面,输出详细报告。将检查集成到CI/CD或定时任务中,发现不合规项立即修复。对于运行中的容器,使用docker inspect查看实际生效的配置,确认没有遗漏--privileged或过宽的cap-add。结合Prometheus等监控工具,跟踪容器的资源使用和异常行为,例如突然的CPU飙升、异常的网络连接、频繁的文件系统写入等,这些可能是入侵的迹象。

总结一下,Ubuntu服务器Docker环境的安全加固需要从守护进程、容器运行时、镜像供应链、网络和存储多个层面同时收紧。核心原则是:最小权限、默认拒绝、持续审计。不要因为怕麻烦而使用--privileged或关闭安全选项,这些便利最终会变成攻击者的突破口。把上述措施落实到部署流程中,Docker环境的安全性会有质的提升。