使用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-composedocker stack命令都支持在编写文件中定义 secret

示例一:开始使用secret

这个简单的示例展示了secret 如何在几个命令中发挥作用。

  1. 为Docker添加一个 secretdocker secret create命令读取标准输入,因为表示要读取secret 的文件的最后一个参数被设置为-

     echo "This is a secret" | docker secret create my_secret_data -
    
    root@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:~# 
    
  2. 创建一个redis服务,并授予它对 secret 的访问权限。默认情况下,容器可以在/run/secrets/<secret_name>访问密钥,但您可以使用target选项自定义容器上的文件名。

     docker service  create --name redis --secret my_secret_data redis:alpine
    
  3. 使用docker service ps验证任务是否运行时没有问题。如果一切正常,输出看起来类似于这个:

     docker service ps redis
    

    如果出现错误,并且任务失败并反复重新启动,您将看到以下内容:

     docker service ps redis
    
  4. 使用docker ps获取redis服务任务容器的ID,以便您可以使用docker container exec连接到容器并读取secret 数据文件的内容,该文件默认为可被所有人读取,并且与secret 的名称相同。

    使用一下命令获取 redis 容器服务的 ID

     docker ps --filter name=redis -q
    

    查看容器中 secret 的内容:

     docker 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
    
    root@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 的内容已经挂载到了我们的容器中。

  5. 如果您提交容器,请验证密钥不可用。

    $ 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
    
  6. 我们删除 secret 发现删除失败,因为redis服务正在运行并可以访问该secret 。

     docker secret ls
     docker secret rm my_secret_data
    
  7. 通过更新服务,从正在运行的redis服务中删除对密钥的访问。

     docker service update --secret-rm my_secret redis
    
  8. 再次重复步骤3和4,验证服务不再访问密钥。容器ID不同,因为service update命令会重新部署服务。

    $ 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
    
  9. 停止并删除服务,并从Docker中删除密钥。

     docker 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.keysite.crt

  1. 生成根密钥。

     openssl genrsa -out "root-ca.key" 4096
    
  2. 使用根密钥生成CSR。

     openssl 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'
    
  3. 配置根CA。编辑一个名为root-ca.cnf的新文件,并将以下内容粘贴到其中。这限制了根CA签署叶证书,而不是中间CA。

    [root_ca]
    basicConstraints = critical,CA:TRUE,pathlen:1
    keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
    subjectKeyIdentifier=hash
    
  4. 在证书上签名。

     openssl x509 -req  -days 3650  -in "root-ca.csr" \
                   -signkey "root-ca.key" -sha256 -out "root-ca.crt" \
                   -extfile "root-ca.cnf" -extensions \
                   root_ca
    
  5. 生成站点密钥。

     openssl genrsa -out "site.key" 4096
    
  6. 生成站点证书,并使用站点密钥进行签名。

     openssl req -new -key "site.key" -out "site.csr" -sha256 \
              -subj '/C=US/ST=CA/L=San Francisco/O=Docker/CN=localhost'
    
  7. 配置站点证书。编辑一个名为site.cnf的新文件,并将以下内容粘贴到其中。这限制了站点证书,使其只能用于验证服务器,不能用于签署证书。

    [server]
    authorityKeyIdentifier=keyid,issuer
    basicConstraints = critical,CA:FALSE
    extendedKeyUsage=serverAuth
    keyUsage = critical, digitalSignature, keyEncipherment
    subjectAltName = DNS:localhost, IP:127.0.0.1
    subjectKeyIdentifier=hash
    
  8. 签署网站证书。

     openssl 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
    
  9. Nginx服务不需要site.csrsite.cnf文件,但如果您想生成新的站点证书,则需要它们。保护root-ca.key文件。

配置Nginx容器

  1. 生成一个非常基本的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;
        }
    }
    
  2. 创建三个secret ,代表密钥、证书和site.conf。只要小于500 KB,您就可以将任何文件存储为secret 。这允许您将密钥、证书和配置与使用它们的服务解耦。在每个命令中,最后一个参数表示从主机文件系统上读取secret 的文件路径。在这些示例中,secret 名称和文件名是相同的。

     docker secret create site.key site.key
     docker secret create site.crt site.crt
     docker secret create site.conf site.conf
    
     docker secret ls
    
  3. 创建一个运行Nginx并可以访问三个secret 的服务。docker service create命令的最后一部分从site.confsecret 的位置创建一个符号链接到/etc/nginx.conf.d/,Nginx在其中查找额外的配置文件。此步骤发生在Nginx实际启动之前,因此如果您更改Nginx配置,则无需重建映像。

    注意:通常情况下,您将创建一个Dockerfile,将site.conf复制到位,构建映像,并使用自定义映像运行容器。此示例不需要自定义图像。它将site.conf放置到位,并在一个步骤中运行容器。

    默认情况下,secret 位于容器中的/run/secrets/目录中,这可能需要在容器中采取额外的步骤才能使secret在不同的路径中可用。下面的示例创建了一个指向site.conf文件真实位置的符号链接,从而 Nginx 可以读取它:

     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.confsecret :

     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.keysite.crtsecret 使用速记语法,没有自定义target位置设置。简短的语法将secret 挂载在`/run/secrets/中,与secret 同名。在正在运行的容器中,现在存在以下三个文件:

    • /run/secrets/site.key
  • /run/secrets/site.crt
    • /etc/nginx/conf.d/site.conf
  1. 验证Nginx服务是否正在运行。

     docker service ls
     docker service ps nginx
    
  2. 验证服务是否可运行:您可以访问Nginx服务器,并且正在使用正确的TLS证书。

    curl --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>
    
     openssl s_client -connect localhost:3000 -CAfile root-ca.crt
    
  3. 我们删除不需要的服务和secret

     docker 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

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 数据。

文献

① BitLocker :https://technet.microsoft.com/en-us/library/cc732774(v=ws.11).aspx

② letsencrypt https://letsencrypt.org/getting-started

类似的帖子