ECI的高级配置选项

Docker套接字挂载权限

默认情况下,启用增强型容器隔离 (ECI) 时,Docker Desktop不允许将Docker Engine套接字绑定挂载到容器中。

$ docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock docker:cli
docker: Error response from daemon: enhanced container isolation: docker socket mount denied for container with image "docker.io/library/docker"; image is not in the allowed list; if you wish to allow it, configure the docker socket image list in the Docker Desktop settings.

这可以防止恶意容器访问Docker Engine,因为此类访问可能允许它们执行供应链攻击。例如,将恶意镜像构建并推送到组织的代码库或类似位置。

但是,某些合法用例需要容器访问Docker Engine套接字。例如,流行的Testcontainers框架有时会将Docker Engine套接字绑定挂载到容器中以管理它们或执行测试后清理。同样,某些Buildpack框架,例如Paketo,需要将Docker套接字绑定挂载到容器中。

管理员可以选择配置ECI以允许以受控方式将Docker Engine套接字绑定挂载到容器中。

这可以通过admin-settings.json文件中的Docker套接字挂载权限部分来完成。例如

{
  "configurationFileVersion": 2,
  "enhancedContainerIsolation": {
    "locked": true,
    "value": true,
    "dockerSocketMount": {
      "imageList": {
        "images": [
          "docker.io/localstack/localstack:*",
          "docker.io/testcontainers/ryuk:*",
          "docker:cli"
        ],
        "allowDerivedImages": true
      },
      "commandList": {
        "type": "deny",
        "commands": ["push"]
      }
    }
  }
}

提示

您现在也可以在Docker管理员控制台中配置这些设置。

如上所示,有两种配置可以将Docker套接字绑定挂载到容器中:imageListcommandList。这些将在下面描述。

镜像列表

imageList是允许绑定挂载Docker套接字的容器镜像列表。默认情况下,列表为空,启用ECI时,不允许任何容器绑定挂载Docker套接字。但是,管理员可以使用以下两种格式之一将镜像添加到列表中

镜像引用格式描述
<image_name>[:<tag>]镜像名称,包含可选标签。如果省略标签,则使用:latest标签。如果标签是通配符*,则表示“该镜像的任何标签”。
<image_name>@<digest>镜像名称,包含特定的代码库摘要(例如,由docker buildx imagetools inspect <image>报告)。这意味着只有与该名称和摘要匹配的镜像才允许。

镜像名称遵循标准约定,因此它可以指向任何注册表和代码库。

在前面的示例中,镜像列表配置了三个镜像

"imageList": {
  "images": [
    "docker.io/localstack/localstack:*",
    "docker.io/testcontainers/ryuk:*",
    "docker:cli"
  ]
}

这意味着如果启用了增强型容器隔离 (ECI),则使用docker.io/localstack/localstackdocker.io/testcontainers/ryuk镜像(任何标签)或docker:cli镜像的容器允许绑定挂载 Docker socket。

$ docker run -it -v /var/run/docker.sock:/var/run/docker.sock docker:cli sh
/ #

提示

请严格限制允许的镜像,如建议中所述。

通常,使用标签通配符格式(例如<image-name>:*)指定镜像更简单,因为这样在使用新版本的镜像时就不需要更新imageList。或者,您可以使用不可变标签,例如:latest,但这并不总是像通配符那样有效,例如,Testcontainers 使用的是特定版本的镜像,不一定是最新版本。

启用 ECI 后,Docker Desktop 会定期从相应的注册表下载允许镜像的镜像摘要并将其存储在内存中。然后,当使用 Docker socket 绑定挂载启动容器时,Docker Desktop 会检查容器的镜像摘要是否与允许的摘要之一匹配。如果匹配,则允许容器启动;否则,将被阻止。

由于进行了摘要比较,因此无法通过将不允许的镜像重新标记为允许镜像的名称来绕过 Docker socket 挂载权限。换句话说,如果用户执行以下操作:

$ docker image rm <allowed_image>
$ docker tag <disallowed_image> <allowed_image>
$ docker run -v /var/run/docker.sock:/var/run/docker.sock <allowed_image>

则标记操作成功,但docker run命令失败,因为不允许的镜像的镜像摘要与存储库中允许的镜像的镜像摘要不匹配。

