ECI 的高级配置选项

订阅: Business
适用于: 管理员

Docker socket 挂载权限

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

$ 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 socket。例如,流行的 Testcontainers 框架有时会将 Docker Engine socket 绑定挂载到容器中,以便管理它们或执行测试后清理。类似地,某些 Buildpack 框架,例如 Paketo,需要将 Docker socket 绑定挂载到容器中。

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

这可以通过 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 socket 绑定挂载到容器中有两种配置:imageListcommandList。下面将对此进行描述。

镜像列表

imageList 是允许绑定挂载 Docker socket 的容器镜像列表。默认情况下,此列表为空,当启用 ECI 时,不允许任何容器绑定挂载 Docker socket。但是,管理员可以将镜像添加到列表中,可以使用以下任意格式:

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

镜像名称遵循标准约定,因此它可以指向任何镜像仓库和仓库。

在前面的示例中,imageList 配置了三个镜像:

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

这意味着使用 docker.io/localstack/localstackdocker.io/testcontainers/ryuk 镜像(带任意标签),或使用 docker:cli 镜像的容器,在启用 ECI 时被允许绑定挂载 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 Socket 挂载权限

要求: Docker Desktop 4.34.0 及更高版本

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

这适用于大多数情况,但并非总是如此,因为它要求事先知道应允许挂载 Docker socket 的镜像名称。一些容器工具,例如 Paketo buildpacks,会构建需要 Docker socket 绑定挂载的临时本地镜像。由于这些临时镜像的名称无法事先得知,imageList 就不够用了。

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

也就是说,如果一个名为“myLocalImage”的本地镜像构建自“myBaseImage”(即其 Dockerfile 包含 FROM myBaseImage 指令),那么如果“myBaseImage”在 imageList 中,则“myBaseImage”和“myLocalImage”都将被允许挂载 Docker socket。

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

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

当 buildpack 运行时,它将创建一个派生自 paketobuildpacks/builder:base 的临时镜像,并将 Docker socket 挂载到其中。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 socket 镜像列表更加容易。

允许所有容器挂载 Docker socket

在 Docker Desktop 4.36 及更高版本中,可以将 imageList 配置为允许任何容器挂载 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 https:///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 的镜像可能会意外地被阻止这样做。这可能发生在远程仓库中的镜像摘要(digest)发生变化(例如,":latest" 镜像已更新),并且该镜像的本地副本(例如,之前通过 docker pull 获取的)不再与远程仓库中的摘要匹配时。在这种情况下,请删除本地镜像并再次拉取(例如,执行 docker rm <image>docker pull <image>)。

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

  • commandList 配置适用于所有允许绑定挂载 Docker socket 的容器。因此,无法为每个容器单独配置它。

  • commandList 中尚不支持以下命令

不支持的命令描述
composeDocker Compose
dev开发环境
extension管理 Docker Extensions
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 Desktop VM 中真正的 root 权限的情况下,安全地运行 Docker-in-Docker。

页面选项
© . This site is unofficial and not affiliated with Kubernetes or Docker Inc.