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套接字绑定挂载到容器中:imageList
和commandList
。这些将在下面描述。
镜像列表
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/localstack
或docker.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套接字挂载权限
如上一节所述,管理员可以通过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 上的build
和push
命令:
"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
中尚不支持以下命令:
不支持的命令 | 描述 |
---|---|
compose | Docker Compose |
dev | 开发环境 |
extension | 管理 Docker 扩展 |
feedback | 向 Docker 发送反馈 |
init | 创建 Docker 相关的启动文件 |
manifest | 管理 Docker 镜像清单 |
plugin | 管理插件 |
sbom | 查看软件物料清单 (SBOM) |
scout | Docker Scout |
trust | 管理 Docker 镜像的信任 |
注意
运行“真正的”Docker-in-Docker(即在容器内运行 Docker Engine)时,Docker socket 挂载权限不适用。在这种情况下,主机 Docker socket 没有绑定挂载到容器中,因此容器不会利用主机 Docker Engine 的配置和凭据执行恶意活动。增强型容器隔离能够安全地运行 Docker-in-Docker,而无需在 Docker Desktop 虚拟机中向外部容器授予真正的 root 权限。