GitOps and Kubernetes Continuous Deployment with Argo CD, Jenkins X, and Flux

1、k8s secrets

1.1、Secrets 的使用

Kubernetes Secrets 与 ConfigMap 一样,可以通过多种方式使用:

  • 作为 Pod 中挂载的文件

  • 作为 Pod 中的环境变量

  • Kubernetes API 访问

(1)作为 Pod 中挂载的文件

利用 Secrets 的第一个技术是将它们作为 volume 安装到 Pod 中。

当将 Secret(或 ConfigMap)作为文件卷投影到 Pod 中时,对底层 Secret 的更改最终将更新 Pod 中挂载的文件。这使得应用程序有机会重新配置自身或热重载,而无需重新启动容器/Pod。

(2)作为 Pod 中的环境变量

利用 Kubernetes Secrets 的第二种方法是将它们设置为环境变量。

将 Secrets 作为环境变量暴露给容器虽然方便,但可以说不是使用 Secrets 的最佳方式,因为它比将它们作为卷使用安全性较低。当 Secret 设置为环境变量时,容器中的所有进程(包括子进程)都将继承操作系统环境,并能够读取环境变量值,从而读取 Secret 数据。

使用 Secret 作为环境变量的第二个缺点是,与投影到卷中的 Secret 不同,如果 Secret 在容器启动后更新,则 Secret 环境变量的值将不会更新。需要重新启动容器或 Pod 才能注意到更改。

(3)Kubernetes API 访问

最后,还可以直接从 Kubernetes API 检索 Kubernetes Secret。假设您有以下带有密码字段的 Secret。

要检索 Secret,Pod 本身可以直接从 Kubernetes 检索 Secret 值,例如,通过使用 kubectl 命令或 REST API 调用。以下 kubectl 命令检索名为 my-secret 的 Secret,对密码字段进行 Base64 解码,并将纯文本值打印到标准输出:

kubectl get secret my-secret -o=jsonpath='{.data.password}' | base64 --decode 

P@55w0rd

1.2、secret 类型

类型

描述

Required fields

Opaque

默认类型。包含任意用户定义的数据。

kubernetes.io/service-accounttoken

包含一个令牌,用于向 Kubernetes API 标识服务帐户。

data["token"]

kubernetes.io/dockercfg

包含序列化的 ~/.dockercfg 文件。

data[".dockercfg"]

kubernetes.io/dockerconfigjson

包含序列化的 ~/.docker/ config.json 文件。

data[".dockerconfigjson"]

kubernetes.io/basic-auth

包含基本的用户名/密码凭据。

data["username"] data["password"]

kubernetes.io/ssh-auth

包含身份验证所需的 SSH 私钥。

data["ssh-privatekey"]

kubernetes.io/tls

包含 TLS 私钥和证书。

data["tls.key"] data["tls.crt"]

2、GitOps and Secrets

2.1、Git 不适合存储 secrets

虽然用户非常习惯在 Git 中存储配置,但当涉及到敏感数据时,出于安全考虑,他们不愿意将数据存储在 Git 中。 Git 被设计为一种协作工具,使多个人员和团队可以轻松访问代码并查看彼此的更改。但这些相同的特性也使得使用 Git 来保存 Secrets 成为一种极其危险的做法。在 Git 中存储 Secrets 是不合适的,有很多原因:

  • 无加密

正如我们之前了解到的,Kubernetes 不对 Secret 的内容提供加密,并且值的 Base64 编码应被视为与纯文本相同。此外,Git 本身并不提供任何形式的内置加密。因此,当将 Secrets 存储在 Git 存储库中时,这些 Secrets 就会向任何有权访问 Git 存储库的人公开。

  • 分布式 Git 存储库

使用 GitOps,您和您的同事将在本地将 Git 存储库克隆到您的笔记本电脑和工作站,以管理应用程序的配置。但如果这样做,您也会在没有充分审计或跟踪的情况下将 Secrets 扩散并分发到许多系统。如果这些系统中的任何一个受到损害(被黑客攻击甚至物理丢失),有人将获得您所有秘密的访问权限。

  • 没有细粒度(文件级)访问控制

Git 不提供对 Git 存储库的子路径或子文件的读保护。换句话说,不可能限制对 Git 存储库中某些文件的访问,但不能限制对其他文件的访问。在处理 Secret 时,理想情况下,您应该在需要知道的基础上授予对 Secret 的读取权限。例如,如果您有一名临时工作人员需要对 Git 存储库进行部分访问,您可能希望向该用户提供尽可能少的内容访问权限。不幸的是,Git 没有提供任何工具来完成此操作,并且在向存储库授予权限时这是一个全有或全无的决定。

  • 不安全的存储

Git 从未打算用作 Secret 管理系统。因此,它没有设计到系统标准安全功能中,例如静态加密。因此,受损的 Git 服务器也有可能泄露其管理的所有存储库的秘密,从而使其成为攻击的主要目标。

  • 完整的提交历史记录

一旦 Secret 添加到 Git 提交历史记录中,就很难删除。如果 Secret 已签入 Git,然后被删除,则仍然可以通过在删除 Secret 之前查看存储库历史记录中的较早点来检索该 Secret。

2.2、Secrets 管理策略

在 GitOps 中处理 Secrets 有许多不同的策略,需要在灵活性、可管理性和安全性之间进行权衡。在应用工具来实施这些策略之前,我们将首先在概念层面上回顾一些策略。

