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 引擎,因为此类访问可能允许它们实施供应链攻击。例如,构建并将恶意镜像推送到组织的仓库或进行类似操作。

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

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

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

{
  "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 套接字。因此,以下操作可行:

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

提示

对您允许的镜像进行限制,如 建议中所述。

通常情况下,使用标签通配符格式指定镜像更为简便,例如 <image-name>:*,因为这样在使用新版本的镜像时,imageList 无需更新。或者,您也可以使用不可变标签,例如 :latest,但其效果并不总是像通配符那样好,因为例如 Testcontainers 使用的是镜像的特定版本,而不一定是最新版本。

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

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

$ 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 套接字的容器镜像列表。

这在大多数情况下都有效,但并非总是如此,因为它需要预先知道应该允许 Docker 套接字挂载的镜像名称。 某些容器工具,例如 Paketo buildpacks, 会构建需要 Docker 套接字绑定挂载的临时本地镜像。由于这些临时镜像的名称无法预先得知,因此 imageList 并不足够。

为了克服这个问题,从 Docker Desktop 版本 4.34 开始,Docker Socket 挂载权限不仅适用于 imageList 中列出的镜像;它们也适用于源自 imageList 中镜像(即基于该镜像构建)的任何本地镜像。

也就是说,如果名为 "myLocalImage" 的本地镜像是从 "myBaseImage" 构建的 (即,其 Dockerfile 中包含 FROM myBaseImage),那么如果 "myBaseImage" 在 imageList 中,则 "myBaseImage" 和 "myLocalImage" 都被允许挂载 Docker 套接字。

例如,要使 Paketo buildpacks 能够与 Docker Desktop 和 ECI 协同工作, 只需将以下镜像添加到 imageList 中:

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

当 buildpack 运行时,它将创建一个源自 paketobuildpacks/builder:base 的临时镜像,并将 Docker 套接字挂载到该镜像。ECI 将允许此操作,因为它会注意到该临时镜像源自一个被允许的镜像。

该行为默认处于禁用状态,必须通过设置 "allowDerivedImages": true 来显式启用,如上所示。通常建议您禁用此设置,除非您确定需要它。

一些注意事项:

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

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

  • 为了使衍生镜像检查生效,父镜像(即 imageList 中的镜像)必须存在于本地(即必须从仓库中显式拉取)。这通常不是问题,因为需要此功能的工具(例如 Paketo buildpacks)会预先拉取父镜像。

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

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

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

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

这告诉 Docker Desktop 允许所有容器挂载 Docker 套接字, 这增加了灵活性但降低了安全性。当使用增强型容器隔离时,它还可以改善容器启动时间。

建议仅在显式列出允许的容器镜像不够灵活的场景中使用此功能。

命令列表

除了前面章节中描述的 imageList 之外,ECI 还可以通过绑定挂载的 Docker 套接字进一步限制容器可以发出的命令。这是通过 Docker 套接字挂载权限 commandList 完成的,并且作为 imageList 的补充安全机制(即,就像第二道防线)。

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

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

默认情况下,这允许容器通过该 Docker 套接字发出任何命令(例如,构建镜像并将其推送到组织的仓库),这通常是不希望发生的。

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

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

命令通配符描述
"容器"指所有 “docker container ...” 命令
"镜像*"指所有 "docker image ..." 命令
"卷*"指所有“docker volume ...”命令
"network*"指所有“docker network ...”命令
"构建*"指所有 "docker build ..." 命令
"system*"指所有 “docker system ...” 命令

例如,以下配置在 Docker 套接字上阻止了 buildpush 命令:

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

因此,如果在容器内对绑定挂载的 Docker 套接字执行这些命令中的任何一个,它们将被阻止:

/ # 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 套接字(即 imageList)的容器镜像列表要严格限制。通常,仅对确实需要且您信任的镜像允许此操作。

  • 如果可能,请在 imageList 中使用标签通配符格式 (例如,<image_name>:*),因为这样无需因镜像标签变更而更新 admin-settings.json 文件。

  • commandList 中,阻止您不希望容器执行的命令。例如,对于本地测试(例如 Testcontainers),绑定挂载 Docker 套接字的容器通常会创建/运行/移除容器、卷和网络,但通常不会构建镜像或将其推送到仓库(尽管有些可能合法地这样做)。允许或阻止哪些命令取决于具体用例。

    • 请注意,容器通过绑定挂载的 Docker 套接字发出的所有“docker”命令也将在增强的容器隔离下执行(即,生成的容器使用 Linux 用户命名空间,敏感系统调用会受到审查等)

注意事项和限制

  • 当 Docker Desktop 重启时,被允许挂载 Docker 套接字的镜像可能会意外地被阻止执行此操作。当远程仓库中的镜像摘要发生变化(例如,":latest" 镜像已更新),且该镜像的本地副本(例如,来自先前的 docker pull)与远程仓库中的摘要不再匹配时,就会发生这种情况。在这种情况下,请删除本地镜像并重新拉取(例如,docker rm <image>docker pull <image>)。

  • 除非本地镜像(即不在注册表中的镜像)是派生自允许的镜像,或者您已允许所有容器挂载 Docker 套接字,否则无法在容器上绑定挂载 Docker 套接字。这是因为 Docker Desktop 会从注册表中拉取允许镜像的摘要,然后将其与本地镜像副本进行比较。

  • commandList 配置适用于所有被允许绑定挂载 Docker 套接字的容器。因此,无法针对每个容器进行不同的配置。

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

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

注意

当运行“true”时,Docker 套接字挂载权限不适用 Docker-in-Docker(即在容器内运行 Docker 引擎)。在 这种情况下,不存在将主机 Docker 套接字绑定挂载到 容器的情况,因此不存在容器利用配置 和主机 Docker 引擎的凭据执行恶意活动的风险。 增强型容器隔离能够安全地运行 Docker-in-Docker, 而无需在 Docker Desktop VM 中授予外部容器真正的 root 权限。