派生镜像的Docker套接字挂载权限

Docker Desktop 版本 4.34.0 中引入

如上一节所述,管理员可以通过imageList配置允许挂载 Docker socket 的容器镜像列表。

这适用于大多数场景,但并非总是如此,因为它需要预先知道应该允许 Docker socket 挂载的镜像名称。某些容器工具(例如 Paketo构建包)构建需要 Docker socket 绑定挂载的临时本地镜像。由于这些临时镜像的名称事先未知,因此imageList不足以满足需求。

为了克服这个问题,从 Docker Desktop 4.34 版本开始,Docker Socket 挂载权限不仅适用于imageList中列出的镜像,还适用于从imageList中的镜像派生的(即,从其构建的)任何本地镜像。

也就是说,如果名为“myLocalImage”的本地镜像是从“myBaseImage”构建的(即,Dockerfile 中包含FROM myBaseImage),并且“myBaseImage”在imageList中,则“myBaseImage”和“myLocalImage”都允许挂载 Docker socket。

例如,要使 Paketo 构建包能够与 Docker Desktop 和 ECI 一起使用,只需将以下镜像添加到imageList

"imageList": {
  "images": [
    "paketobuildpacks/builder:base"
  ],
  "allowDerivedImages": true
}

构建包运行时,它将创建一个从paketobuildpacks/builder:base派生的临时镜像,并将其挂载到 Docker socket。ECI 将允许此操作,因为它会注意到临时镜像是从允许的镜像派生的。

此行为默认情况下处于禁用状态,必须通过设置"allowDerivedImages": true来显式启用,如上所示。通常建议除非您知道需要此设置,否则应禁用它。

一些注意事项:

  • 设置"allowedDerivedImages" :true 会使容器的启动时间最多增加 1 秒,因为 Docker Desktop 需要对容器镜像执行更多检查。

  • allowDerivedImages设置仅适用于从允许的镜像构建的仅本地镜像。也就是说,派生镜像不能存在于远程存储库中,因为如果存在,您只需将其名称列在imageList中。

  • 为了使派生镜像检查工作,父镜像(即imageList中的镜像)必须存在于本地(即必须已从存储库显式拉取)。这通常不是问题,因为需要此功能的工具(例如 Paketo 构建包)将预先拉取父镜像。

  • 仅适用于 Docker Desktop 4.34 和 4.35 版本:allowDerivedImages设置适用于使用显式标签指定的imageList中的所有镜像(例如,<name>:<tag>)。它不适用于使用上一节中描述的标签通配符(例如,<name>:*)指定的镜像。在 Docker Desktop 4.36 及更高版本中,此警告不再适用,这意味着allowDerivedImages设置适用于使用或不使用通配符标签指定的镜像。这使得管理 ECI Docker socket 镜像列表更加容易。

允许所有容器挂载Docker套接字

在 Docker Desktop 4.36 及更高版本中,可以配置镜像列表以允许任何容器挂载 Docker socket。您可以通过将"*"添加到imageList来实现此目的。

"imageList": {
  "images": [
    "*"
  ]
}

这告诉 Docker Desktop 允许所有容器挂载 Docker socket,这增加了灵活性,但也降低了安全性。在使用增强型容器隔离时,它还可以缩短容器启动时间。

建议您仅在显式列出允许的容器镜像不够灵活的情况下使用此方法。

命令列表

除了上一节中描述的imageList之外,ECI 可以进一步限制容器可以通过绑定挂载的 Docker socket 发出的命令。这是通过 Docker socket 挂载权限commandList完成的,它作为imageList的补充安全机制(即作为第二道防线)。

例如,假设imageList配置为允许镜像docker:cli挂载 Docker socket,并且使用它启动了一个容器:

$ docker run -it --rm -v /var/run/docker.sock:/var/run/docker.sock sh
/ #

默认情况下,这允许容器通过该 Docker socket 发出任何命令(例如,构建和推送镜像到组织的存储库),这通常是不希望的。

为了提高安全性,可以配置commandList以限制容器内的进程在绑定挂载的 Docker socket 上发出的命令。您可以根据需要将commandList配置为“拒绝”列表(默认)或“允许”列表。

