Docker和密码安全

237

最近我一直在使用Docker进行各种服务的构建和尝试,但有个问题一直困扰着我——如何在Dockerfile中存储密码?作为开发者,将密码存储在源代码中让人感到非常不安。这是否应该成为一个问题?有没有好的惯例来处理Dockerfile中的密码?


8
Github上有一个未解决的问题,请求关于Docker和secrets的最佳实践,该问题链接在这里:https://github.com/docker/docker/issues/13490。 - Luís Bianchin
14个回答

163
这确实是一个问题。Dockerfiles通常会被检入到代码库中并与他人共享。另一种方法是在运行时将任何凭据(用户名、密码、令牌或任何敏感信息)作为环境变量提供。可以通过-e参数(用于CLI上的单个变量)或--env-file参数(用于文件中的多个变量)传递给docker run来实现。阅读此处以了解如何在docker-compose中使用环境变量。
使用--env-file绝对是更安全的选择,因为它可以防止在使用set -x时将机密信息显示在ps或日志中。
然而,环境变量并不特别安全。它们可以通过 docker inspect 查看,并且因此可供任何能够运行 docker 命令的用户使用。(当然,任何在主机上具有 docker 访问权限的用户都已经 拥有 root 权限 了。)
我的首选模式是将包装脚本用作 ENTRYPOINTCMD。包装脚本可以在运行时首先从外部位置导入秘密到容器中,然后执行应用程序,提供秘密。这个过程的确切机制根据您的运行时环境而异。在 AWS 中,您可以使用 IAM 角色、密钥管理服务和 S3 在 S3 存储桶中存储加密的秘密。类似 HashiCorp Vaultcredstash 的工具也是一个选择。
据我所知,目前没有一种最佳的模式来将敏感数据作为构建过程的一部分使用。实际上,我在此主题上有一个stackoverflow问题。您可以使用docker-squash从镜像中删除层。但是Docker本身没有这个功能。
您可能会发现shykes在容器配置注释中的评论很有用。

1
是的,环境变量似乎是最好的选择。我一直在研究如何在TDD Dockerfile开发中使用它。 - gnoll110
5
我担心如果您的密码是一个环境变量,它会出现在 docker inspect 中。 - slim
默认的Docker安装(在Linux上)需要sudoer权限才能运行"docker inspect"。如果攻击者已经可以使用sudo,那么从docker inspect中窃取密码可能不是现在可能出错的事情列表中的重点。这个细节对我来说似乎是可以接受的风险。 - GrandOpener
8
这只适用于有攻击者使用您的系统的情况。如果我将 Docker 镜像推送到仓库,并被其他人拉取,我不关心他们在自己的系统上是否有 sudo,但我一定关心他们是否能看到不应该存在的 env 中的机密信息。 - vee_ess
2
环境参数的一个问题不就是它们本质上会被复制到所有后代进程中,包括孙子进程吗?因此,在您的 Docker 容器中使用的脚本或插件可能会意外地访问这些密码,对吧? - Groostav
显示剩余2条评论

107

我们的团队避免将凭据放入存储库中,因此这意味着它们不允许出现在Dockerfile中。在应用程序内部,我们的最佳实践是使用来自环境变量的凭据。

我们通过使用docker-compose来解决这个问题。

docker-compose.yml中,您可以指定一个文件,其中包含容器的环境变量:

 env_file:
- .env

请务必在.gitignore中添加.env,然后在.env文件中设置凭据,例如:

SOME_USERNAME=myUser
SOME_PWD_VAR=myPwd

.env文件存储在本地或其他团队成员可以获取的安全位置。

参见:https://docs.docker.com/compose/environment-variables/#/the-env-file


21
如果您愿意,也可以不使用.env文件来完成这一操作。只需在您的docker-compose.yml文件中使用环境变量属性即可。_"仅具键的环境变量将被解析为Compose正在运行的机器上的值,这对于秘密或特定于主机的值非常有帮助。"_ - D. Visser
1
给这个人一个饼干! :) 是的,这是非常好的练习。我只想补充一点,https://docs.docker.com/compose/env-file/ 这应该自动工作,但在docker compose版本2中,似乎需要按照这个答案所述进行声明。 - equivalent8
6
Docker团队本身不鼓励使用环境变量,因为环境变量可以通过/proc/<pid>/environ和docker inspect来查看。这只会混淆攻击者获取凭据的方式,但无法真正防止获得root访问权限的攻击者窃取凭据。当然,凭据永远不应该被存储在版本控制系统中。我认为,防止root用户获取凭据的唯一方法是从Web应用程序内部读取凭据(希望它不会更新其进程环境文件),并从加密文件中解密,解密过程需要安全地请求密码。我打算尝试使用Tomb:https://github.com/dyne/Tomb - pawamoy
.gitignore so that the .env file with sensitive information does not get checked-in to GitHub. I'm pretty sure this won't work if you add it .dockerignore - theUtherSide
4
如果您可以提供任何“方法/工具”的示例或链接,那将非常好。 - fsevenm
显示剩余2条评论

