使用Docker secret 管理敏感数据
关于secret
就Docker Swarm集群服务而言,secret 是块状数据,例如密码、SSH私钥、SSL证书或其他不应通过网络传输或未加密存储在Dockerfile或应用程序源代码中的数据。我们可以使用Docker secret 集中管理这些数据,并仅将其安全地传输到需要访问它的容器。在传输过程中被加密,并在Docker集群中保存。只有那些已获准明确访问它的服务才能访问给定 secret ,并且只有在这些服务任务正在运行时才能访问。
您可以使用secret 来管理容器在运行时需要的任何敏感数据。比如一下情况:
- 用户名和密码
- TLS证书和密钥
- SSH密钥
- 其他重要数据,如数据库或内部服务器的名称
- 通用字符串或二进制内容(大小高达500kb)
注意:Docker secret 仅适用于集群服务,不适用于独立的容器。要使用此功能,请考虑调整您的容器作为服务运行。有状态容器通常可以在不更改容器代码的情况下以1的比例运行。
使用secret 的另一个用例是在容器和一组凭据之间提供一层抽象。考虑一个场景,即您的应用程序有单独的开发、测试和生产环境。这些环境中的每一个都可以有不同的凭据,存储在具有相同secret 名称的开发、测试和生产群中。您的容器只需要知道secret 的名称,即可在所有三个环境中运行。
您还可以使用secret 来管理非敏感数据,例如配置文件。然而,Docker支持使用配置来存储非敏感数据。配置直接挂载到容器的文件系统中,而无需使用RAM磁盘。
对 Windows 支持
Docker 包括对Windows容器上secret 的支持。如果实现方面存在差异。有以下几方面的差异,在下面的示例中说明:
- Microsoft Windows没有用于管理RAM磁盘的内置驱动程序,因此在运行的Windows容器中,secret 会以清晰的文本持续到容器的根磁盘。然而,当容器停止时,secret 会被明确删除。此外,Windows不支持使用
docker commit
或类似命令将运行容器作为映像持久化。 - 在Windows上,我们建议在主机上包含Docker根目录的卷上启用
BitLocker
^①^,以确保运行容器的 secret 在静态时被加密。 - 具有自定义目标的secret 文件不会直接绑定挂载到
Windows
容器中,因为Windows
不支持非目录文件绑定挂载。相反,容器的secret
都安装在容器内的C:\ProgramData\Docker\internal\secrets
(应用程序不应依赖的实现细节)。符号链接用于从那里指向容器内secret
的所需目标。默认目标是C:\ProgramData\Docker\secrets
。 - 在创建使用Windows容器的服务时,secret 不支持指定UID、GID和模式的选项。目前只有管理员和在容器内具有
system
访问权限的用户才能访问secret 。
Docker如何管理secret
当您向集群中添加secret 时,Docker会通过相互的TLS
连接将secret
发送给群管理器。secret
存储在Raft
日志中,该日志是加密的。整个 Raft
日志在其他管理节点之间复制,确保了与其他蜂群管理数据相同的高可用性保证。
当您授予新创建或正在运行的服务对密钥的访问权限时,解密的密钥将安装在内存文件系统中的容器中。容器中挂载点的位置默认为Linux
容器中的/run/secrets/<secret_name>
,或 Windows
容器中的C:\ProgramData\Docker\secrets
。您还可以指定自定义位置。
您可以随时更新服务,以授予其访问其他 secret
或撤销其对给定secret
的访问。
只有当节点是集群管理器或正在运行已获准访问secret
的服务任务时,节点才能访问(加密)secret 。当容器任务停止运行时,共享到它的解密机密将从该容器的内存文件系统中卸载,并从节点的内存中刷新。
如果节点在运行具有 secret
访问权限的任务容器时失去与群的连接,则任务容器仍然可以访问其 secret
,但在节点重新连接到群之前无法接收更新。
您可以随时添加或检查单个 secret
,或列出所有 secret
。您无法删除正在运行的服务正在使用的 secret
。
要更轻松地更新或回滚 secret
,请考虑在secret
名称中添加版本号或日期。控制给定容器中secret
的安装点的能力使这变得更容易。
docker secret 命令
docker secret create
创建一个secret
docker secret inspect
查看一个secret
的详细信息docker secret ls
查看有多少个secret
docker secret rm
删除secret
- [
--secret
] 在创建服务的时候 docker service create 指定使用的 secret - --secret-add
和
--secret-rm 在更新服务 secret 时候docker service update
示例
我们使用以下示例渐进的说明如何使用Docker secret 。这些示例中使用的镜像已更新,以便于使用Docker secret 。
注意:为了简单起见,这些示例使用单引擎集群和未缩放服务。这些示例使用Linux容器,但Windows容器也支持secret 。
在编写文件中定义和使用secret
docker-compose
和docker stack
命令都支持在编写文件中定义 secret
。
示例一:开始使用secret
这个简单的示例展示了secret
如何在几个命令中发挥作用。
-
为Docker添加一个
secret
。docker secret create
命令读取标准输入,因为表示要读取secret 的文件的最后一个参数被设置为-
。bashecho "This is a secret" | docker secret create my_secret_data -
bashroot@master:~# echo "this is a demo secret" | docker secret create my-secret - yy7gnh0ji9wm64fs841yej5kv root@master:~# docker secret ls ID NAME DRIVER CREATED UPDATED yy7gnh0ji9wm64fs841yej5kv my-secret 5 seconds ago 5 seconds ago root@master:~#
-
创建一个
redis
服务,并授予它对secret
的访问权限。默认情况下,容器可以在/run/secrets/<secret_name>
访问密钥,但您可以使用target
选项自定义容器上的文件名。bashdocker service create --name redis --secret my_secret_data redis:alpine
-
使用
docker service ps
验证任务是否运行时没有问题。如果一切正常,输出看起来类似于这个:bashdocker service ps redis
如果出现错误,并且任务失败并反复重新启动,您将看到以下内容:
bashdocker service ps redis
-
使用
docker ps
获取redis
服务任务容器的ID,以便您可以使用docker container exec
连接到容器并读取secret 数据文件的内容,该文件默认为可被所有人读取,并且与secret
的名称相同。使用一下命令获取 redis 容器服务的 ID
bashdocker ps --filter name=redis -q
查看容器中 secret 的内容:
bashdocker container exec $(docker ps --filter name=redis -q) ls -l /run/secrets docker container exec $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data
bashroot@master:~# docker exec $(docker ps --filter name=redis -q) ls -l /run/secrets total 4 -r--r--r-- 1 root root 22 Aug 5 08:09 my-secret root@master:~# docker exec $(docker ps --filter name=redis -q) cat /run/secrets cat: read error: Is a directory root@master:~# docker exec $(docker ps --filter name=redis -q) cat /run/secrets/my-secret this is a demo secret root@master:~#
可以看到 secret 的内容已经挂载到了我们的容器中。
-
如果您提交容器,请验证密钥不可用。
bash$ docker commit $(docker ps --filter name=redis -q) committed_redis $ docker run --rm -it committed_redis cat /run/secrets/my_secret_data cat: can't open '/run/secrets/my_secret_data': No such file or directory
-
我们删除
secret
发现删除失败,因为redis
服务正在运行并可以访问该secret 。bashdocker secret ls docker secret rm my_secret_data
-
通过更新服务,从正在运行的
redis
服务中删除对密钥的访问。bashdocker service update --secret-rm my_secret redis
-
再次重复步骤3和4,验证服务不再访问密钥。容器ID不同,因为
service update
命令会重新部署服务。bash$ docker container exec -it $(docker ps --filter name=redis -q) cat /run/secrets/my_secret_data cat: can't open '/run/secrets/my_secret_data': No such file or directory
-
停止并删除服务,并从Docker中删除密钥。
bashdocker service rm redis docker secret rm my_secret
示例②:在Nginx服务中使用 secret
这个例子分为两部分。第一部分是关于生成站点证书,不直接涉及 Docker secret ,但它设置了我们可以在其中存储和使用站点证书和Nginx
配置作为 secret
。
生成站点证书
为您的站点生成根CA和TLS证书和密钥。对于生产站点,您可能希望使用Let's Encrypt
等服务来生成TLS证书和密钥,但此示例使用命令行工具。这个步骤有点复杂,但只是一个设置步骤,这样您就有一些东西可以存储为Dockersecret 。如果您想跳过这些子步骤,您可以使用Let's Encrypt
^②^ 生成站点密钥和证书,为文件命名site.key
和site.crt
。
-
生成根密钥。
bashopenssl genrsa -out "root-ca.key" 4096
-
使用根密钥生成CSR。
bashopenssl req \ -new -key "root-ca.key" \ -out "root-ca.csr" -sha256 \ -subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=Swarm Secret Example CA'
-
配置根CA。编辑一个名为
root-ca.cnf
的新文件,并将以下内容粘贴到其中。这限制了根CA签署叶证书,而不是中间CA。bash[root_ca] basicConstraints = critical,CA:TRUE,pathlen:1 keyUsage = critical, nonRepudiation, cRLSign, keyCertSign subjectKeyIdentifier=hash
-
在证书上签名。
bashopenssl x509 -req -days 3650 -in "root-ca.csr" \ -signkey "root-ca.key" -sha256 -out "root-ca.crt" \ -extfile "root-ca.cnf" -extensions \ root_ca
-
生成站点密钥。
bashopenssl genrsa -out "site.key" 4096
-
生成站点证书,并使用站点密钥进行签名。
bashopenssl req -new -key "site.key" -out "site.csr" -sha256 \ -subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost'
-
配置站点证书。编辑一个名为
site.cnf
的新文件,并将以下内容粘贴到其中。这限制了站点证书,使其只能用于验证服务器,不能用于签署证书。bash[server] authorityKeyIdentifier=keyid,issuer basicConstraints = critical,CA:FALSE extendedKeyUsage=serverAuth keyUsage = critical, digitalSignature, keyEncipherment subjectAltName = DNS:localhost, IP:127.0.0.1 subjectKeyIdentifier=hash
-
签署网站证书。
bashopenssl x509 -req -days 750 -in "site.csr" -sha256 \ -CA "root-ca.crt" -CAkey "root-ca.key" -CAcreateserial \ -out "site.crt" -extfile "site.cnf" -extensions server
-
Nginx服务不需要
site.csr
和site.cnf
文件,但如果您想生成新的站点证书,则需要它们。保护root-ca.key
文件。
配置Nginx容器
-
生成一个非常基本的Nginx配置,通过HTTPS为静态文件提供服务。TLS证书和密钥存储为Docker机密,以便轻松旋转。
在当前目录中,创建一个名为
site.conf
的新文件,其中包含以下内容:nginxserver { 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; } }
-
创建三个secret ,代表密钥、证书和
site.conf
。只要小于500 KB,您就可以将任何文件存储为secret 。这允许您将密钥、证书和配置与使用它们的服务解耦。在每个命令中,最后一个参数表示从主机文件系统上读取secret 的文件路径。在这些示例中,secret 名称和文件名是相同的。bashdocker secret create site.key site.key docker secret create site.crt site.crt docker secret create site.conf site.conf
bashdocker secret ls
-
创建一个运行Nginx并可以访问三个secret 的服务。
docker service create
命令的最后一部分从site.conf
secret 的位置创建一个符号链接到/etc/nginx.conf.d/
,Nginx在其中查找额外的配置文件。此步骤发生在Nginx实际启动之前,因此如果您更改Nginx配置,则无需重建映像。注意 :通常情况下,您将创建一个Dockerfile,将
site.conf
复制到位,构建映像,并使用自定义映像运行容器。此示例不需要自定义图像。它将site.conf
放置到位,并在一个步骤中运行容器。默认情况下,secret 位于容器中的
/run/secrets/
目录中,这可能需要在容器中采取额外的步骤才能使secret在不同的路径中可用。下面的示例创建了一个指向site.conf
文件真实位置的符号链接,从而Nginx
可以读取它:
bash
docker service create \
--name nginx \
--secret site.key \
--secret site.crt \
--secret site.conf \
--publish published=3000,target=443 \
nginx:latest \
sh -c "ln -s /run/secrets/site.conf /etc/nginx/conf.d/site.conf && exec nginx -g 'daemon off;'"
secret 允许您使用target
选项指定自定义位置,而不是创建符号链接。下面的示例说明了如何在不使用符号链接的情况下在容器内的/etc/nginx/conf.d/site.conf
上提供thesitesite.conf
secret :
bash
docker service create \
--name nginx \
--secret site.key \
--secret site.crt \
--secret source=site.conf,target=/etc/nginx/conf.d/site.conf \
--publish published=3000,target=443 \
nginx:latest \
sh -c "exec nginx -g 'daemon off;'"
site.key
和site.crt
secret 使用速记语法,没有自定义target
位置设置。简短的语法将secret 挂载在`/run/secrets/中,与secret 同名。在正在运行的容器中,现在存在以下三个文件:
/run/secrets/site.key
/run/secrets/site.crt
/etc/nginx/conf.d/site.conf
-
验证Nginx服务是否正在运行。
bashdocker service ls docker service ps nginx
-
验证服务是否可运行:您可以访问Nginx服务器,并且正在使用正确的TLS证书。
htmlcurl --cacert root-ca.crt https://localhost:3000 <title>Welcome to nginx!<</title> <h1>Welcome to nginx!</h1> If you see this page, the nginx web server is successfully installed and For online documentation and support. refer to <a>nginx.org</a>.<br/> <p><a>nginx.com</a></p> <p><em>Thank you for using nginx.</em></p>
bashopenssl s_client -connect localhost:3000 -CAfile root-ca.crt
-
我们删除不需要的服务和secret
bashdocker service rm nginx docker secret rm site.crt site.key site.conf
在镜像中构建对Docker Secrets的支持
如果您开发了一个可以作为服务部署的容器,并且需要敏感数据(如凭据)作为环境变量,请考虑调整您的映像以利用Docker secret 。一种方法是确保您在创建容器时传递给图像的每个参数也可以从文件中读取。
Docker hub 中的许多Docker官方图像,都以这种方式进行了更新。
当您启动WordPress容器时,您可以通过将其设置为环境变量来为其提供所需的参数。WordPress图像已更新,因此包含WordPress重要数据的环境变量(如WORDPRESS_DB_PASSWORD
)也具有可以从文件(WORDPRESS_DB_PASSWORD_FILE
)中读取其值的变体。此策略可确保保持向后兼容性,同时允许您的容器从Docker管理的secret 中读取信息,而不是直接传递。
NOTE
Docker机密不会直接设置环境变量。这是一个有意识的决定,因为环境变量可能会无意中在容器之间泄露(例如,如果您使用
--link
)。
在docker-compose中使用secret
yaml
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:
此示例使用编写文件中的两个secret 创建一个简单的WordPress网站。
关键字secrets:
定义两个secret db_password:
和db_root_password:
。
部署时,Docker会创建这两个secret ,并用编写文件中指定的文件中的内容填充它们。
db服务同时使用两个secret ,而wordpress正在使用一个secret 。
当您部署时,Docker会在服务的/run/secrets/<secret_name>
下挂载一个文件。这些文件永远不会在磁盘上持久化,而是在内存中管理。
每个服务都使用环境变量来指定服务应该在哪里查找该secret 数据。