Docker 17
引擎 | Engine

使用Docker机密管理敏感数据(引擎) | Manage sensitive data with Docker secrets (Engine)

使用Docker机密管理敏感数据

关于机密

就Docker Swarm服务而言,秘密是一组数据,如密码,SSH私钥,SSL证书或另一部分数据,这些数据不应通过网络传输或在Dockerfile或应用程序中未加密存储源代码。在Docker 1.13及更高版本中,您可以使用Docker 机密来集中管理这些数据,并将其安全地传输给需要访问的那些容器。在运输过程中密码被加密,并在Docker群中休息。给定的秘密只能被那些被授予明确访问权限的服务访问,并且只有在这些服务任务正在运行时才能访问。

您可以使用秘密来管理容器在运行时需要的任何敏感数据,但不想将其存储在映像或源代码管理中,例如:

  • 用户名和密码

注意:Docker机密仅适用于群集服务,而不适用于独立容器。要使用此功能,请考虑将您的容器作为1级服务运行。

另一个使用秘密的用例是在容器和一组证书之间提供一个抽象层。考虑一个场景,您可以为应用程序分别开发,测试和生产环境。这些环境中的每一个都可以拥有不同的凭证,并以相同的秘密名称存储在开发,测试和生产群中。您的容器只需要知道秘密的名称,以便在三种环境中都能正常工作。

您还可以使用机密来管理非敏感数据,例如配置文件。但是,Docker 17.06和更高版本支持使用configs来存储非敏感数据。配置直接安装到容器的文件系统中,而不使用RAM磁盘。

Windows支持

Docker 17.06和更高版本包含对Windows容器上的秘密的支持。在实现中存在差异的地方,它们在下面的例子中被调用。牢记以下显着差异:

  • Microsoft Windows没有用于管理RAM磁盘的内置驱动程序,因此在运行Windows容器中,秘密以明文形式保存到容器的根磁盘。但是,当容器停止时,秘密将被明确删除。另外,Windows不支持将正在运行的容器作为使用图像docker commit或类似命令的图像。

Docker如何管理机密

当您为swarm添加秘密时,Docker会通过相互TLS连接将密钥发送给swarm管理器。秘密存储在加密的Raft日志中。整个Raft日志被复制到其他管理者中,确保与其他群管理数据相同的高可用性保证。

警告:Raft数据在Docker 1.13和更高版本中被加密。如果您的任何Swarm管理人员运行早期版本,并且其中一位经理成为群体经理,则秘密将以未加密方式存储在该节点的Raft日志中。在添加任何秘密之前,将所有管理器节点更新到Docker 1.13或更高版本,以防止将秘密写入纯文本的Raft日志。

当您授予新创建或正在运行的服务对秘密的访问权限时,解密的秘密将被装入容器中的内存中文件系统。 容器中安装点的位置默认为Linux容器中的/ run / secrets / <secret_name>,或Windows容器中的C:\ ProgramData \ Docker \ secrets。 您可以在Docker 17.06和更高版本中指定自定义位置。

您可以更新服务,授权其访问其他机密或随时撤销对指定机密的访问权限。

如果节点是群管理器或者它正在运行已被授权访问秘密的服务任务,那么节点只能访问(加密的)秘密。当容器任务停止运行时,共享给它的解密秘密将从该容器的内存中文件系统卸载,并从节点的内存刷新。

如果节点在运行可访问秘密的任务容器时失去与群集的连接,则任务容器仍可访问其秘密,但在节点重新连接群集之前无法接收更新。

您可以随时添加或检查个人密码,或列出所有密码。您无法删除正在运行的服务正在使用的秘密。请参阅旋转秘密以在不中断正在运行的服务的情况下移除秘密。

为了更容易地更新或回滚秘密,请考虑在秘密名称中添加版本号或日期。通过控制给定容器内秘密的安装点的能力使这变得更容易。

阅读更多关于docker secret命令的信息

使用这些链接阅读有关特定命令的内容,或继续关于在服务中使用秘密的示例。

  • docker secret create

例子

本节包含三个演示如何使用Docker机密的毕业示例。这些示例中使用的图像已更新,以便于使用Docker机密。要了解如何以类似的方式修改自己的图像,请参阅将Docker机密支持构建到图像中。

注意:为简单起见,这些示例使用单引擎群和非标度服务。这些示例使用Linux容器,但Windows容器也支持Docker 17.06和更高版本中的秘密。请参阅Windows支持。

简单的例子:开始秘密