列表中的每个命令都由其名称指定,由docker --help报告(例如,“ps”、“build”、“pull”、“push”等)。此外,允许使用以下命令通配符来阻止整个命令组:

命令通配符描述
"container*"指所有“docker container ...”命令
"image*"指所有“docker image ...”命令
"volume*"指所有“docker volume ...”命令
"network*"指所有“docker network ...”命令
"build*"指所有“docker build ...”命令
"system*"指所有“docker system ...”命令

例如,以下配置阻止了 Docker socket 上的buildpush命令:

"commandList": {
  "type": "deny",
  "commands": ["build", "push"]
}

因此,如果在容器内部,您在绑定挂载的 Docker socket 上发出这两个命令中的任何一个,它们都将被阻止。

/ # docker push myimage
Error response from daemon: enhanced container isolation: docker command "/v1.43/images/myimage/push?tag=latest" is blocked; if you wish to allow it, configure the docker socket command list in the Docker Desktop settings or admin-settings.

同样地:

/ # curl --unix-socket /var/run/docker.sock -XPOST http://localhost/v1.43/images/myimage/push?tag=latest
Error response from daemon: enhanced container isolation: docker command "/v1.43/images/myimage/push?tag=latest" is blocked; if you wish to allow it, configure the docker socket command list in the Docker Desktop settings or admin-settings.

请注意,如果commandList已配置为“允许”列表,则效果相反:只有列出的命令才被允许。是否将列表配置为允许列表或拒绝列表取决于用例。

建议

  • 请严格限制允许绑定挂载 Docker socket 的容器镜像列表(即imageList)。通常,只允许绝对需要且您信任的镜像这样做。

  • 如果可能,请在imageList中使用标签通配符格式(例如,<image_name>:*),因为这消除了由于镜像标签更改而需要更新admin-settings.json文件的必要。

  • commandList中,阻止您不希望容器执行的命令。例如,对于本地测试(例如 Testcontainers),绑定挂载 Docker socket 的容器通常会创建/运行/删除容器、卷和网络,但通常不会构建镜像或将其推送到存储库(尽管某些镜像可能会合法地执行此操作)。允许或阻止哪些命令取决于用例。

    • 请注意,容器通过绑定挂载的 Docker socket 发出的所有“docker”命令也将运行在增强型容器隔离下(即,生成的容器使用 Linux 用户命名空间,敏感系统调用经过验证等)。

警告和限制

  • 重新启动 Docker Desktop 后,允许挂载 Docker socket 的镜像可能意外地被阻止。当远程存储库中的镜像摘要发生更改(例如,更新了“:latest”镜像)并且该镜像的本地副本(例如,来自之前的docker pull)与远程存储库中的摘要不再匹配时,就会发生这种情况。在这种情况下,请删除本地镜像并再次拉取它(例如,docker rm <image>docker pull <image>)。

  • 除非它们来自允许的镜像或您已允许所有容器挂载 Docker socket,否则无法允许在使用仅本地镜像的容器上进行 Docker socket 绑定挂载(即,不在注册表上的镜像)。这是因为 Docker Desktop 从注册表中拉取允许镜像的摘要,然后使用它与镜像的本地副本进行比较。

  • commandList配置适用于所有允许绑定挂载 Docker socket 的容器。因此,它不能针对每个容器进行不同的配置。

  • commandList中尚不支持以下命令:

不支持的命令描述
composeDocker Compose
dev开发环境
extension管理 Docker 扩展
feedback向 Docker 发送反馈
init创建 Docker 相关的启动文件
manifest管理 Docker 镜像清单
plugin管理插件
sbom查看软件物料清单 (SBOM)
scoutDocker Scout
trust管理 Docker 镜像的信任

注意

运行“真正的”Docker-in-Docker(即在容器内运行 Docker Engine)时,Docker socket 挂载权限不适用。在这种情况下,主机 Docker socket 没有绑定挂载到容器中,因此容器不会利用主机 Docker Engine 的配置和凭据执行恶意活动。增强型容器隔离能够安全地运行 Docker-in-Docker,而无需在 Docker Desktop 虚拟机中向外部容器授予真正的 root 权限。