NGINX 教程:如何安全地管理容器中的机密

本文是帮助您将 2023 年 3 月的微服务概念付诸实践的四篇教程之一:开始交付微服务:

  • 如何部署和配置微服务
  • 如何安全地管理容器中的机密(本文)
  • 如何使用 GitHub Actions 自动化微服务金丝雀发布
  • 如何使用 OpenTelemetry 跟踪来了解您的微服务

您的许多微服务需要秘密才能安全运行。机密的示例包括 SSL/TLS 证书的私钥、用于向其他服务进行身份验证的 API 密钥或用于远程登录的 SSH 密钥。正确的秘密管理需要严格限制秘密使用的上下文,仅在需要的地方使用,并防止秘密在需要时被访问。但在匆忙的应用程序开发中,这种做法常常被跳过。结果?秘密管理不当是信息泄露和泄露的常见原因的利用。

教程概述

在本教程中,我们将展示如何安全地分发和使用客户端容器用于访问服务的 JSON Web 令牌 (JWT)。在本教程的四个挑战中,您将尝试四种不同的秘密管理方法,不仅可以了解如何在容器中正确管理秘密,还可以了解不充分的方法:

  • 在您的应用中硬编码机密
  • 将机密作为环境变量传递
  • 使用本地机密
  • 使用机密管理器

虽然本教程使用 JWT 作为示例机密,但这些技术适用于您需要保密的容器的任何内容,例如数据库凭据、SSL 私钥和其他 API 密钥。

本教程利用两个主要软件组件:

  • API 服务器 – 运行 NGINX 开源和一些基本 NGINX JavaScript 代码的容器,该代码从 JWT 中提取声明并从其中之一返回一个值他声明,或者如果没有声明,则显示错误消息
  • API 客户端 – 运行非常简单的 Python 代码的容器,只需向 API 服务器发出 GET 请求

观看此视频,了解教程的实际演示。

学习本教程的最简单方法是注册 Microservices March 并使用提供的基于浏览器的实验室。本文提供了在您自己的环境中运行本教程的说明。

先决条件和设置

先决条件

要在您自己的环境中完成本教程,您需要:

  • Linux/Unix 兼容环境
  • 基本熟悉 Linux 命令行
  • nano 或 vim 等文本编辑器
  • Docker(包括 Docker Compose 和 Docker Engine Swarm)
  • curl(已安装在大多数系统上)
  • git(已安装在大多数系统上)

注释:

  • 本教程使用测试服务器 listening 在端口 80 上。如果您已经在使用端口 80,请在使用 docker run 命令启动测试服务器时使用 -p 标志为测试服务器设置不同的值。然后在curl命令中包含本地主机上的:后缀。
  • 在整个教程中,Linux 命令行上的提示被省略,以便更轻松地将命令剪切并粘贴到终端中。波形符 (~) 代表您的主目录。

设置

在本部分中,您将克隆教程存储库、启动身份验证服务器,并发送带或不带令牌的测试请求。

