本文从《安全地远程调用 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/createexec、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:

  1. 使用独立服务账号和独立客户端证书;
  2. 证书从密钥挂载或短期凭据获取,不打进镜像;
  3. 连接池设置超时,不无限等待 daemon;
  4. API 请求记录调用者、目标资源和结果,但不记录私钥或完整环境变量;
  5. 操作使用资源 allowlist,拒绝任意镜像、挂载、命令和 host network;
  6. 将 Engine API 版本升级纳入兼容测试;
  7. 证书泄露时能够单独撤销,而不是更换全体客户端。

尤其不要让用户输入直接拼成镜像名、volume path、Cmd 或环境变量。对 Docker API 来说,这些字段不是普通字符串,而是宿主机权限的一部分。

上线前检查清单

  • 没有对网络开放未认证的 2375;
  • 管理员操作优先使用 SSH Docker context;
  • TCP 访问使用 mTLS、2376、VPN/防火墙和证书轮换;
  • 客户端证书按 root 级凭据保护;
  • 需要最小权限时使用授权插件或窄接口 broker;
  • 普通应用和不受信任 runner 没有挂载 docker.sock;
  • systemd 与 daemon.json 没有重复配置 hosts;
  • rootless 只作为纵深防御,不替代认证授权;
  • 所有操作有审计、超时和撤销方案。

参考资料