本文从《安全地远程调用 Docker API》重新整理。最大的调整是删除“先开放无鉴权端口快速体验”的路径:Docker API 是宿主机控制面,即使只是短暂暴露,风险也远高于普通业务接口。
能够访问 Docker daemon 的调用方通常可以:
- 启动特权容器;
- 挂载宿主机根目录;
- 读取其他容器环境变量和挂载卷;
- 注入网络、执行命令和导出镜像;
- 取得宿主机级控制能力。
因此 Docker socket 权限应近似看作 root 权限。把 tcp://0.0.0.0:2375 暴露到网络,不是“缺少一个登录页”,而是把服务器控制面交给能连接该端口的人。
先判断是否真的需要远程 Docker API
不同需求对应不同方案:
| 需求 | 更合适的入口 |
|---|---|
| 管理员偶尔执行 docker 命令 | SSH Docker context |
| CI 发布固定服务 | 部署平台、受限 job 或 GitOps |
| 应用只需重启一个服务 | 自建窄接口的本地 agent |
| 跨主机编排容器 | Kubernetes、Swarm 或其他编排 API |
| 完整远程 Engine API | 仅在确有需要时使用 mTLS + 网络隔离 |
如果业务只需要“重启 order-service”,就不要给它创建、挂载和执行任意容器的权限。建立一个只接受服务白名单和固定动作的 broker,通常比直接暴露 Engine API 更容易审计。
首选:通过 SSH 使用 Docker context
Docker CLI 可以通过 SSH 把请求转发到远端 Unix socket,不需要额外开放 Docker TCP 端口:
docker context create remote-prod \
--docker host=ssh://docker-operator@host1.example.com \
--description="Production Docker host"
docker --context remote-prod ps
远端用户仍必须有权访问 Docker socket,因此要保护该 SSH 账号:
- 使用独立账号和密钥;
- 禁止密码登录;
- 限制来源网络;
- 审计 SSH 登录;
- 不与普通应用账号共用;
- 定期轮换密钥。
SSH context 解决了传输和主机认证,但没有改变 Docker 默认的全权限模型。能访问 socket 的账号仍应按 root 级别管理。
必须使用 TCP 时启用双向 TLS
Docker 官方的安全 TCP 端口是 2376。daemon 开启 tlsverify 后:
- 客户端验证服务端证书;
- 服务端只接受受信 CA 签发的客户端证书;
- 通信经过 TLS 加密。
daemon 配置示意:
{
"hosts": [
"unix:///var/run/docker.sock",
"tcp://0.0.0.0:2376"
],
"tls": true,
"tlsverify": true,
"tlscacert": "/etc/docker/pki/ca.pem",
"tlscert": "/etc/docker/pki/server-cert.pem",
"tlskey": "/etc/docker/pki/server-key.pem"
}
不同发行版的 systemd unit 可能已经通过 ExecStart 指定 -H。不要同时在 systemd 参数和 daemon.json 重复配置 hosts,否则 Docker 可能因冲突无法启动。修改前先保存回滚入口,并在控制台或第二条 SSH 会话中验证。
网络层还应做到:
- 只允许管理网或 VPN 来源访问 2376;
- 不对公网开放;
- 服务端证书 SAN 与实际 DNS/IP 一致;
- CA 私钥离线或存入受控密钥系统;
- 客户端私钥使用严格文件权限;
- 证书有负责人、到期监控和撤销流程。
客户端证书本质上是 root 级凭据。复制一份证书给某个应用,等价于把 Docker daemon 的控制权交给它。
mTLS 解决身份,不自动解决最小权限
Docker 默认授权模型接近 all-or-nothing:只要调用方通过 socket 或客户端证书进入,就可以调用几乎所有 Engine API。
需要按用户、路径或资源限制时,可以考虑:
- Docker authorization plugin;
- 只暴露白名单 API 的受限代理;
- 本地 agent 将业务动作映射为固定 Docker 操作;
- 用编排平台的 RBAC 代替直接调用 daemon。
受限代理不能只按 URL 字符串过滤。containers/create、exec、build、挂载和镜像操作的请求体都可能扩大权限,必须基于完整语义设计 allowlist,并对 Docker API 版本升级做回归测试。
不要随手把 docker.sock 挂进容器
下面的配置让容器内进程直接控制宿主机 Docker:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
即使容器本身没有 privileged: true,它也可以通过 daemon 创建一个挂载宿主机目录的特权容器,从而绕过原有隔离。
使用 auto-update、可视化面板、CI runner 或监控工具前应检查:
- 是否真的需要写权限;
- 能否改为只读信息或事件订阅;
- 项目是否维护、镜像是否固定 digest;
- Web UI 是否有强认证和 CSRF 防护;
- 它能否访问外网和其他敏感网络;
- 被攻破时影响是否限定在独立主机。
Rootless 可以降低宿主机影响,但不是授权系统
Rootless mode 让 daemon 和容器运行在非 root 用户的 user namespace 中,可以缓解 daemon 或运行时漏洞的宿主机影响。
它不意味着可以放心公开 API:调用方仍能控制该用户拥有的全部容器、镜像、卷和网络,也可能读取业务秘密。Rootless 是纵深防御,不是认证、授权或网络隔离的替代品。
应用接入时的安全边界
如果 Java 或其他服务确实需要调用 Engine API:
- 使用独立服务账号和独立客户端证书;
- 证书从密钥挂载或短期凭据获取,不打进镜像;
- 连接池设置超时,不无限等待 daemon;
- API 请求记录调用者、目标资源和结果,但不记录私钥或完整环境变量;
- 操作使用资源 allowlist,拒绝任意镜像、挂载、命令和 host network;
- 将 Engine API 版本升级纳入兼容测试;
- 证书泄露时能够单独撤销,而不是更换全体客户端。
尤其不要让用户输入直接拼成镜像名、volume path、Cmd 或环境变量。对 Docker API 来说,这些字段不是普通字符串,而是宿主机权限的一部分。
上线前检查清单
- 没有对网络开放未认证的 2375;
- 管理员操作优先使用 SSH Docker context;
- TCP 访问使用 mTLS、2376、VPN/防火墙和证书轮换;
- 客户端证书按 root 级凭据保护;
- 需要最小权限时使用授权插件或窄接口 broker;
- 普通应用和不受信任 runner 没有挂载 docker.sock;
- systemd 与 daemon.json 没有重复配置 hosts;
- rootless 只作为纵深防御,不替代认证授权;
- 所有操作有审计、超时和撤销方案。