这个简单的例子展示了秘密如何在几个命令中工作。对于真实世界的例子,继续到中级例子:使用Nginx服务的秘密。

  • 给Docker添加一个秘密。该docker secret create命令读取标准输入,因为最后一个参数表示要读取密钥的文件设置为-。$ echo "This is a secret" | docker secret create my_secret_data -

简单的例子:在Windows服务中使用秘密

这是一个非常简单的例子,它展示了如何使用运行在Microsoft Windows Server 2016上的Docker 17.06 EE或Microsoft Windows 10上的Docker for Mac 17.06上运行的Microsoft IIS服务的秘密。这是一个理想的例子,它将网页存储在一个秘密中。

此示例假定您已安装PowerShell。

  • 将以下内容保存到一个新文件中:index.html<html> <head> <title> Hello Docker </ title> </ head> <body> <p> Hello Docker!您已经部署了一个HTML页面。</ p> </ body> </ html>

中级示例:使用Nginx服务的秘密

这个例子分为两部分。第一部分是关于生成站点证书的内容,并不直接涉及Docker机密,但是它建立了第二部分,在那里存储和使用站点证书和Nginx配置作为秘密。

生成站点证书

为您的站点生成根CA和TLS证书和密钥。对于生产站点,您可能希望使用服务Let’s Encrypt来生成TLS证书和密钥,但此示例使用命令行工具。这一步有点复杂,但只是一个设置步骤,以便您可以将某些内容存储为Docker的秘密。如果你想跳过这些子步骤,您可以使用我们的加密生成网站密钥和证书,命名文件site.keysite.crt,并跳过配置Nginx的容器。

  • 生成一个根密钥。$ openssl genrsa -out“root-ca.key”4096

配置Nginx容器

  • 生成一个非常基本的Nginx配置,通过HTTPS提供静态文件。TLS证书和密钥将作为Docker机密存储,以便它们可以轻松旋转。在当前目录中,site.conf使用以下内容创建一个新文件:server { listen 443 ssl; server_name localhost; ssl_certificate /run/secrets/site.crt; ssl_certificate_key /run/secrets/site.key; location / { root /usr/share/nginx/html; index index.html index.htm; } }

在Docker 17.05及更早版本中,机密总是位于/run/secrets/目录中。Docker 17.06及更高版本允许您为容器中的秘密指定自定义位置。下面的两个例子说明了它们的区别。该命令的较早版本要求您创建一个到site.conf文件的真实位置的符号链接,以便Nginx可以读取它,但是较新的版本不需要这样做。较旧的示例已保存,以便您可以看到差异。

- **Docker 17.06 and higher**: $ docker service create \ --name nginx \ --secret site.key \ --secret site.crt \ --secret source=site.conf,target=/etc/nginx/conf.d/site.conf \ --publish 3000:443 \ nginx:latest \ sh -c "exec nginx -g 'daemon off;'"

- **Docker 17.05 and earlier**: $ docker service create \ --name nginx \ --secret site.key \ --secret site.crt \ --secret site.conf \ --publish 3000:443 \ nginx:latest \ sh -c "ln -s /run/secrets/site.conf /etc/nginx/conf.d/site.conf && exec nginx -g 'daemon off;'"

第一个示例显示秘密的短语和长语,而第二个示例仅显示短语法。简短语法创建/run/secrets/与机密具有相同名称的文件。在正在运行的容器中,现在存在以下三个文件:

- `/run/secrets/site.key` - `/run/secrets/site.crt` - `/etc/nginx/conf.d/site.conf` (or `/run/secrets/site.conf` if you used the second example)

  • 验证Nginx服务正在运行。$ docker service ls ID NAME MODE REPLICAS IMAGE zeskcec62q24 nginx replicated 1/1 nginx:latest $ docker service ps nginx NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS nginx.1.9ls3yo9ugcls nginx:latest moby Running Running 3 minutes ago

高级示例:使用WordPress服务的秘密

在本例中,您使用自定义root密码创建单节点MySQL服务,将凭证添加为秘密,并创建使用这些凭证连接到MySQL的单节点WordPress服务。下一个示例基于这个示例,向您展示如何旋转MySQL密码并更新服务,以便WordPress服务仍然可以连接到MySQL。

此示例说明了一些使用Docker机密的技巧,以避免将敏感凭证保存在映像中或直接在命令行上传递它们。