40

Docker现在支持管理私密信息,版本号为1.13或17.06及更高版本。这里是一个概述和更详细的文档

KubernetesDCOS也有类似的功能。


1
一些有用的来自上述链接的命令: docker secret create:创建一个秘密 docker secret inspect:显示有关秘密的详细信息 docker secret ls:查看所有秘密 docker secret rm:删除特定的秘密 --secret 标志用于 docker service create:在服务创建期间创建一个秘密 --secret-add--secret-rm 标志用于 docker service update:在服务更新任务期间更新秘密的值或删除秘密。Docker secrets 在管理节点上处于静止状态,并在容器启动期间向工作节点供应。 - P.J
16
是的,您需要设置一个 swarm 才能使用 Docker secrets。 - Heather QC
20
这是一个不错的回答开端,但需要更多信息来自链接以便把它们包含到回答中。 - Jeff Lambert
22
如果只适用于群集,那么不确定这是否可以成为被接受的答案。许多人并不使用群集,但仍需要传递机密信息。 - John Y
Docker Compose可以在没有Swarm的情况下管理secrets。请参见此答案 - Robin Dinse
这并不是很有帮助,因为大多数开箱即用的镜像都期望通过环境变量传递密码。 - Sebi2020

9
除非你可以接受向任何可以下载镜像的人广播凭据,否则不要将凭据添加到容器中。特别是,进行ADD凭据,然后再RUN rm凭据并不安全,因为凭据文件仍然存在于最终镜像中的一个中间文件系统层中。任何有权访问该镜像的人都可以轻松提取它。
我通常看到的解决方案是,在需要检出依赖项等情况下,使用一个容器来构建另一个容器。也就是说,通常在基础容器中有一些构建环境,并且您需要调用它来构建您的应用程序容器。因此,简单的解决方案是添加应用程序源代码,然后运行RUN构建命令。如果您需要在该RUN中使用凭据,则此方法不安全。相反,您需要将源代码放入本地目录,运行(如docker run)容器以使用挂载为卷的本地源目录执行构建步骤,并将凭据注入或作为另一个卷挂载。完成构建步骤后,通过仅ADD本地源目录(其中现在包含构建的工件)来构建最终容器。
我希望Docker能够添加一些功能来简化所有这些!
更新:看起来未来的方法将是具有嵌套构建。简而言之,dockerfile 将描述用于构建运行时环境的第一个容器,然后使用第二个嵌套容器构建可以将所有部件组装到最终容器中的内容。因此,构建时间的内容不在第二个容器中。例如,Java 应用程序需要 JDK 构建应用程序,但仅需要 JRE 运行它。正在讨论许多提案,最好从https://github.com/docker/docker/issues/7115开始,并跟随一些链接以获取备选提案。

7

仅运行时解决方案

docker-compose还提供了一个非Swarm模式的解决方案(自v1.11以来: 使用绑定挂载的Secrets)。

Secrets作为文件被挂载到/run/secrets/下,由docker-compose处理。这解决了在运行容器时的问题,但不解决构建镜像时的问题,因为/run/secrets/在构建镜像时未被挂载。此外,此行为取决于使用docker-compose运行容器。


示例:

Dockerfile

FROM alpine
CMD cat /run/secrets/password

docker-compose.yml

version: '3.1'
services:
  app:
    build: .
    secrets:
      - password

secrets:
  password:
    file: password.txt

要构建,请执行:

docker-compose up -d

更多阅读资料:


1
但不是在构建时(构建镜像),而你的示例是针对构建时!如果您在CMD pragma中使用挂载的secret-file,则会有意义。 - lnksz
@lnksz 谢谢,我已经适应了这个例子。 - Murmel
这只能使用类似这样的前缀才能正常工作:DOCKER_BUILDKIT=1 docker-compose up -d - Robin Dinse

7

如果你有很多环境变量,使用它们可能会变得混乱不堪。此时可以使用主机文件夹,在容器中使其可访问,作为一种替代方案。

