将密钥安全地传递给 docker 容器

信息安全 密钥管理 码头工人
2021-08-16 21:25:46

我想传递在 Docker 容器中运行的应用程序所需的秘密值。这个特定的容器是短暂的——它启动、运行一个命令,然后终止。

方法一:在启动容器时通过命令行将值作为环境变量传递(Docker 支持将此作为启动容器的命令行参数)。我觉得这很糟糕,因为该命令将显示在启动 docker 容器的主机上的进程列表中(带有密钥和全部)。

方法 2:通过 env 变量文件将值作为 env 变量传递。这解决了进程列表问题,但docker info在主机上运行的容器上运行会显示为该容器设置的所有环境变量的列表。这让我相信 Docker 将这些存储在不安全的主机上的磁盘上的某个位置(因为在运行的容器中添加新的环境变量不会更新此列表,它不能直接实时读取它)。

一般来说,我觉得环境变量不足以安全地存储秘密数据(即使只是暂时的),但我没有足够的知识来支持这个想法。

将秘密数据传递到容器的安全方法是什么?

4个回答

环境变量是最好的方法,特别是方法 2。默认情况下,Docker 不允许自己由 root 以外的用户运行。禁止访问套接字。我想说方法 2 是相当安全的,因为如果攻击者具有 root 访问权限(并且可以在您的 docker 容器中四处寻找),那么您已经处于糟糕的状态。

两个 Docker 安全说明。启用 API 时要格外小心,因为默认情况下没有加密或身份验证。他们有办法使用他们记录的证书和 TLS,但要谨慎行事。

此外,如果可能,请在您的服务器上启用 SELinux。它的较新版本能够实际看到 docker 容器并自动为每个容器构建安全上下文。这可以防止容器妥协轻松移回主机。默认情况下,docker 以 root 用户身份运行,即使使用 USER 指令,它仍然直接与内核交互,这与 VM 不同。一旦 docker 容器受到威胁,这会使主机暴露于任何本地特权漏洞。

Docker 人员最近为此推出了他们的本地解决方案: https ://blog.docker.com/2017/02/docker-secrets-management/

使用模式是:

$ echo "This is a secret" | docker secret create my_secret_data -
$ docker service  create --name="redis" --secret="my_secret_data" redis:alpine

然后将未加密的秘密挂载到位于内存文件系统中的容器中/run/secrets/<secret_name>

虽然这只能在一个 swarm

你可以在这里找到完整的文档:https ://docs.docker.com/engine/swarm/secrets/

简短的回答

docker build--secretAPI 版本 1.39+ 的选项

长答案

API 版本 1.39+ 表示 docker 18.09.0+

在发行说明中,在18.09.0 的“Docker Engine EE 和 CE 的新功能”部分下说:

  • 将 API 版本更新为 1.39 moby/moby#37640

指南中的“Build Enhancements for Docker”页面的解释有点过时。

--secretNew Docker Build secret information找到了选项,但这里的解释已经过时了。它说

这个 Dockerfile 只是为了证明秘密可以被访问。如您所见,构建输出中打印的秘密。最终构建的图像不会有秘密文件

但实际上秘密并没有打印在构建输出中。我认为这是出于安全考虑。

buildkit 中的“Dockerfile 前端实验语法”页面有最新的解释。

然后我找到了以下页面。

如何使用 docker build --secret

以下是要遵循的步骤。

  1. 确保使用所需版本的 docker。
$ docker version
Client: Docker Engine - Community
 Version:           19.03.2
 API version:       1.40
 Go version:        go1.12.8
 Git commit:        6a30dfc
 Built:             Thu Aug 29 05:29:11 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.2
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.8
  Git commit:       6a30dfc
  Built:            Thu Aug 29 05:27:45 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.2.6
  GitCommit:        894b81a4b802e4eb2a91d1ce216b8817763c29fb
 runc:
  Version:          1.0.0-rc8
  GitCommit:        425e105d5a03fabd737a126ad93d62a9eeede87f
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683
  1. DOCKER_BUILDKIT环境变量设置为1
$ export DOCKER_BUILDKIT=1
  1. 创建一个秘密文件。
$ echo "It's a secret" > mysecret.txt
  1. 创建一个 Dockerfile。
$ cat <<EOF > Dockerfile
# syntax = docker/dockerfile:experimental
FROM alpine
RUN --mount=type=secret,id=mysecret,target=/foobar cat /foobar | tee /output
EOF

确保您# syntax = docker/dockerfile:experimentalDockerfile. 请注意,上面的示例仅用于演示。实际使用时不要保存secret的内容。

  1. docker build使用--secret选项运行。
$ docker build -t secret-example --secret id=mysecret,src=mysecret.txt .
[+] Building 2.3s (8/8) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 176B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => resolve image config for docker.io/docker/dockerfile:experimental
 => CACHED docker-image://docker.io/docker/dockerfile:experimental@sha256:888f21826273409b5ef5ff9ceb90c64a8f8ec7760da30d1ffbe6c3e2d323a7bd
 => [internal] load metadata for docker.io/library/alpine:latest
 => CACHED [1/2] FROM docker.io/library/alpine
 => [2/2] RUN --mount=type=secret,id=mysecret,target=/foobar cat /foobar | tee /output
 => exporting to image
 => => exporting layers
 => => writing image sha256:22c44473107b6d1f92095c6400613a7e82c9835f5baaa85853a114e4bb5d8744
 => => naming to docker.io/library/secret-example

请注意,mysecret.txt即使在构建输出中也不会打印 的内容。

验证密钥是否正确使用。同样,这仅用于演示目的。

$ docker run -t secret-example cat /output
It's a secret

我注意到/foobar没有保存的内容,但空文件保留在构建的图像中。

$ docker run -t secret-example ls -l /foobar
-rwxr-xr-x    1 root     root             0 Sep 16 19:16 /foobar

docker secret仅在swarm模式下工作。对于本地模式,要传递一些简单的秘密,例如密码,我们可以将密码读入标准输入的变量中。困难来自detach模式,在读取容器内的管道时会挂起。这是一个解决方法:

cid=$(docker run -d -i alpine sh -c 'read A; echo "[$A]"; exec some-server')
docker exec -i $cid sh -c 'cat > /proc/1/fd/0' <<< _a_secret_

首先,使用选项创建 docker 守护进程-i,该命令read A将挂起,等待来自 的输入/proc/1/fd/0然后运行第二个 docker 命令,从标准输入读取秘密并重定向到最后一个挂起的进程。秘密只会被读取一次,并且不会被检查。