注意:为简单起见,此示例使用单引擎群,并使用单节点MySQL服务,因为单个MySQL服务器实例无法通过简单地使用复制服务来扩展,并且设置MySQL群集超出了本示例的范围。另外,更改MySQL根密码并不像更改磁盘上的文件那么简单。您必须使用查询或mysqladmin命令来更改MySQL中的密码。

  • 为MySQL生成一个随机的字母数字密码,并mysql_password使用该docker secret create命令将其作为Docker机密存储。要使密码更短或更长,请调整该openssl命令的最后一个参数。这只是创建相对随机密码的一种方式。如果您选择,您可以使用其他命令来生成密码。 注意:创建秘密后,您无法更新它。您只能删除并重新创建它,并且无法删除服务正在使用的秘密。但是,您可以使用授予或撤销正在运行的服务对秘密的访问权限docker service update。如果您需要更新密码的功能,请考虑在密码名称中添加一个版本组件,以便稍后添加新版本,更新服务以使用它,然后删除旧版本。

最后一个参数设置为-,表示输入是从标准输入读取的。

$ openssl rand -base64 20 | docker secret create mysql_password - l1vinzevzhj4goakjap5ya409

返回的值不是密码,而是密码的ID。在本教程的其余部分中,ID输出被省略。

为MySQL root用户生成第二个秘密。此秘密不会与稍后创建的WordPress服务共享。它只需要引导mysql服务。

$ openssl rand -base64 20 | docker secret create mysql_root_password -

列出由Docker管理的秘密docker secret ls

$ docker secret ls ID NAME CREATED UPDATED l1vinzevzhj4goakjap5ya409 mysql_password 41 seconds ago 41 seconds ago yvsczlx9votfw3l0nz5rlidig mysql_root_password 12 seconds ago 12 seconds ago

秘密存储在群集的加密的Raft日志中。

  • 创建用于MySQL和WordPress服务之间通信的用户定义覆盖网络。不需要将MySQL服务公开给任何外部主机或容器。$ docker network create -d overlay mysql_private

- Because the scale is set to `1`, only a single MySQL task runs. Load-balancing MySQL is left as an exercise to the reader and involves more than just scaling the service. - Only reachable by other containers on the `mysql_private` network. - Uses the volume `mydata` to store the MySQL data, so that it persists across restarts to the `mysql` service. - The secrets are each mounted in a `tmpfs` filesystem at `/run/secrets/mysql_password` and `/run/secrets/mysql_root_password`. They are never exposed as environment variables, nor can they be committed to an image if the `docker commit` command is run. The `mysql_password` secret is the one used by the non-privileged WordPress container to connect to MySQL. - Sets the environment variables `MYSQL_PASSWORD_FILE` and `MYSQL_ROOT_PASSWORD_FILE` to point to the files `/run/secrets/mysql_password` and `/run/secrets/mysql_root_password`. The `mysql` image reads the password strings from those files when initializing the system database for the first time. Afterward, the passwords are stored in the MySQL system database itself. - Sets environment variables `MYSQL_USER` and `MYSQL_DATABASE`. A new database called `wordpress` is created when the container starts, and the `wordpress` user will have full permissions for this database only. This user will not be able to create or drop databases or change the MySQL configuration. $ docker service create \ --name mysql \ --replicas 1 \ --network mysql_private \ --mount type=volume,source=mydata,destination=/var/lib/mysql \ --secret source=mysql_root_password,target=mysql_root_password \ --secret source=mysql_password,target=mysql_password \ -e MYSQL_ROOT_PASSWORD_FILE="/run/secrets/mysql_root_password" \ -e MYSQL_PASSWORD_FILE="/run/secrets/mysql_password" \ -e MYSQL_USER="wordpress" \ -e MYSQL_DATABASE="wordpress" \ mysql:latest

  • 验证mysql容器是否正在使用该docker service ls命令运行。$ docker service ls ID NAME MODE REPLICAS IMAGE wvnh0siktqr3 mysql replicated 1/1 mysql:latest 在这一点上,你实际上可以撤销该mysql服务对访问mysql_passwordmysql_root_password秘密,因为密码已被保存在MySQL系统数据库。现在不要这样做,因为稍后我们会使用它们来方便旋转MySQL密码。