2.2.1、在 Git 中存储 Secrets

GitOps 和 Secrets 的第一个策略是根本没有策略。换句话说,您只需像任何其他 Kubernetes 资源一样在 Git 中提交和管理您的 Secret,并接受安全后果。

在实践中,Secrets 可以按原样存储在 Git 中的唯一真正可接受的场景是 Secrets 不包含任何真正敏感的数据,例如开发和测试环境。

2.2.2、将 Secrets 拷贝到容器镜像中

为了避免将 Secret 存储在 Git 中,可能会想到的一种天真的策略是将敏感数据直接烘焙到容器映像中。在这种方法中,作为 Docker 构建过程的一部分,Secret 数据会直接复制到容器映像中。

将 Secrets 烘焙到镜像中的简单 Dockerfile 可能如下所示。

FROM scratch 
COPY ./my-app /my-app 
COPY ./credentials.txt /credentials.txt 

ENTRYPOINT [“/my-app”]

这种方法的一个优点是它消除了 Git 甚至 Kubernetes 本身。事实上,通过将 Secret 数据烘焙到容器镜像中,该镜像可以在任何地方运行,而不仅仅是 Kubernetes,并且无需任何配置即可运行。

然而,将敏感数据直接烘焙到容器映像中存在一些非常糟糕的缺点,这应该自动排除它作为可行的选择。第一个问题是容器镜像本身现在很敏感。由于敏感数据已烘焙到映像中,因此有权访问容器映像(例如通过 docker pull)的任何人或任何东西现在都可以轻松复制并检索秘密。

另一个问题是,由于 Secret 被烘焙到镜像中,因此 Secret 数据的更新极其繁重。每当需要轮换凭证时,都需要完全重建容器映像。

第三个问题是,容器镜像不够灵活,无法适应同一镜像需要使用不同 Secret 数据集运行的情况。假设您有三个将部署此容器映像的环境:开发、测试、生产。

这些环境中的每一个都需要一组不同的凭据,因为它连接到三个不同的数据库。将 Secret 数据烘焙到容器镜像中的方法在这里不起作用,因为它只能选择其中一个数据库凭证来烘焙到镜像中。

2.2.3、带外管理

通过这种方法,除了 Kubernetes Secrets 之外的所有内容都将在 Git 中定义并通过 GitOps 进行部署,但将使用其他一些机制来部署 Secrets。

例如,用户可以将其 Secret 存储在数据库、云提供商托管的 Secret 存储中,甚至本地工作站上的文本文件中。当需要部署时,用户将手动运行 kubectl apply 将 Secret 部署到集群中,然后让 GitOps operator 部署其他所有内容。

这种方法的明显缺点是,您需要两种不同的机制来将资源部署到集群:一种用于通过 GitOps 的普通 Kubernetes 资源,另一种则严格用于 Secrets。

2.2.4、外部 Secrets 管理系统

在 GitOps 中处理 Secret 的另一种策略是使用 Kubernetes 之外的外部 Secret 管理系统。在此策略中,应用程序容器本身在运行时、使用时动态检索 Secret 值,而不是使用本机 Kubernetes 功能将 Secret 存储和加载到容器中。

存在多种 Secret 管理系统,但最流行和最广泛使用的一种是 HashiCorp Vault,这是我们在讨论外部秘密管理系统时将主要关注的工具。各个云提供商还提供自己的机密管理服务,例如 AWS Secrets Manager、Google Cloud Secret Manager 和 Microsoft Azure Key Vault。这些工具的功能和特性集可能有所不同,但一般原则是相同的,应该适用于所有人。

通过选择使用外部 Secret 管理系统(例如 Vault)来管理您的 Secret,您实际上也做出了不使用 Kubernetes Secret 的决定。这是因为使用此策略时,您依赖外部秘密管理系统来存储和检索您的秘密,而不是 Kubernetes。一个重要的后果是,您也无法利用 Kubernetes Secret 提供的一些便利,例如从 Secret 设置环境变量的值或将 Secret 映射为卷中的文件。

这种技术的另一个后果是,由于 Secrets 是在单独的数据库中管理的,因此您不会拥有与在 Git 中管理的配置相同的 Secrets 更改历史记录/记录。这甚至可能会影响您以可预测的方式回滚的能力。例如,在回滚期间,应用上一次 Git 提交时的清单可能还不够。您还必须在 Git 提交的同时将 Secret 回滚到之前的值。根据所使用的秘密商店的不同,这在最好的情况下可能会很不方便,或者在最坏的情况下完全不可能。

2.2.5、在 Git 中加密秘密

由于 Git 被认为存储纯文本 Secret 是不安全的,因此一种策略是对敏感数据进行加密,以便安全地存储在 Git 中,然后在更接近其使用点的地方解密数据。执行解密的参与者必须拥有必要的密钥来解密加密的 Secret。这可能是应用程序本身、填充应用程序使用的卷的初始化容器,或者是无缝处理应用程序这些任务的控制器。

在 Git 中加密 Secret 的挑战在于,仍然涉及到最后一个 Secret,那就是用于加密这些 Secret 的加密密钥。如果没有对加密密钥进行充分的保护,这种技术就毫无意义,因为任何有权访问加密密钥的人现在都可以解密并访问清单中的敏感数据。