克隆教程存储库

  • 在您的主目录中,创建 microservices-march 目录并将 GitHub 存储库克隆到其中。 (您还可以使用不同的目录名称并相应地调整说明。)存储库包含配置文件和使用不同方法获取机密的 API 客户端应用程序的单独版本.

    mkdir ~/微服务-march
    cd ~/微服务-march
    git 克隆 https://github.com/microservices-march/auth.git

  • 显示秘密。它是一个签名的 JWT,通常用于对服务器的 API 客户端进行身份验证。

    cat ~/microservices-march/auth/apiclient/token1.jwt
    “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCISImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2Nz UyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNlIiwic3ViIjoiYXBpS2V5MSJ9._ 6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA”

  • 虽然有多种方法可以使用此令牌进行身份验证,但在本教程中,API 客户端应用程序使用 OAuth 2.0 不记名令牌授权框架将其传递到身份验证服务器。这涉及到在 JWT 上添加 Authorization: Bearer 前缀,如下例所示:

    “授权:持有者eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCISImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNliiwic3ViIjoiYXBpS2 V5MSJ9._6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA”

    构建并启动身份验证服务器

  • 更改到身份验证服务器目录:

    cd api服务器

  • 为身份验证服务器构建 Docker 映像(注意最后的句点):

    docker build -t apiserver 。

  • 启动身份验证服务器并确认其正在运行(为了便于阅读,输出分布在多行中):

    docker run -d -p 80:80 apiserver
    码头工人
    容器 ID 图像命令 …
    2b001f77c5cb apiserver“nginx -g ‘守护进程…”…

    …创建状态…
    … 26 秒前 向上 26 秒 …

    … 端口 …
    … 0.0.0.0:80->80/tcp, :::80->80/tcp, 443/tcp …

    … 姓名
    … 放松_proskuriakova

  • 测试身份验证服务器

  • 验证身份验证服务器是否拒绝不包含以下内容的请求JWT,返回 401 需要授权:

    卷曲 -X GET http://localhost

    需要401授权

    需要401授权


    nginx/1.23.3

  • 使用授权标头提供 JWT。 200 OK 返回代码表示 API 客户端应用已成功通过身份验证。

    curl -i -X GET -H“授权:承载 `cat $HOME/microservices-march/auth/apiclient/token1.jwt`” http://localhost
    HTTP/1.1 200 好
    服务器:nginx/1.23.2
    日期:日,DD 星期一 YYYY hh:mm:ss TZ
    内容类型:text/html
    内容长度:64
    最后修改时间:日,DD 星期一 YYYY hh:mm:ss TZ
    连接:保持活动状态
    ETag:“63dc0fcd-40”
    X-MESSAGE:成功 apiKey1
    接受范围:字节

    { “response”: “成功”, “授权”: true, “value”: “999” }

  • 挑战 1:在您的应用中硬编码秘密(不是!)

    在开始本章节之前请澄清一下:将秘密硬编码到您的应用程序中是一个糟糕的主意!您将看到有权访问容器映像的任何人如何轻松查找和提取硬编码凭据。

    在此挑战中,您将 API 客户端应用程序的代码复制到构建目录中,构建并运行应用程序,然后提取密钥。

    复制 API 客户端应用

    apiclient 目录的 app_versions 子目录包含针对四个挑战的简单 API 客户端应用程序的不同版本,每个版本都比前一个版本稍微安全一些(有关详细信息,请参阅教程概述)。

  • 更改为 API 客户端目录:

    cd ~/microservices-march/auth/apiclient

  • 将本次挑战的应用程序(带有硬编码秘密的应用程序)复制到工作目录:

    cp ./app_versions/very_bad_hard_code.py ./app.py

  • 看看该应用程序:

    猫应用程序.py
    导入 urllib.request
    导入 urllib.error

    jwt =“哎呀JhbGciOiJIUZI1NiIsInR5cCI6IkpXVCISImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNliiwic3ViIjoiYXBpS2V5MSJ9._6L _Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA”
    authstring =“承载者”+ jwt
    req = urllib.request.Request(“http://host.docker.internal”)
    req.add_header(“授权”, authstring)
    尝试:
    以 urllib.request.urlopen(req) 作为响应:
    the_page = 响应.read()
    message = response.getheader(“X-MESSAGE”)
    打印(“200”+消息)
    除了 urllib.error.URLError 为 e:
    print(str(e.code) + ” s ” + e.msg)

    该代码只是向本地主机发出请求并打印出成功消息或失败代码。

    请求在此行添加授权标头:

    req.add_header(“授权”, authstring)

    你还注意到什么吗?也许是硬编码的 JWT?我们稍后会讨论这个问题。首先让我们构建并运行应用程序。

  • 构建并运行 API 客户端应用

    我们正在使用将 docker compose 命令与 Docker Compose YAML 文件一起使用 – 这使得更容易理解正在发生的事情。

    (请注意,在上一节的步骤 2 中,您将特定于挑战 1 的 API 客户端应用的 Python 文件 (very_bad_hard_code.py) 重命名为 app.py。您也将在其他三个挑战中执行此操作。每次使用 app.py 都可以简化后勤工作,因为您不需要更改 Dockerfile。这确实意味着您需要在 docker compose 命令中包含 ‑build 参数以强制每次重建容器。)

    docker compose 命令构建容器,启动应用程序,发出单个 API 请求,然后关闭容器,同时在控制台上显示 API 调用的结果。

    输出倒数第二行的 200 成功代码表示身份验证成功。 apiKey1 值是进一步确认,因为它显示了 auth 服务器能够解码 JWT 中该名称的声明:

    docker compose -f docker-compose.hardcode.yml up -build

    apiclient-apiclient-1 | apiclient-apiclient-1 | 200 成功 apiKey1
    apiclient-apiclient-1 退出,代码为 0

    因此,硬编码凭据适用于我们的 API 客户端应用 – 这并不奇怪。但它安全吗?也许是这样,因为容器在退出之前只运行此脚本一次并且没有 shell?

    事实上 – 不,一点也不安全。

    从容器镜像中检索 Secret

    硬编码凭据使任何可以访问容器映像的人都可以检查它们,因为提取容器的文件系统是一项微不足道的工作。

  • 创建解压目录并更改为:

    mkdir提取
    光盘摘录

  • 列出容器镜像的基本信息。 –format 标志使输出更具可读性(出于相同的目的,输出分布在两行中)儿子):

    docker ps -a –format “表 {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}”
    容器 ID 名称 图像 …
    11b73106fdf8 apiclient-apiclient-1 apiclient …
    ad9bdc05b07c 令人兴奋的_clarke apiserver …

    …创建状态
    … 6 分钟前 退出 (0) 4 分钟前
    … 43 分钟前 43 分钟

  • 将最新的 apiclient 映像提取为 .tar 文件。对于 ,替换上面输出中 CONTAINER ID 字段中的值(本教程中为 11b73106fdf8):

    docker export -o api.tar

    创建 api.tar 存档需要几秒钟的时间,其中包括容器的整个文件系统。查找秘密的一种方法是提取整个存档并解析它,但事实证明,有一个快捷方式可以查找可能有趣的内容 – 使用 docker History 命令显示容器的历史记录。 (这个短ut 特别方便,因为它也适用于您在 Docker Hub 或其他容器注册表上找到的容器,因此可能没有 Dockerfile,而只有容器映像。

  • 显示容器的历史记录:

    docker 历史 apiclient
    图像已创建…
    9396dde2aad0 8 分钟前 …
    8 分钟前…
    28分钟前…

    … 由尺寸创建 …
    … CMD [“python”“./app.py”] 622B …
    … 复制 .​​/app.py ./app.py # buildkit 0B …
    … WORKDIR /usr/app/src 0B …

    … 评论
    … buildkit.dockerfile.v0
    … buildkit.dockerfile.v0
    … buildkit.dockerfile.v0

    输出行按时间倒序排列。它们显示工作目录设置为 /usr/app/src,然后复制该应用程序的 Python 代码文件并运行。不需要g侦探推断出该容器的核心代码库位于 /usr/app/src/app.py 中,因此这可能是凭证的位置。

  • 有了这些知识,就可以提取该文件:

    tar –extract –file=api.tar usr/app/src/app.py

  • 显示文件的内容,就像这样,我们已经获得了对“安全”JWT 的访问权限:

    猫 usr/app/src/app.py

    jwt=”eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCISImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA4MTMsImlzcyI6ImFwaUtleTEiLCJhdWQiOiJhcGlTZXJ2aWNlIiwic3ViIjoiYXBpS2V5MSJ9 ._6L_Ff29p9AWHLLZ-jEZdihy-H1glooSq_z162VKghA”

  • 挑战 2:将机密作为环境变量传递(再次强调,不!)

    如果您已完成 2023 年 3 月微服务的第 1 单元(将十二因子应用应用于微服务架构),您会熟悉如何使用环境变量将配置数据传递到容器。如果您错过了,也不必担心——之后即可按需获取你注册。

    在此挑战中,您将机密作为环境变量传递。与挑战 1 中的方法一样,我们不推荐这种方法!它不像硬编码秘密那么糟糕,但正如您所看到的,它有一些弱点。

    有四种方法可以将环境变量传递给容器:

    • 使用 Dockerfile 中的 ENV 语句进行变量替换(为所有构建的镜像设置变量)。例如:

      环境端口 $PORT

    • 在 docker run 命令上使用 ‑e 标志。例如:

      docker run -e PASSWORD=123 mycontainer

    • 在 Docker Compose YAML 文件中使用环境密钥。
    • 使用包含变量的 .env 文件。

    在此挑战中,您使用环境变量来设置 JWT 并检查容器以查看 JWT 是否公开。

    传递环境变量

  • 改回 API 客户端目录:

    cd ~/microservices-march/auth/apiclient

  • 将本次挑战赛的应用(使用环境变量的应用程序)复制到工作目录,覆盖挑战 1 中的 app.py 文件:

    cp ./app_versions/medium_environment_variables.py ./app.py

  • 看看该应用程序。在相关的输出行中,秘密(JWT)被读取为本地容器​​中的环境变量:

    猫应用程序.py

    杰沃特=“”
    如果 os.environ 中有“JWT”:
    jwt = “承载者” + os.environ.get(“JWT”)

  • 如上所述,可以选择多种方法将环境变量放入容器中。为了保持一致性,我们坚持使用 Docker Compose。显示 Docker Compose YAML 文件的内容,该文件使用环境密钥设置 JWT 环境变量:

    猫 docker-compose.env.yml

    版本:“3.9”
    服务:
    API客户端:
    建造: 。
    图片:api客户端
    额外主机:
    – “host.docker.internal:主机网关”
    环境:
    – 智威汤逊

  • 运行应用程序无需设置环境变量。输出倒数第二行的 401 Unauthorized 代码确认身份验证失败,因为 API 客户端应用未通过 JWT:

    docker compose -f docker-compose.env.yml up -build

    apiclient-apiclient-1 | apiclient-apiclient-1 | 401 未经授权
    apiclient-apiclient-1 退出,代码为 0

  • 为了简单起见,在本地设置环境变量。在本教程的这一点上这样做就可以了,因为现在关注的不是安全问题:

    导出 JWT=`cat token1.jwt`

  • 再次运行容器。现在测试成功,并显示与挑战 1 中相同的消息:

    docker compose -f docker-compose.env.yml up -build

    apiclient-apiclient-1 | apiclient-apiclient-1 | 200 成功 apiKey1
    apiclient-apiclient-1 退出,代码为 0

  • 所以至少现在基础镜像不包含秘密,我们可以在运行时传递它,这更安全。但仍然存在一个问题。

    检查 C容器

  • 显示有关容器映像的信息,以获取 API 客户端应用程序的容器 ID(为了便于阅读,输出分布在两行中):

    docker ps -a –format “表 {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}”
    容器 ID 名称 图像 …
    6b20c75830df apiclient-apiclient-1 apiclient …
    ad9bdc05b07c 令人兴奋的_clarke apiserver …

    …创建状态
    … 6 分钟前 退出 (0) 6 分钟前
    … 大约一个小时前 大约一个小时

  • 检查 API 客户端应用程序的容器。对于 ,请替换上面输出中 CONTAINER ID 字段中的值(此处为 6b20c75830df)。

    docker检查命令允许您检查所有已启动的容器,无论它们当前是否正在运行。这就是问题所在——即使容器没有运行,输出也会暴露 Env 数组中的 JWT,这是不安全的y 保存在容器配置中。

    docker 检查

    “环境”:[
    “JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCISImtpZCI6InNpZ24ifQ.eyJpYXQiOjE2NzUyMDA…”,
    “PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin”,
    “LANG=C.UTF-8”,
    “GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D”,
    “PYTHON_VERSION=3.11.2”,
    “PYTHON_PIP_VERSION=22.3.1”,
    “PYTHON_SETUPTOOLS_VERSION=65.5.1”,
    “PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/1a96dc5acd0303c4700e026…”,
    “PYTHON_GET_PIP_SHA256=d1d09b0f9e745610657a528689ba3ea44a73bd19c60f4c954271b790c…”
    ]

  • 挑战 3:使用本地机密

    到目前为止,您已经了解到硬编码机密和使用环境变量并不像您(或您的安全团队)需要的那么安全。

    为了提高安全性,您可以尝试使用本地 Docker 密钥来存储敏感信息。再说一遍,这不是黄金标准方法,但了解它的工作原理是有好处的。前夕如果您不在生产中使用 Docker,那么重要的一点是如何使从容器中提取秘密变得困难。

    在 Docker 中,秘密通过文件系统挂载 /run/secrets/ 暴露给容器,其中有一个单独的文件包含每个秘密的值。

    在此挑战中,您使用 Docker Compose 将本地存储的机密传递到容器,然后验证使用此方法时该机密在容器中不可见。

    将本地存储的秘密传递到容器

  • 正如您现在所期望的,您首先要更改到 apiclient 目录:

    cd ~/microservices-march/auth/apiclient

  • 将本次挑战赛的应用(使用容器内机密的应用程序)复制到工作目录,覆盖挑战 2 中的 app.py 文件:

    cp ./app_versions/better_secrets.py ./app.py

  • 看一下 Python 代码,其中显示:e 来自 /run/secrets/jot 文件的 JWT 值。 (是的,我们可能应该检查该文件是否只有一行。也许在 2024 年 3 月的微服务中?)

    猫应用程序.py

    jotfile =“/运行/秘密/jot”
    杰沃特=“”
    如果 os.path.isfile(jotfile):
    将 open(jotfile) 作为 jwtfile:
    对于 jwtfile 中的行:
    jwt =“承载”+行

    好的,那么我们如何创建这个秘密呢?答案就在 docker-compose.secrets.yml 文件中。

  • 看一下 Docker Compose 文件,其中 Secrets 部分定义了 Secret 文件,然后由 apiclient 服务引用:

    猫 docker-compose.secrets.yml

    版本:“3.9”
    秘密:
    记下:
    文件:token1.jwt
    服务:
    API客户端:
    建造: 。
    额外主机:
    – “host.docker.internal:主机网关”
    秘密:
    – 记下

  • 验证秘密在容器中不可见

  • 运行应用程序。因为我们已经使 JWT 在容器内可访问,身份验证成功并显示现在熟悉的消息:

    docker compose -f docker-compose.secrets.yml up -build

    apiclient-apiclient-1 | apiclient-apiclient-1 | 200 成功 apiKey1
    apiclient-apiclient-1 退出,代码为 0

  • 显示有关容器映像的信息,并记下 API 客户端应用的容器 ID(有关示例输出,请参阅检查挑战 2 中的容器中的步骤 1):

    docker ps -a –format “表 {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}”

  • 检查 API 客户端应用程序的容器。对于 ,替换上一步输出中 CONTAINER ID 字段的值。与检查容器的步骤 2 中的输出不同,Env 部分的开头没有 JWT= 行:

    docker 检查
    “环境”:[
    “PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin”,
    “LANG=C.UTF-8″,
    “GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D”,
    “PYTHON_VERSION=3.11.2”,
    “PYTHON_PIP_VERSION=22.3.1”,
    “PYTHON_SETUPTOOLS_VERSION=65.5.1”,
    “PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/1a96dc5acd0303c4700e026…”,
    “PYTHON_GET_PIP_SHA256=d1d09b0f9e745610657a528689ba3ea44a73bd19c60f4c954271b790c…”
    ]

    到目前为止,一切顺利,但我们的秘密位于 /run/secrets/jot 的容器文件系统中。也许我们可以使用与挑战 1 中的从容器映像中检索秘密相同的方法从那里提取它。

  • 更改为提取目录(您在挑战 1 期间创建的目录)并将容器导出到 tar 存档中:

    光盘摘录
    docker export -o api2.tar

  • 在 tar 文件中查找机密文件:

    tar tvf api2.tar | tvf api2.tar | grep 记事本
    -rwxr-xr-x 0 0 0 0 周一 DD hh:mm run/secrets/jot

    呃哦,包含 JWT 的文件可见。我们不是说在容器中嵌入秘密是“安全的”吗?事情是否像挑战 1 中一样糟糕?

  • 让我们看看 – 从 tar 文件中提取机密文件并查看其内容:

    tar –extract –file=api2.tar run/secrets/jot
    猫跑/秘密/jot

    好消息! cat 命令没有输出,这意味着容器文件系统中的 run/secrets/jot 文件是空的——里面没有秘密!即使我们的容器中存在秘密工件,Docker 也足够聪明,不会在容器中存储任何敏感数据。

  • 也就是说,尽管此容器配置是安全的,但它有一个缺点。这取决于运行容器时本地文件系统中是否存在名为 token1.jwt 的文件。如果重命名该文件,尝试重新启动容器将失败。 (您可以通过重命名 [不删除!] token1.jwt 并再次运行步骤 1 中的 docker compose 命令来自行尝试。)

    所以我们已经成功了一半:容器使用秘密的方式可以保护它们免受轻易泄露,但秘密是在主机上仍然不受保护。您不希望秘密以未加密的方式存储在纯文本文件中。是时候引入秘密管理工具了。

    挑战 4:使用 Secrets Manager

    机密管理器可帮助您在机密的整个生命周期中管理、检索和轮换机密。有很多秘密管理器可供选择,它们都实现相似的目的:

    • 安全地存储机密
    • 控制访问
    • 在运行时分发它们
    • 启用秘密轮换

    您的秘密管理选项包括:

    • 云提供商都拥有机密服务(例如 AWS Secrets Manager、Google Cloud Platform 的 Secret Manager 和 Microsoft Azure 的 Key Vault)
    • Kubernetes 有 Secret 对象
    • Hashicorp Vault 是一款流行的跨平台机密管理器
    • OpenShift 具有机密管理服务
    • Docker Swarm 有一个秘密服务

    为了简单起见,本次挑战使用 Docker Swarm,但对于许多秘密管理器来说,原理是相同的。

    在此挑战中,您在 Docker 中创建一个密钥,复制该密钥和 API 客户端代码,部署容器,看看是否可以提取该密钥,然后轮换该密钥。

    配置 Docker Secret

  • 按照现在的传统,切换到 apiclient 目录:

    cd ~/microservices-march/auth/apiclient

  • 初始化 Docker Swarm:

    docker集群初始化
    Swarm 初始化:当前节点 (t0o4eix09qpxf4ma1rrs9omrm) 现在是管理器。

  • 创建一个秘密并将其存储在 token1.jwt 中:

    docker 秘密创建 jot ./token1.jwt
    qe26h73nhb35bak5fr5east27

  • 显示有关秘密的信息。请注意,秘密值(JWT)本身并未显示:

    docker 秘密检查点
    [
    {
    “ID”:“qe26h73nhb35bak5fr5east27”,
    “版本”: {
    “索引”:11
    },
    “创建时间”: “YYYY-MM-DDThh:mm:ss.msZ”,”更新时间”: “YYYY-MM-DDThh:mm:ss.msZ”,
    “规格”:{
    “名称”:“记号”,
    “标签”: {}
    }
    }
    ]

  • 使用 Docker 密钥

    在 API 客户端应用程序代码中使用 Docker 密钥与使用本地创建的密钥完全相同 – 您可以从 /run/secrets/ 文件系统读取它。您所需要做的就是更改 Docker Compose YAML 文件中的秘密限定符。

  • 查看 Docker Compose YAML 文件。请注意外部字段中的 true 值,表明我们正在使用 Docker Swarm 密钥:

    猫 docker-compose.secretmgr.yml

    版本:“3.9”
    秘密:
    记下:
    外部:真实
    服务:
    API客户端:
    建造: 。
    图片:api客户端
    额外主机:
    – “host.docker.internal:主机网关”
    秘密:
    – 记下

    因此,我们可以期望此 Compose 文件能够与我们现有的 API 客户端应用程序代码配合使用。嗯,差不多了。虽然 Docker Swarm(或任何其他容器编排平台)带来了 l虽然没有额外的价值,但它确实带来了一些额外的复杂性。

    由于 docker compose 无法使用外部机密,因此我们将不得不使用一些 Docker Swarm 命令,特别是 docker stack deploy。 Docker Stack 隐藏了控制台输出,因此我们必须将输出写入日志,然后检查日志。

    为了让事情变得更简单,我们还使用连续的 while True 循环来保持容器运行。

  • 将本次挑战赛的应用(使用机密管理器的应用程序)复制到工作目录,覆盖挑战 3 中的 app.py 文件。显示 app.py 的内容,我们看到代码几乎与挑战 3 的代码。唯一的区别是添加了 while True 循环:

    cp ./app_versions/best_secretmgr.py ./app.py
    猫./app.py

    而真实:
    时间.睡眠(5)
    尝试:
    以 urllib.request.urlopen(req) 作为响应:
    the_page = 响应.read()message = response.getheader(“X-MESSAGE”)
    打印(“200”+消息,文件=sys.stderr)
    除了 urllib.error.URLError 为 e:
    print(str(e.code) + ” ” + e.msg, file=sys.stderr)

  • 部署容器并检查日志

  • 构建容器(在之前的挑战中 Docker Compose 已经解决了这个问题):

    docker build -t apiclient 。

  • 部署容器:

    docker stack deploy –compose-file docker-compose.secretmgr.yml Secretstack
    创建网络secretstack_default
    创建服务secretstack_apiclient

  • 列出正在运行的容器,记下 Secretstack_apiclient 的容器 ID(与之前一样,输出分布在多行中以提高可读性)。

    docker ps –format “表 {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}”
    集装箱编号…
    20d0c83a8b86 …
    ad9bdc05b07c …

    … 姓名 …
    …secretstack_apiclient.1.0e9s4mag5tadvxs6op6lk8vmo …
    …令人兴奋的_克拉克…

    … 图像创建状态
    … apiclient:最新 31 秒前 最多 30 秒
    … apiserver 2 小时前 Up 2 小时

  • 显示Docker日志文件;对于 ,替换上一步输出中 CONTAINER ID 字段的值(此处为 20d0c83a8b86)。日志文件显示了一系列成功消息,因为我们在应用程序代码中添加了 while True 循环。按 Ctrl+c 退出命令。

    docker log -f
    200 成功 apiKey1
    200 成功 apiKey1
    200 成功 apiKey1
    200 成功 apiKey1
    200 成功 apiKey1
    200 成功 apiKey1

    ^c

  • 尝试访问秘密

    我们知道没有设置敏感环境变量(但您始终可以使用 docker inform 命令进行检查,如检查 Conta 的步骤 2 中所示)挑战 2 中的惰性)。

    从挑战 3 中我们还知道 /run/secrets/jot 文件为空,但您可以检查:

    光盘摘录
    docker 导出 -o api3.tar
    tar –extract –file=api3.tar run/secrets/jot
    猫跑/秘密/jot

    成功!您无法从容器中获取机密,也无法直接从 Docker 中读取机密。

    旋转秘密

    当然,使用正确的权限,我们可以创建一个服务并将其配置为将机密读取到日志中或将其设置为环境变量。此外,您可能已经注意到我们的 API 客户端和服务器之间的通信未加密(纯文本)。

    因此,几乎任何秘密管理系统仍然有可能泄露秘密。限制造成损害的可能性的一种方法是定期轮换(更换)秘密。

    使用 Docker Swarm,您只能删除然后重新创建机密(Kubernetes 允许动态更新机密)。您也无法删除秘密 atta准备运行服务。

  • 列出正在运行的服务:

    docker 服务 ls
    ID 名称模式…
    sl4mvv48vgjz Secretstack_apiclient 已复制…

    … 复制图像端口
    … 1/1 apiclient:最新

  • 删除secretstack_apiclient服务。

    docker 服务 rm Secretstack_apiclient

  • 删除密钥并使用新令牌重新创建它:

    docker 秘密 rm 点
    docker 秘密创建 jot ./token2.jwt

  • 重新创建服务:

    docker stack deploy –compose-file docker-compose.secretmgr.yml Secretstack

  • 查找 apiclient 的容器 ID(有关示例输出,请参阅部署容器并检查日志中的步骤 3):

    docker ps –format “表 {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}”

  • 显示 Docker 日志文件,其中显示一系列成功消息。对于 ,替换值来自上一步输出中的 CONTAINER ID 字段。按 Ctrl+c 退出命令。

    docker log -f
    第200章 成功
    第200章 成功
    第200章 成功
    第200章 成功

    ^c

  • 看到从 apiKey1 到 apiKey2 的变化了吗?您已经轮换了秘密。

    在本教程中,API 服务器仍然接受这两个 JWT,但在生产环境中,您可以通过要求 JWT 中声明的某些值或检查 JWT 的过期日期来弃用旧的 JWT。

    另请注意,如果您使用的机密系统允许更新您的机密,则您的代码需要经常重新读取该机密,以便获取新的机密值。

    清理

    要清理您在本教程中创建的对象:

  • 删除secretstack_apiclient服务。

    docker 服务 rm Secretstack_apiclient

  • 删除秘密。

    docker 秘密 rm 点

  • 离开群体(假设你创建了一个群体仅适用于本教程)。

    docker swarm 离开 –force

  • 终止正在运行的 apiserver 容器。

    docker ps -a | docker ps -a | grep“apiserver”| awk {‘print $1’} |xargs docker Kill

  • 通过列出然后删除不需要的容器来删除它们。

    docker ps -a –format “表 {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.RunningFor}}\t{{.Status}}”
    docker rm

  • 通过列出并删除任何不需要的容器映像来删除它们。

    docker 镜像列表
    docker image rm

  • 后续步骤

    您可以使用此博客在您自己的环境中实施本教程,或在我们基于浏览器的实验室中尝试(在此处注册)。要了解有关公开 Kubernetes 服务主题的更多信息,请按照第 2 单元:微服务秘密管理 101 中的其他活动进行操作。

    要了解有关使用 NGINX Plus 进行生产级 JWT 身份验证的更多信息,请查看我们的文档并阅读使用 JWT 对 API 客户端进行身份验证以及我们博客上的 NGINX Plus。


    评论

    发表回复

    您的电子邮箱地址不会被公开。 必填项已用 * 标注