如果你将所有凭据存储在该文件夹中,则容器可以读取文件并随意使用。

例如:

$ echo "secret" > /root/configs/password.txt
$ docker run -v /root/configs:/cfg ...

In the Docker container:

# echo Password is `cat /cfg/password.txt`
Password is secret

许多程序可以从独立文件中读取其凭据,因此您只需要将程序指向其中一个文件即可。

1
这是一个糟糕的想法,特别是在 bash 中输入密码。你所有的秘密都会被存储到 bash_history 中。 - Sebi2020
是的,那是真的,但我只是用它作为文件内容应该是什么样子的一个例子。你可以使用文本编辑器或其他程序来创建文件。然而,如果你在运行可能会被攻击的系统上,你根本不应该把密码放在文件、环境变量或命令行中。在处理这样的密码时,请使用安全的系统。 - Malvineous

2

我的方法似乎可行,但可能太过天真。请告诉我它的问题所在。

在docker build期间设置的ARGs可以通过history子命令公开,因此无法使用。然而,在运行容器时,run命令中提供的环境变量可用于容器,但不是镜像的一部分。

因此,在Dockerfile中,进行不涉及机密数据的设置。将CMD设置为类似于/root/finish.sh的内容。在运行命令中,使用环境变量将秘密数据发送到容器中。 finish.sh基本上使用这些变量来完成构建任务。

为了更轻松地管理机密数据,请将其放入一个文件中,并使用--env-file开关加载该文件。当然,要保持文件的机密性,如.gitignore等。

对于我来说,finish.sh运行一个Python程序。它检查是否已经运行过,然后完成设置(例如,将数据库名称复制到Django的settings.py中)。


2

现在有一个新的docker命令用于“secrets”管理。但这只适用于群集。

docker service create
--name my-iis
--publish target=8000,port=8000
--secret src=homepage,target="\inetpub\wwwroot\index.html"
microsoft/iis:nanoserver 

2

2020年9月,Sebastiaan van Stijn 更新了 问题13490“秘密:撰写最佳实践、要点与不要点、路线图”

使用 buildkit 作为构建器时,现在可以使用构建时间机密;请参阅来自 Tõnis Tiigi 的博客文章 "Docker 18.09 中的构建机密和 SSH 转发",发布于2018年11月。

文档已更新:"使用 BuildKit 构建镜像"

RUN --mount 选项用于机密将很快成为默认(稳定)的 Dockerfile 语法。

那最后一部分是新的(2020年9月)。

New Docker Build secret information

The new --secret flag for docker build allows the user to pass secret information to be used in the Dockerfile for building docker images in a safe way that will not end up stored in the final image.

id is the identifier to pass into the docker build --secret.
This identifier is associated with the RUN --mount identifier to use in the Dockerfile.
Docker does not use the filename of where the secret is kept outside of the Dockerfile, since this may be sensitive information.

dst renames the secret file to a specific file in the Dockerfile RUN command to use.

For example, with a secret piece of information stored in a text file:

$ echo 'WARMACHINEROX' > mysecret.txt

And with a Dockerfile that specifies use of a BuildKit frontend docker/dockerfile:1.0-experimental, the secret can be accessed.

For example:

# syntax = docker/dockerfile:1.0-experimental
FROM alpine

# shows secret from default secret location:
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret

# shows secret from custom secret location:
RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar

这个Dockerfile只是为了证明可以访问到密钥。正如你所看到的,密钥已经在构建输出中打印出来了。最终构建的镜像将不会包含密钥文件。
$ docker build --no-cache --progress=plain --secret id=mysecret,src=mysecret.txt .
...
#8 [2/3] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
#8       digest: sha256:5d8cbaeb66183993700828632bfbde246cae8feded11aad40e524f54ce7438d6
#8         name: "[2/3] RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret"
#8      started: 2018-08-31 21:03:30.703550864 +0000 UTC
#8 1.081 WARMACHINEROX
#8    completed: 2018-08-31 21:03:32.051053831 +0000 UTC
#8     duration: 1.347502967s

#9 [3/3] RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar
#9       digest: sha256:6c7ebda4599ec6acb40358017e51ccb4c5471dc434573b9b7188143757459efa
#9         name: "[3/3] RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar"
#9      started: 2018-08-31 21:03:32.052880985 +0000 UTC
#9 1.216 WARMACHINEROX
#9    completed: 2018-08-31 21:03:33.523282118 +0000 UTC
#9     duration: 1.470401133s
...

1

1
我感谢对圣经的引用。 - JakeCowton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接