- Because the scale is set to `1`, only a single WordPress task runs. Load-balancing WordPress is left as an exercise to the reader, because of limitations with storing WordPress session data on the container filesystem. - Exposes WordPress on port 30000 of the host machine, so that you can access it from external hosts. You can expose port 80 instead if you do not have a web server running on port 80 of the host machine. - Connects to the `mysql_private` network so it can communicate with the `mysql` container, and also publishes port 80 to port 30000 on all swarm nodes. - Has access to the `mysql_password` secret, but specifies a different target file name within the container. The WordPress container will use the mount point `/run/secrets/wp_db_password`. Also specifies that the secret is not group-or-world-readable, by setting the mode to `0400`. - Sets the environment variable `WORDPRESS_DB_PASSWORD_FILE` to the file path where the secret is mounted. The WordPress service will read the MySQL password string from that file and add it to the `wp-config.php` configuration file. - Connects to the MySQL container using the username `wordpress` and the password in `/run/secrets/wp_db_password` and creates the `wordpress` database if it does not yet exist. - Stores its data, such as themes and plugins, in a volume called `wpdata` so these files persist when the service restarts.

$ docker service create \ --name wordpress \ --replicas 1 \ --network mysql_private \ --publish 30000:80 \ --mount type=volume,source=wpdata,destination=/var/www/html \ --secret source=mysql_password,target=wp_db_password,mode=0400 \ -e WORDPRESS_DB_USER="wordpress" \ -e WORDPRESS_DB_PASSWORD_FILE="/run/secrets/wp_db_password" \ -e WORDPRESS_DB_HOST="mysql:3306" \ -e WORDPRESS_DB_NAME="wordpress" \ wordpress:latest

  • 验证服务正在使用docker service lsdocker service ps命令运行。$ docker service ls ID NAME MODE REPLICAS IMAGE wvnh0siktqr3 mysql replicated 1/1 mysql:latest nzt5xzae4n62 wordpress replicated 1/1 wordpress:latest $ docker service ps wordpress ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS aukx6hgs9gwc wordpress.1 wordpress:latest moby Running Running 52 seconds ago 此时,您实际上可以撤消WordPress服务对mysql_password秘密的访问,因为WordPress已将秘密复制到其配置文件中wp-config.php。现在不要这样做,因为我们稍后会使用它来方便旋转MySQL密码。

例如:回旋机密

这个例子建立在前一个例子上。在这种情况下,您使用新的MySQL密码创建一个新密码,更新mysqlwordpress使用它的服务,然后删除旧密码。

注意:更改MySQL数据库的密码涉及运行额外的查询或命令,而不是仅仅更改单个环境变量或文件,因为如果数据库尚不存在,映像只设置MySQL密码,并且MySQL存储默认情况下,MySQL数据库中的密码。轮换密码或其他秘密可能涉及Docker之外的其他步骤。

  • 创建新密码并将其存储为一个名为secret的密码mysql_password_v2。$ openssl rand -base64 20 | docker secret create mysql_password_v2 -

将Docker秘诀支持到您的图像中

如果您开发的容器可以作为服务进行部署,并且需要敏感数据(如凭证)作为环境变量,那么可以考虑调整图像以充分利用Docker机密。一种方法是确保在创建容器时传递给图像的每个参数也可以从文件中读取。

Docker库中的许多官方图像,例如上面示例中使用的wordpress图像,都以这种方式进行了更新。

当你启动一个WordPress容器时,通过将它们设置为环境变量来提供它所需的参数。WordPress图片已经更新,因此包含WordPress重要数据的环境变量(如WORDPRESS_DB_PASSWORD变量)也可以从文件(WORDPRESS_DB_PASSWORD_FILE)中读取它们的值。这种策略可确保向后兼容性得到保留,同时允许容器从Docker管理的秘密中读取信息,而不是直接传递。

注意:Docker机密不会直接设置环境变量。这是一个有意识的决定,因为环境变量可能会无意中泄漏到容器之间(例如,使用--link)。

在撰写中使用秘密

version: '3.1' services: db: image: mysql:latest volumes: - db_data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD_FILE: /run/secrets/db_password secrets: - db_root_password - db_password wordpress: depends_on: - db image: wordpress:latest ports: - "8000:80" environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD_FILE: /run/secrets/db_password secrets: - db_password secrets: db_password: file: db_password.txt db_root_password: file: db_root_password.txt volumes: db_data:

本示例使用撰写文件中的两个秘密创建一个简单的WordPress网站。

关键字secrets:定义了两个秘密db_password:db_root_password:

部署时,Docker将创建这两个秘密,并使用撰写文件中指定的文件中的内容填充它们。

数据库服务使用两个秘密,并且wordpress使用一个。

在部署时,Docker将/run/secrets/<secret_name>在服务下安装一个文件。这些文件永远不会保存在磁盘中,而是在内存中进行管理。

每个服务使用环境变量来指定服务应该在哪里查找该秘密数据。

有关秘密短语和长语法的更多信息可以在Compose文件版本3参考中找到。