Kubernetes-机密手册-全-
Kubernetes 机密手册(全)
原文:
annas-archive.org/md5/5cc672682018ad75f1f334c1efca7664译者:飞龙
序言
Kubernetes Secrets 管理是通过一系列实践和工具,帮助用户在 Kubernetes 集群内安全存储和管理敏感信息(如密码、令牌和证书)并确保其安全。保护 Secrets(如密码、API 密钥和其他敏感信息)对防止未经授权的访问、保护应用程序和数据至关重要。了解 Kubernetes Secrets 管理的开发人员可以帮助确保 Secrets 得到安全有效的管理,从而降低安全漏洞的风险。许多行业和监管框架对敏感数据的管理有具体要求。通过学习 Kubernetes Secrets 管理的实践,开发人员可以确保他们的应用程序遵守这些要求,从而避免潜在的法律或财务处罚。
本书适合谁阅读
本书适用于希望在 Kubernetes 上部署和管理 Secrets 的软件工程师、DevOps 工程师和系统管理员。具体来说,面向以下人员:
-
已经熟悉 Kubernetes 并希望理解如何有效管理 Secrets 的开发人员。这包括已经使用 Kubernetes 进行应用程序部署的人员,以及那些对 Kubernetes 平台感兴趣并希望进一步了解其能力的新手。
-
对于那些希望学习如何在 Kubernetes 环境中安全管理 Secrets 的安全专业人士来说,这本书也非常适用。这些人可能负责保护应用程序、基础设施或网络的安全,或者负责合规性和监管要求的人员。
-
任何有兴趣使用 Kubernetes 安全部署和管理应用程序,并希望理解如何在该环境中有效管理 Secrets 的人。
本书的内容
第一章,理解 Kubernetes Secrets 管理,介绍了 Kubernetes 及其在 Kubernetes 上部署应用程序时 Secrets 管理的重要性。它概述了管理 Secrets 的挑战和风险、目标以及本书的范围。
第二章,深入讲解 Kubernetes Secrets 管理概念,涵盖了 Kubernetes Secrets 管理的基础知识,包括不同类型的 Secrets、它们的使用场景、如何在 Kubernetes 中创建、修改和删除 Secrets,以及安全存储和访问控制。还讨论了如何通过 RBAC 和 Pod 安全标准安全访问 Secrets,以及如何审计和监控 Secrets 的使用情况。
第三章,以 Kubernetes 原生方式加密 Secrets,教你如何在传输和 etcd 中存储的 Secrets 加密,以及在 Kubernetes 中的密钥管理和轮换。
第四章,调试和故障排除 Kubernetes Secrets,提供了识别和解决管理 Kubernetes Secrets 时常见问题的指导。涵盖了调试和故障排除 Secrets 的最佳实践,包括使用监控和日志工具,确保基于 Kubernetes 的应用程序的安全性和可靠性。
第五章,安全性、审计与合规性,重点讨论了在 Kubernetes 中管理 Secrets 时合规性和安全性的重要性。涵盖了如何遵循安全标准和法规,减轻安全漏洞,并确保 Kubernetes Secrets 管理的安全性。
第六章,灾难恢复和备份,帮助你了解 Kubernetes Secrets 的灾难恢复和备份。还涵盖了备份策略和灾难恢复计划。
第七章,管理 Secrets 的挑战和风险,重点讨论了在混合云和多云环境中管理 Secrets 时的挑战和风险。还涵盖了缓解 Kubernetes Secrets 管理中的安全风险的策略、确保 Kubernetes Secrets 管理安全的指南以及可用于 Kubernetes Secrets 管理的工具和技术。
第八章,探索 AWS 上的云密钥存储,介绍了 AWS Secrets Manager 和 KMS 以及如何将它们与 Kubernetes 集成。还涵盖了使用 AWS CloudWatch 对 Kubernetes Secrets 进行监控和日志记录操作。
第九章,探索 Azure 上的云密钥存储,教你如何将 Kubernetes 与 Azure Key Vault 集成进行密钥存储,以及如何加密存储在 etcd 上的密钥。还涵盖了通过 Azure 的可观察性工具对 Kubernetes Secrets 进行监控和日志记录操作。
第十章,探索 GCP 上的云密钥存储,介绍了 GCP Secret Manager 和 GCP KMS 以及如何将它们与 Kubernetes 集成。还涵盖了使用 GCP 监控和日志对 Kubernetes Secrets 进行监控和日志记录操作。
第十一章,探索外部密钥存储,探讨了不同类型的第三方外部密钥存储,如 HashiCorp Vault 和 CyberArk Secrets Manager。教你如何使用外部密钥存储来存储敏感数据及其最佳实践。此外,本章还涵盖了使用外部密钥存储的安全影响,以及它们如何影响 Kubernetes 集群的整体安全性。
第十二章,与 Secret 存储的集成,教你如何将第三方 Secrets 管理工具与 Kubernetes 集成。它介绍了 Kubernetes 中的外部 Secret 存储及可以使用的不同类型的外部 Secret 存储。你还将了解使用外部 Secret 存储的安全影响,并学习如何通过多种方式(如初始化容器、边车容器、CSI 驱动程序、操作员和密封 Secrets)使用它们来存储敏感数据。本章还涵盖了使用外部 Secret 存储的最佳实践,以及它们如何影响 Kubernetes 集群的整体安全性。
第十三章,案例研究与实际应用示例,涵盖了 Kubernetes Secrets 在生产环境中的实际应用案例。它介绍了已经在 Kubernetes 中实施 Secrets 管理的组织案例研究,以及从实际部署中获得的经验教训。此外,你还将学习如何在 CI/CD 流水线中管理 Secrets,并将 Secrets 管理集成到 CI/CD 过程中。本章还介绍了用于在流水线中管理 Secrets 的 Kubernetes 工具和安全的 CI/CD Secrets 管理最佳实践。
第十四章,总结与 Kubernetes Secrets 管理的未来,概述了 Kubernetes Secrets 管理的当前状态以及未来的发展趋势。它还讨论了如何跟进 Kubernetes Secrets 管理的最新趋势和最佳实践。
为了从本书中获得最大的收获
你应该理解 Bash 脚本、容器化以及 Docker 的工作原理。你还应该理解 Kubernetes 和基本的安全概念。了解 Terraform 和云服务提供商的知识也将大有裨益。
| 本书涵盖的软件 | 操作系统要求 |
|---|---|
| Docker | Windows、macOS 或 Linux |
| Shell 脚本 | |
| Podman 和 Podman Desktop | |
| minikube | |
| Helm | |
| Terraform | |
| GCP | |
| Azure | |
| AWS | |
| OKD 和 Red Hat OpenShift | |
| StackRox 和 Red Hat 高级集群安全 | |
| Aqua 的 Trivy | |
| HashiCorp Vault |
如果你使用的是本书的数字版,我们建议你自己输入代码,或从本书的 GitHub 仓库访问代码(下一节会提供链接)。这样可以避免由于复制和粘贴代码而可能出现的错误。
下载示例代码文件
你可以从 GitHub 下载本书的示例代码文件,链接为github.com/PacktPublishing/Kubernetes-Secrets-Handbook。如果代码有更新,它将在 GitHub 仓库中同步更新。
我们还有其他来自丰富图书和视频目录的代码包,您可以在github.com/PacktPublishing/找到。快来看看吧!
使用的约定
本书中使用了多种文本约定。
文本中的代码:表示文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。举个例子:“kms提供程序插件将kube-apiserver与外部 KMS 连接,以利用封套加密原则。”
一段代码的设置如下:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aesgcm:
keys:
- name: key-20230616
secret: DlZbD9Vc9ADLjAxKBaWxoevlKdsMMIY68DxQZVabJM8=
- identity: {}
当我们希望将您的注意力集中在代码块的某个部分时,相关行或项会以粗体显示:
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::11111:role/eks-secret-reader"
name: service-token-reader
namespace: default
任何命令行输入或输出如下所示:
$ kubectl get events
...
11m Normal Pulled pod/webpage Container image "nginx:stable" already present on machin
粗体:表示新术语、重要词汇或屏幕上显示的词语。例如,菜单或对话框中的词语通常以粗体显示。举个例子:“GCP 提供的另一个显著工具是GKE 安全 姿态仪表板,用以提升 GKE 集群的安全性。”
提示或重要说明
显示如下。
与我们联系
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何部分有疑问,请通过电子邮件联系我们:customercare@packtpub.com,并在邮件主题中注明书名。
勘误:虽然我们已经尽一切努力确保内容的准确性,但错误有时仍会发生。如果您在本书中发现了错误,请向我们报告。请访问www.packtpub.com/support/errata并填写表格。
盗版:如果您在互联网上发现我们作品的任何非法复制品,欢迎您提供其地址或网站名称。请通过 copyright@packtpub.com 与我们联系,并附上该材料的链接。
如果您有兴趣成为作者:如果您在某个主题上拥有专长,并且有兴趣写书或为书籍做贡献,请访问authors.packtpub.com。
分享您的想法
阅读完《Kubernetes Secrets Handbook》后,我们希望听听您的想法!请点击这里直接前往亚马逊评论页面并分享您的反馈。
您的评论对我们和技术社区至关重要,将帮助我们确保提供高质量的内容。
下载本书的免费 PDF 副本
感谢您购买本书!
喜欢随时随地阅读,但又不能随身携带纸质书吗?
您的电子书购买是否与您的设备不兼容?
别担心,现在每本 Packt 图书都提供免费的无 DRM PDF 版本,您可以免费获取。
在任何地方、任何设备上随时阅读。直接从您最喜爱的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
福利不仅仅是这些,您还可以每天在邮箱中获得独家折扣、时事通讯和精彩的免费内容。
按照以下简单步骤获得福利:
- 扫描二维码或访问以下链接。
packt.link/free-ebook/9781805123224
-
提交您的购买凭证。
-
就这样!我们将直接把您的免费 PDF 和其他福利发送到您的电子邮件中。
第一部分:Kubernetes Secrets 管理简介。
本部分将为您提供 Kubernetes Secrets 的基础知识,并介绍其在管理 Kubernetes 上部署应用程序的敏感数据中的重要性。在本部分结束时,您将掌握 Kubernetes Secrets 的目的、功能和使用方法,并通过实际案例进行学习。
本部分包含以下章节:
-
第一章**,理解 Kubernetes Secrets 管理。
-
第二章**,深入了解 Kubernetes Secrets 管理概念。
-
第三章**,以 Kubernetes 原生方式加密 Secrets。
-
第四章**,调试和排除 Kubernetes Secrets 故障。
第一章:理解 Kubernetes Secrets 管理
本章将为您提供容器的复习内容,以及 Kubernetes 和其 Secrets 管理实现的全面概述。在这个第一部分的实践中,所有角色(开发人员、平台和安全工程师)都将了解如何设计和实现这些主题,并通过一系列实际示例加以说明。在这些示例中,我们将重点突出本书将解决的相应安全问题,涵盖一系列用例,最终为混合多云场景提供一个生产级的解决方案,包括业务连续性视角。
本章将涵盖以下主题:
-
理解 Kubernetes 的起源和设计原则
-
设置我们的第一个 Kubernetes 测试环境
-
探索 Kubernetes 的
Secret和ConfigMap对象 -
分析为什么 Kubernetes Secrets 很重要
-
揭示与 Kubernetes Secrets 管理相关的挑战和风险
-
本书目标和范围的映射
技术要求
为完成本章的实践部分,我们将利用一系列常用于与容器、Kubernetes 和 Secrets 管理交互的工具和平台。在本章中,我们将一起设置这个环境,并通过一个友好的桌面图形解决方案进行第一组示例的启动。别担心——我们为您准备了 Code in Action 和 GitHub 仓库,其中包含 macOS 安装示例。以下是所需工具的列表:
-
在用户级别使用
systemd来自动启动容器/Pods。 -
Podman Desktop (
podman-desktop.io) 是一款开源软件,提供图形用户界面用于构建、启动和调试容器,运行本地 Kubernetes 实例,简化从容器到 Pod 的迁移,甚至可以连接到如 Red Hat OpenShift、Azure Kubernetes Engine 等远程平台。 -
Golang (
go.dev) 或 Go 是我们示例中将使用的编程语言。请注意,Kubernetes 及其大多数第三方组件都是用 Go 编写的。 -
Git (
git-scm.com) 是我们将用来展示本书示例的版本控制系统,也将在我们探索 Secrets 管理解决方案时发挥作用。
本书的 GitHub 仓库包含与本书相关的数字资料:github.com/PacktPublishing/Kubernetes-Secrets-Handbook。
理解 Kubernetes 的起源和设计原则
尽管从一个平台到另一个平台的演变可能显而易见,但其中的关键事件和内部机制可能并不明显。为了在 Kubernetes 中安全地处理敏感数据,我们必须理解其历史和架构演变。这将帮助我们为关键业务应用程序实施一个安全的生产级环境。
接下来的几个章节将介绍一系列概念,探索并通过一个简单的容器运行时和 Kubernetes 集群进行实践,并建立这些概念与本手册将要解决的安全问题之间的直接联系。
重要说明
尽管我们希望你在阅读的同时进行实际操作示例,但我们理解你可能没有机会这样做。因此,我们为每个实际操作示例提供了简要说明和回顾。
从裸金属到容器
四十年前,应用程序的部署通常是在物理服务器上完成,这种方式通常被称为裸金属安装。这种方法使工作负载可以直接访问物理资源,提供最佳的原生性能。然而,由于从软件角度来看资源管理的局限性,在物理服务器上部署多个应用程序一直是一个运营挑战,这导致了一个次优的模型,根本原因如下:
-
物理资源利用率:在物理机器上部署较少的应用程序,以限制由于缺乏有效资源管理能力而导致的服务质量下降,这些能力本可以帮助解决某些应用程序占用所有计算资源的问题。
-
可扩展性、灵活性和市场响应时间:采购、安装、配置物理机器,并安装应用程序的时间通常需要几周甚至几个月,这会影响业务增长。
-
拥有总成本(TCO)与创新:物理服务器的采购、集成、运维和生命周期管理,加上由于高成本和长交付周期而导致的资源低利用和有限的原型开发,减缓了组织的创新能力。
然后,在 2000 年代初,虚拟化或虚拟机监控程序开始在商品化的开放系统中得到应用。虚拟机监控程序是一种集成到操作系统中的软件,安装在裸金属服务器上,允许 IT 部门创建虚拟机。通过这一技术,运维团队能够根据应用程序的具体需求创建和定制这些虚拟机,并在应用生命周期中根据业务需求调整计算资源。得益于合理的资源管理和隔离,多个虚拟机可以在单一服务器上运行,而不会因邻近虚拟机的影响导致潜在的服务降级。
这一模型提供了巨大的优化,帮助加速了服务的数字化,并引入了一个除了传统数据中心业务外的新市场——云计算。然而,虚拟化模型也带来了新的挑战:
-
虚拟机的数量由于持续创新而不断增加。这种资产的指数级增长加剧了维护和保障操作系统、库和应用程序的运营负担。
-
对于大规模复杂基础设施和安全组件进行日常创建、读取、更新和删除(CRUD)操作的自动化需求日益增加。
-
需要一个经过深思熟虑的治理框架来强制执行,以解决数千个服务的生命周期、安全性和业务连续性,从而支持组织关键应用程序的业务连续性。
最终,容器作为下一层优化方式崭露头角。尽管容器的构建并不新颖,与虚拟化一样,它需要一个重要的参与者在商品化的开放系统上进行投资,使其有机地成为下一次(r)革命。
我们可以把容器看作是一种轻量级的虚拟机,但不需要完整的操作系统,从而减少了与软件开发生命周期和安全管理相关的整体资源消耗和运营负担。相反,多个应用程序作为容器共享底层物理主机,无需虚拟化管理程序的开销,并且能够获得接近本机性能的好处。容器为您提供以下优点:
-
OCI(
opencontainers.org)提供了一个明确定义的标准,便于在任何符合 OCI 规范的平台上构建、(重新)分发和部署容器。 -
一种高度高效、可预测且不可变的介质,应用程序中心化,只包含必要的库和应用程序运行时
-
由于基础设施和平台无关的解决方案,实现了应用程序的可移植性
-
开发人员和平台工程师之间有机的关注点分离,因为不需要访问物理或虚拟主机操作系统就能开发、构建、测试和部署应用程序
-
采纳自动化优先的方法和 DevOps 实践,来处理基础设施、应用程序和安全管理
如果不提及一些挑战,那就是不对的,以下是一些挑战:
-
大多数 IT 组织在架构和管理方面都难以接受这一新范式
-
考虑到开发人员和平台工程师之间的有机关注点分离,以支持各自的孤岛结构
-
关于微服务的炒作过头了,这可能导致潜在的次优应用架构,既没有性能优化,又增加了复杂性
以下图示展示了自下而上的堆栈,显示了每台物理服务器上按各自部署类型划分的潜在应用密度:

图 1.1 – 裸机、虚拟机和容器之间的层次比较
我们已经列出了许多好处,但仍应强调一些额外的好处,这些好处有助于快速原型制作、更快部署、轻松的实时功能测试等:
-
每个微服务的代码基础较小,便于维护和扩展,同时支持更容易的上线/回滚操作
-
当其中一个微服务失败而其他微服务仍正常运行时,具备在降级模式下运行的能力
-
在不影响整个应用程序的情况下,能够故障排除行为不正常的微服务
-
故障恢复速度更快,因为只需要重新调度相关的微服务
-
细粒度计算资源分配和可扩展性
微服务不仅帮助解耦大型单体应用,还引入了新的设计模式以加速创新。
听起来很棒,不是吗?确实如此,但我们这里仍然缺少一个关键要素:像 Docker 或 Podman 这样的容器运行时在出现故障时并不提供任何容错能力。为了实现这一点,容器运行时需要一个额外的软件层,提供高可用性能力来支持应用程序。大规模管理数百个微服务需要一个强大且高度可靠的调度器,以确保应用程序的业务连续性,同时保证对底层基础设施的高度自动化和抽象。这将实现无摩擦的构建、部署和运行操作,改善 IT 员工日常处理部署在应用平台上的工作负载的责任。
这是一个大挑战,许多 IT 部门都在面对并尝试解决这个问题,尤其是在传统模式下。解决这个复杂问题的答案是 Kubernetes,一个容器平台,或者我们应该称之为应用平台。
Kubernetes 概述
没有比 Kubernetes 项目维护者的话更能描述 Kubernetes 的本质了:“容器是打包和运行应用程序的好方法。在生产环境中,你需要管理运行应用程序的容器,确保没有停机。例如,如果一个容器出现故障,另一个容器需要启动。如果这种行为由 一个系统来处理,岂不是更容易?”
这就是 Kubernetes 来拯救的地方!Kubernetes 为你提供了一个框架,可以可靠地运行分布式系统。它负责应用程序的扩展和故障转移,提供部署模式,以及 更多功能。”(kubernetes.io/docs/concepts/overview/#why-you-need-kubernetes-and-what-can-it-do)
Kubernetes 的同一页面列出以下 Kubernetes 的优势:
-
服务发现和负载平衡
-
存储编排
-
自动化的部署和回滚
-
自动的装箱
-
自愈
-
Secret 和配置管理
在阅读本手册时,我们将探索并实践所有这些优势,同时为关键工作负载设计生产级别的 Secrets 管理解决方案。
Kubernetes 的设计原则
我们已经建立了关于容器的演变和采用的背景,以及 Kubernetes 在考虑到我们的应用程序需要具备弹性、可伸缩性和部署模式的情况下的能力。但是,Kubernetes 如何实现如此无摩擦的体验?
基于作为 Red Hat 专业服务组织中的前云架构师的经验,我尝试回答这个问题:
-
从工作负载的角度来看,应用程序将使用的每个基础设施需求都以声明方式简单定义,无需网络、存储、安全等领域专家介入。描述
Pod、Service和Deployment对象期望状态的 YAML 清单然后由 Kubernetes 作为服务代理处理,供应商为每个具有 Kubernetes 集成的特定供应商编写一个与环境和 Kubernetes 发行版无关的清单。 -
从基础设施的角度来看,堆栈的每个组件都有相应的 Kubernetes API 对象。如果没有,供应商可以通过标准的 Kubernetes API 对象
CustomResourceDefinition(也称为CRD)引入自己的对象。这确保了一个共同的标准,即使与第三方软件、硬件或云供应商交互时也是如此。
当 Kubernetes 收到具有有效对象定义的请求时,编排器将应用相关的 CRUD 操作。换句话说,Kubernetes 引入了本地自动化和编排。同样的原则应适用于作为容器运行的每个 Kubernetes 组件,以便它们从自愈、弹性和可伸缩性中受益,同时不依赖于底层软件、硬件或云提供商。
这种方法不仅支持容器化应用程序的可移植性,还支持整个应用程序平台的可移植性,同时减少了在部署应用程序、维护平台甚至通过新功能或组件丰富 Kubernetes 项目时需要技术专家参与的需求。
定义 Kubernetes API 对象的 YAML 清单的概念已经流行了一段时间。现在是时候看一个简单的例子,显示Pod对象(一个或多个容器的逻辑分组)的期望状态:
apiVersion: v1
kind: Pod
metadata:
name: hello-app
spec:
containers:
- name: hello-world
image: hello-path:0.1
ports:
- containerPort: 8080
此Pod对象的定义为 Kubernetes 提供了以下必要信息:
-
定义具有名称
hello-app的Pod对象的期望状态。 -
指定有
containers,其中一个名为hello-world,并使用hello-path的容器镜像。为此,我们希望从容器注册表中拉取0.1版本。 -
接受传入流量到
hello-world应用程序,使用容器级别的8080端口。
就这样!这是我们第一个Pod定义。它允许我们部署一个简单的容器化应用程序,无需复杂操作且对底层基础设施没有任何了解。
Kubernetes 架构
这背后没有太多魔力,多个组件的工作提供了极好的弹性和抽象级别,并且带来顺畅的体验。以下图表概述了运行在 Kubernetes 实例中的组件:

图 1.2 – Kubernetes 组件
一个 Kubernetes 集群可以分为两个逻辑组——控制平面(一些发行版将其称为主节点)和(工作)节点。我们来深入了解每个逻辑组及其相应的组件:
-
控制平面:
-
kube-apiserver:该组件负责暴露 Kubernetes API,并启用有关对象定义及其在etcd中的状态的 CRUD 操作。 -
etcd:该组件是一个键值存储,并作为资产管理服务。如果etcd损坏,将导致完全灾难性的情况。 -
kube-scheduler:该组件跟踪Pod的期望状态,并处理集群内可能的漂移。例如,如果创建或修改了Pod对象定义,kube-scheduler会调整其状态,确保容器仅在健康节点上运行。 -
kube-controller-manager:该组件运行一系列控制器,负责处理节点、作业、端点和服务帐户的期望状态。控制器是协调循环,跟踪对象的期望状态与当前状态之间的差异,并调整后者以使其匹配最新的对象定义。 -
cloud-controller-manager(可选):类似于kube-controller-manager,当在云中部署 Kubernetes 时,该组件通过增强集群与相关云提供商服务的交互,提供额外的抽象。
-
-
节点(控制平面也包括在内!):
-
kubelet:该组件与kube-apiserver交互,以验证和调整绑定到节点的 Pod 的期望状态。 -
kubeproxy:该组件在每个节点上提供基本的网络连接,同时维护网络规则,以允许(或不允许)内外部网络流量访问 Pod。 -
container runtime:该组件负责运行容器。
-
还有一些附加组件,应该作为附加项进行考虑,因为它们直接依赖于 Kubernetes 发行版。这些附加项负责处理诸如 DNS、日志记录、度量、用户界面等服务。
重要说明
在开发/测试环境中,可以部署单个节点作为同时充当控制平面和工作节点的控制平面。然而,出于弹性目的,生产级环境应考虑至少三个控制平面,带有专用的工作节点来提高弹性和关注点分离,以及为应用程序分配计算资源。
从本地容器到 Kubernetes Pod 的实际操作
容器的主要优点是其可移植性和平台无关性。使用 Docker、Podman 或 Kubernetes 在容器中部署著名的Hello World应用程序不应该需要修改应用程序代码。我甚至会进一步说,我们不应关心底层基础设施。另一方面,使用裸金属或虚拟化方法部署应用程序时会有大量的约束要处理。
在开始之前,我们假设您具备以下条件:
-
本章开头提到的所有技术要求
-
访问本书的 GitHub 存储库(
github.com/PacktPublishing/Kubernetes-Secrets-Handbook) -
此示例位于
ch01/example01文件夹中;
让我们看一个简单的示例,说明基本的软件供应链:
-
构建应用程序二进制文件:该示例是一个简单的 Go 应用程序,展示了 HTTP 服务和控制台日志功能
-
构建包含应用程序二进制文件的容器镜像:将使用 Golang 工具集容器镜像构建应用程序;第二个小型容器镜像将用于携带应用程序二进制文件
-
使用 Podman 运行容器化应用程序:这是第一次运行,将利用 Podman 桌面的图形界面来说明运行容器的相当简单的过程
-
使用
kubectl命令行展示如何处理我们的第一个 YAML 清单以创建一个 KubernetesPod对象
注意,这个示例与整个过程将发生的 CPU 架构无关。这意味着您可以在不同的 CPU 目标上安全地执行相同的练习,而无需重写代码或更改任何配置文件。
有趣的是,像 Docker 或 Podman 这样的容器运行时用于构建应用程序和包含应用程序二进制文件的容器镜像。这是通过一个称为 Dockerfile 的文本文件完成的,该文件定义了构建容器镜像所需的所有步骤:
FROM registry.access.redhat.com/ubi8/go-toolset@sha256:168ac23af41e6c5a6fc75490ea2ff9ffde59702c6ee15d 8c005b3e3a3634fcc2 AS build
COPY ./hello/* .
RUN go mod init hello
RUN go mod tidy
RUN go build .
FROM registry.access.redhat.com/ubi8/ubi-micro@sha256:6a56010de933f172b195a1a575855d37b70a4968be8edb 35157f6ca193969ad2
LABEL org.opencontainers.image.title "Hello from Path"
LABEL org.opencontainers.inage.description "Kubernetes Secrets Handbook - Chapter 01 - Containter Build Example"
COPY --from=build ./opt/app-root/src/hello .
EXPOSE 8080
ENTRYPOINT ["./hello"]
Dockerfile 的构建步骤如下:
-
获取
go-toolset镜像用于构建。 -
将所有应用程序内容放入该镜像中。
-
运行 Go 构建过程。
-
获取
ubi-micro镜像作为目标容器。 -
设置一些容器镜像元数据。
-
将二进制文件从构建镜像复制到目标镜像。
-
设置应用程序的端口暴露。这里是
8080。 -
运行应用程序二进制文件。
就这样!一旦应用程序构建完成,容器镜像成功创建并推送到注册中心,容器镜像将可在本地容器注册中心使用,之后可以使用 Docker 或 Podman 启动容器。只需通过一个简单的命令行加上几个参数即可完成此操作,尽管你也可以利用 Podman Desktop 图形界面。
另一方面,在像 Kubernetes 这样的应用平台上运行容器需要不同的方法——也就是说,声明性地使用 YAML 清单。一个示例已在本章前面提供,并可以在本书的 GitHub 仓库中找到。此 YAML 清单通过诸如 kubectl 之类的工具提交给 kube-apiserver。
这是 Kubernetes Pod 对象创建的事务性概览:

图 1.3 – Kubernetes Pod 创建
如我们所见,etcd 记录在 Pod 对象创建期间持续更新。所需状态被保存;过程中的每个组件的当前状态也被保存,从而生成一种审计轨迹。这种设计使得当未达到预期结果时,调试变得更加容易。
一旦 Pod 对象在 etcd 中注册,所有 Kubernetes 组件便会开始执行任务,向所需状态聚合,无论是否存在网络分区、节点故障等潜在问题。这就是在单台机器上使用本地容器运行时(如 Docker 或 Podman)运行容器与使用像 Kubernetes 这样的容器平台进行大规模编排的区别。
这里有一些值得思考的内容:
-
我写了“运行容器化应用程序”和“部署容器化应用程序”来说明容器运行时(如 Docker 或 Podman)运行容器化应用程序与 Kubernetes 调度容器并编排其他资源(如网络、存储、密钥等)之间的区别。请注意,Kubernetes 中有一个名为
Deployment的对象,用于处理发布管理和可扩展性功能。更多细节请参见kubernetes.io/docs/concepts/workloads/controllers/deployment/。 -
即使在非生产环境中使用虚拟机执行这样的操作,也可能需要几天甚至几周的时间。
-
使用容器开发应用程序,无论是单体架构还是微服务架构,都可以实现真正敏捷的开发周期,要求一切都必须是持续的(开发、集成、改进和部署)。
-
使用 YAML 清单部署应用程序将引发 Git 仓库的有机使用,进而激发另一种实践——GitOps。简而言之,应用程序及其基础设施管理的每一个期望状态定义都会存储在 Git 仓库中,默认情况下为应用程序和基础设施团队提供了一个集中配置管理点,包括授权、同行评审和组织等功能。
通过这个过程,我们已经从通过本地容器运行目标应用程序过渡到通过 Kubernetes Pod 运行它。这样,我们掌握了如何创建 Pod,涉及到哪些 Kubernetes 组件,以及其中的交互和更多内容。
Kubernetes 中的 Secrets
在这一部分,我们回顾了容器和 Kubernetes 的基础知识,并通过实际操作的例子帮助我们建立了关键概念,如下所示:
-
通过时代与技术演变的应用部署
-
为什么选择容器和 Kubernetes
-
Kubernetes 的架构和原则
-
使用 Podman Desktop 和 Kubernetes 构建和运行容器
借助我们已掌握的知识,我们可以开始深入了解 Kubernetes 中的 Secrets 这一更高级的概念。我们将探讨 Secrets 如何在 Kubernetes 中存储,如何注入到最小执行单元——Pod 中,以及我们需要解决的安全问题。
Secrets 概念
有趣的是,在我设计和部署 Red Hat OpenShift 的过程中,这个话题总是被我所合作的客户和合作伙伴团队认为与他们无关。经过反思,我得出结论,这与我们过去二三十年来使用的遗留模式有关。
在传统环境中,无论是物理机器还是虚拟机器,关注点分离原则都会产生明确的结果。基础设施团队关注基础设施,应用团队关注应用程序。这包括管理诸如凭证、令牌、许可证密钥、证书等 Secrets。应用团队的成员不会与基础设施团队共享 MySQL 数据库的凭证。
在 Kubernetes 中,这些问题按设计合并为一个入口点:应用平台。尽管关注点有所分离,Kubernetes 与外部 API 驱动服务的集成仍需要凭证、令牌或证书来进行身份验证和信任。这些 Secrets 必须保持在平台内部,以确保集群和应用的韧性、可扩展性和编排能力。
在容器镜像设计中,Secrets 不能被硬编码或包含在容器镜像中。硬编码 Secrets 会让所有能够访问容器镜像注册表的内部和外部人员都能够获得这些敏感信息。如果容器镜像被推送到公共注册表(为了便于重新分发,通常会这么做),它将使 Secrets 被更广泛的受众获取。
这就是 Kubernetes 内置 Secrets 管理框架的原因,提供了一个名为 Secret 的专用 API 对象。以下是 Secret 对象定义的大致概览:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4K
password: UGFja3QxMjMhCg==
让我们一起看看清单:
-
我们告知 Kubernetes 我们想要创建一个
Secret对象。 -
这个对象的名称是
mysecret,我们定义了一个名为Opaque的类型,这意味着我们自己定义了data字段的容器。 -
data字段由两个键值对组成
现在,让我们更仔细地看看这些键值对。看似随机的一组字符其实是经过 base64 编码的数据。
为什么使用 base64?虽然我们可以假设需要加密诸如凭证之类的敏感数据,但使用 base64 仅是为了简化通过命令行、网络和 kube-apiserver 进行的处理,以避免由于特殊字符而导致有效负载被截断。
这两个条目可以在每个操作系统或提供 base64 编码/解码工具的网站上解码。那么,我们是否可以假设在 kube-apiserver 将有效负载保存在 etcd 密钥存储中时,会加密数据?嗯,本手册已经给出了一个明确的提示:不会!
另一个 API 对象有一个类似的数据字段,可以用来与应用程序共享敏感数据:ConfigMap。虽然 ConfigMap 最初设计用于存储环境变量和应用程序参数,但开发人员已经迅速将其用于包含类似于许可证密钥文件的高级应用程序配置。因此,这个对象的内容可能被恶意黑客利用,从而访问容器,获取平台内外其他工作负载的访问权限,甚至控制 Kubernetes 集群。因此,ConfigMap 应该像 Secret 对象一样小心处理。以下是 ConfigMap 对象的概述:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-environment
data:
appversion1: dev
如你所见,Secret 的 data 字段和 ConfigMap 的 data 字段有一个显著的区别——编码部分。ConfigMap 的 data 字段规格要求 UTF-8 字符串,而 Secret 的要求是键值对,其中值需要用 base64 编码。这个示例展示了如何将应用程序设置为 dev 模式,从而启用额外的调试工具。
在 Kubernetes 上存储 Secrets
现在是进行第二个实际操作示例的好时机,这样我们可以理解 Secrets 和其他 Kubernetes 对象(如 ConfigMap)之间的差异。在开始之前,我们假设你已经具备以下条件:
-
本章开头提到的所有技术要求
-
访问本书的 GitHub 仓库:
github.com/PacktPublishing/Kubernetes-Secrets-Handbook -
当前示例可在
ch01/example02文件夹中找到
让我们来看看通过这个示例我们达成了什么:
-
首先,我们使用提供的 YAML 清单创建一个
Secret对象,类似于我们之前为mysecret创建的秘密定义。我们还检查它的状态以及如何从 Kubernetes 恢复其定义。当我们想恢复对象的当前状态并稍后重新创建时,这非常有用。我们还解码了base64负载以揭示键值对。 -
接着,我们从 Kubernetes 恢复 YAML 清单,展示了一种非常简单的方式来恢复我们的
Secret对象及其敏感数据。换句话说,如果恶意黑客成功与kube-apiserver交互,他们就可以从 Kubernetes 集群中提取部分或全部Secret对象。 -
接下来,我们从头开始创建一个新的
Secret对象,通过将键值对编码为base64,编写 YAML 清单,并通过命令行推送。以下是 KubernetesSecret对象创建的事务概述:

图 1.4 – Secret 创建
- 下一步是从
Pod对象中访问Secret负载。在这个示例中,我们使用了一种名为busybox的特殊类型容器,它提供了一个小巧的环境,非常适合进行测试/调试。Pod清单包括对我们新创建的Secret对象的引用,并将其值分配给一个环境变量,我们将从busybox中使用echo命令输出。这将出现在容器日志中。以下是一个 KubernetesPod对象创建的事务概述,其中包括注入的Secret对象:

图 1.5 – 将 Secret 注入 Pod
-
请注意,
kubelet组件将负责解码base64负载。这是为了确保负载能够在不同组件之间以及跨网络传输。 -
然后,我们通过其 API 访问
etcd键存储。这表明通过这种方式检索的数据既没有加密也没有base64编码!此时,成功攻破etcdPod 的恶意黑客可以完全访问 Kubernetes 资产管理,并可以控制应用平台的整个命运,甚至是部署在云提供商账户中的平台。 -
最后,我们进一步通过提取本地
etcd文件并进行检查,来获取我们最后创建的Secret对象。这看起来像是一个牵强的场景,但请想一想文件系统访问或备份中的 Kubernetes 集群,这些备份包括了etcd文件。即使 Kubernetes 集群经过良好的加固,且安全暴露较少,恶意黑客如果攻破了存储和/或备份系统,依然可以从这些文件中恢复所有Secret对象。
与之前的 Pod 创建工作流相比,从交易的角度来看,我们可以确定其对整体过程的影响较小。请注意,我们的第一个示例展示了如何将 Secret 键值对作为环境变量加载。然而,其他选项也存在;我们将在本书后面探讨它们的潜在缓解措施。
重要说明
只有在其 Pod 配置中定义了环境变量的容器才能访问这些键值对。然而,运行 privileged: true 的容器将能够访问它运行的节点上的所有 Secrets,这会导致重大的安全暴露。
我们为什么要关注这个问题?
虽然 Kubernetes 为平台和应用团队提供了顺畅的体验,但它并没有为你提供加固的解决方案。
解决此安全问题的第一种方法是利用一个保险库解决方案(如 HashiCorp Vault、CyberArk Conjure 或 Azure Key Vault)。然而,这只会确保应用程序端的安全性。我们仍然需要考虑使用 ConfigMap 或多集群服务,如应用程序互联,这涉及到使用证书进行相互认证,而这些证书是在应用平台内生成和部署的。
那么,让我们将需求重新思考成简单的层次结构:
-
Secret对象是为了让内部组件与其他组件(存储、网络、执行单元、控制器等)进行交互而创建的。后续部署的第三方组件以丰富平台的功能时,也会遵循相同的模型。这些Secret对象不应为提高可用性而转移到保险库解决方案中。如果与外部 Vault 解决方案存在任何类型的分区,Kubernetes 集群将开始崩溃,所有工作负载也会随之崩溃。 -
ConfigMap对象、其卷的加密密钥、TLS 证书等。与平台相关的
Secret对象类似,这些对象应该存储在 Kubernetes 中,以确保应用平台的调度、自愈和可操作性。
按照设计,Kubernetes 不会成为一个存储系统或加密的 Vault 来保护敏感数据。相反,Kubernetes 将提供必要的框架,以实现第三方解决方案之间的互联,并利用它们在各自领域的专业知识来解决特定需求。
考虑到这些方面,解决 Kubernetes 中 Secrets 管理的安全问题并不像勾选一个框那么简单。
安全暴露
在本章中,我们已阐明了在 Kubernetes 上运行容器化应用的好处,但也暴露了此类应用平台上 Secrets 管理的安全挑战。
通过我们的实践示例,我们意识到我们在 Secret 和 ConfigMap 对象中的敏感数据是多么不安全。我们还可以列出一系列安全暴露点,这些点可能被利用来破坏应用平台,包括外部服务:
-
kube-apiserver:这是 Kubernetes 的主要组件,恶意黑客可以利用这个应用平台的第一个入口点。
-
etcd不提供任何加密功能。数据库文件是一个二进制文件,可以轻松读取。 -
kube-apiserver组件,etcd是一个基于 API 的服务,任何访问或网络跟踪都可能暴露数据。 -
Pod对象意味着访问写入数据库文件的文件系统。 -
etcdPod 的文件系统托管在一个卷上,以提供持久存储。该卷附加在节点上,通过访问该节点,可以通过附加的卷访问数据。*etcd文件。访问备份可能会暴露数据。
Secrets 可能以多种方式暴露。例如,您可以与 Kubernetes 组件如 kube-apiserver 和 etcd 交互来实现,或者通过物理层面如直接节点访问或访问备份来实现。
总结
在本章中,我们介绍了容器、Kubernetes 和 Secrets。我们回顾了迄今为止的历史,了解了裸机、虚拟机和基于容器的部署概念。我们有机会理解容器化的好处,并介绍了容器编排引擎。我们深入学习了 Kubernetes 及其组件,这使得我们能够运行第一个 Kubernetes secret 示例,并深入探讨了参与其中的 Kubernetes 组件,从而便于 Secret 的使用。这帮助我们识别了使用 Kubernetes Secrets 时所面临的安全性和健壮性问题。
在下一章中,我们将重点介绍 Kubernetes Secrets 的不同类型、它们的用途以及与 Secrets 一起出现的交叉问题,如审计和访问权限。
第二章:逐步了解 Kubernetes Secrets 管理概念
在上一章中,我们对 Kubernetes 及其组成组件进行了全面概述,并了解了配置是如何应用和存储的。此外,我们构建了一个 Golang 应用,并成功地在 Kubernetes 上运行了该应用。正如预期的那样,Secrets 需要被添加到我们的应用配置中。Secrets 管理涉及多种问题。从创建、修改到删除,我们需要解决安全性、可扩展性和弹性等方面的挑战。
在本章中,我们将讨论以下主题:
-
什么是 Kubernetes Secrets,它与其他 Kubernetes 对象有何不同?
-
不同类型的 Secrets 及其使用场景
-
在 Kubernetes 中创建、修改和删除 Secrets
-
在不同部署场景中的 Kubernetes Secrets 配置
-
管理机密的要求,包括安全存储和访问控制
-
使用 RBAC 保护对 Secrets 的访问
-
审计和监控 Secret 使用情况
技术要求
为了将概念与实践示例结合,我们将利用一系列常用的工具和平台,这些工具和平台通常用于与容器、Kubernetes 和 Secrets 管理进行交互。本章中,我们将使用一个友好的桌面图形解决方案:
-
Podman Desktop (
podman-desktop.io) 是一个开源软件(OSS),它与容器进行交互,运行本地 Kubernetes 实例,甚至连接远程平台,如 Red Hat OpenShift、Azure Kubernetes Service(AKS)等。在本章中,我们将使用 Go 编程语言。要在系统中安装 Go,可以参考官方文档中的安装说明(go.dev/doc/install)。 -
在本章中,还将使用 minikube。要在系统中安装 minikube,可以参考官方文档中的安装说明(
minikube.sigs.k8s.io/docs/start/)。 -
本书中的所有代码示例都可以在我们的专用 GitHub 仓库中找到,仓库结构清晰,并且为每一章提供了相应的文件夹和说明(
github.com/PacktPublishing/Kubernetes-Secrets-Handbook)。
什么是 Kubernetes Secrets,它与其他 Kubernetes 对象有何不同?
Kubernetes 的一个基本构建模块是 Kubernetes 对象。通过 Kubernetes 对象,我们可以表示系统的状态。运行在 Kubernetes 上的应用程序包括实际的程序、应用程序使用的资源和应用程序的配置(如健康检查)。关于其他横向关注点,如安全性,存在 基于角色的访问控制(RBAC)配置;这些配置包括集群范围的角色、命名空间角色以及与用户或实体绑定的角色。此外,Kubernetes 对象还包括命名空间,它们充当逻辑容器,以及网络策略,它们是集群范围的流量规则。通过创建 Kubernetes 对象,我们声明了集群的期望状态。Kubernetes 负责并将致力于确保系统的实际状态与我们创建的对象所定义的状态匹配。
一个典型的 Kubernetes 对象有一些必填字段:apiVersion、kind、metadata 和 spec。
我们可以看到它的 YAML 表示如下:
apiVersion: apps/v1 #version of Kubernetes api
kind: Deployment #type of Object
metadata: #metadata information
name: example-deployment
spec: #the state the object should be
Kubernetes 用户最常接触到的 Kubernetes 对象如下:
-
Pod -
Deployment -
StatefulSet -
Cronjob -
Service -
Ingress -
NetworkPolicy -
ConfigMap -
Secret
之前提到的对象可以逻辑上归为表示工作负载的对象。像 Pod、Deployment、StatefulSet 和 Cronjob 这样的对象用于定义执行特定任务的计算资源;这些任务可能是运行服务器、执行定时任务,甚至设置分布式内存网格。像 Service、Ingress 和 NetworkPolicy 这样的对象指定了应用程序的网络方面;这可以是负载均衡内部流量、将 Kubernetes 服务暴露到互联网,或在应用程序之间阻止内部流量。到目前为止,提到的 Kubernetes 对象都针对应用程序的计算资源部署和应用程序之间的流量路由。
ConfigMap 和 Secret 在使用上有所不同,因为它们分别用于配置存储。ConfigMap 和 Secret 是由在 Kubernetes 上运行的应用程序使用的对象。ConfigMap 可以用来存储配置,常见的配置示例包括存储在 nginx.conf 中的 nginx 配置、基于 JSON 的配置,或基于 YAML 的应用程序配置。Secret 对象用于存储敏感数据。例如,在 nginx 配置中,我们需要存储在 .key 文件中的 TLS 密钥和存储在 .pem 文件中的证书。这两者都是敏感文件,需要安全处理。此安全处理还应适用于用户名和密码、访问令牌等凭证。实际上,Kubernetes Secrets 是用于存储敏感配置数据的 Kubernetes 对象,因此应该限制访问,并确保以安全的方式处理 Secrets 中存储的信息。
不同类型的 Secrets 及其使用场景
Kubernetes 为我们提供了多种类型的 Secrets。在幕后,它使用我们在 第一章 中看到的相同存储机制,理解 Kubernetes Secrets 管理;一旦创建,Secrets 将被序列化并存储在 etcd 中。不同之处在于这些 Secrets 在使用时的处理方式。Secrets 有多种类型,让我们逐一查看。
Opaque
Opaque secret 是默认的 secret 类型。每当我们需要添加敏感配置时,无论是文件还是变量,它都会作为 Opaque secret 创建。
Opaque Secrets 可以通过提供键值对来使用:
$ kubectl create secret generic opaque-example-from-literals --from-literal=literal1=text-for-literal-1
$ kubectl get secret opaque-example-from-literals -o yaml
apiVersion: v1
data:
literal1: dGV4dC1mb3ItbGl0ZXJhbC0x
kind: Secret
...
type: Opaque
Opaque Secrets 也可以通过应用 YAML 文件来执行:
$ kubectl create secret generic opaque-example-from-literals --from-literal=literal1=text-for-literal-1
$ kubectl create secret generic secretfile --from-file=secret-file.txt=./secret.file.txt
kubectl get secret secretfile -o yaml
apiVersion: v1
data:
secret-file.txt: QSBmaWxlIHdpdGggc2Vuc2l0aXZlIGRhdGE=
kind: Secret
metadata:
...
type: Opaque
Opaque Secrets 的示例可以在 ch02/secret-types/opaque 文件夹中找到。opaque.sh 脚本将运行所需的 Bash 命令以实现最终结果。
Kubernetes 服务账户令牌
Pod 是 Kubernetes 中的一个工作单元;需要与 Kubernetes API 进行交互的 Pod 需要一个身份。服务账户是可以直接或通过部署间接映射到 Pod 的身份。只要 Pod 配置了服务账户,它就可以与 Kubernetes API 进行交互。附加的服务账户被授权访问感兴趣的资源。在启动时,配置了服务账户的 Pod 会将服务账户令牌附加到其文件系统上。
长期有效的访问令牌
在 Kubernetes 中,在 v1.27 版本之前,服务账户令牌会作为 Kubernetes 管理的 Kubernetes secret 进行访问。这被称为长期有效的访问令牌。
在最新版本中,仍然可以创建长期有效的访问令牌。可以通过创建一个空的 secret 并在其中添加一个带有服务账户名称的注解来实现:
apiVersion: v1
kind: Secret
metadata:
name: service-account-secret
annotations:
kubernetes.io/service-account.name: example-service-account
type: kubernetes.io/service-account-token
如我们所见,我们注意到在 annotations 部分中有服务账户。通过运行 apply 命令,我们应该能够看到生成了一个令牌:
$ kubectl create sa example-service-account
kubectl apply -f service-account-secret.yaml
kubectl get secret service-account-secret -o yaml
apiVersion: v1
data:
ca.crt: ...==
namespace: default
token: eyJhbGxffQ.eyJhdWQ3RlbTpdW50In0.0LyJWAc2M9SdA3g
kind: Secret
metadata:
annotations:
...
type: kubernetes.io/service-account-token
创建长期有效访问令牌的步骤可以参考以下脚本:ch02/secret-types/service-account/long-live-access-token.sh
服务账户令牌已挂载到 Pod
我们已经看过服务账户作为 secret 的情况;现在让我们看看如何将服务账户令牌挂载到 Pod 上。
一个带有服务账户的 Pod 应该是这样的:
apiVersion: v1
kind: ServiceAccount
metadata:
name: example-service-account
---
apiVersion: v1
kind: Pod
...
spec:
...
serviceAccountName: example-service-account
一旦我们应用了前面的 YAML 清单,我们可以在刚刚调度的 Pod 中运行一个命令。我们将按如下方式打印挂载的服务账户令牌:
$ kubectl exec -it busybox -- cat /var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGdkxfTlUifQ.eyJhdWQidwid3RlbTpdW50In0.0LyJWAc2M9SdA3g
如我们所见,这是一个 JSON Web Token (JWT) 令牌。
创建一个带有服务账户的 Pod 的步骤可以参考以下脚本:ch02/secret-types/service-account/service-account-with-pod.sh
Docker 配置
在 Pod 上使用镜像时,我们可能希望从替代的容器注册中心拉取镜像。为此,我们需要挂载 Docker 配置,以便能够与我们选择的注册中心进行通信。我们可以通过仅使用本地 Docker 配置来测试这一点。
我们将使用以下模板生成一个 YAML 清单:
apiVersion: v1
kind: Secret
metadata:
name: registry-docker-config
type: kubernetes.io/dockercfg
data:
.dockercfg: |
REPLACE_WITH_BASE64
你可以看到REPLACE_WITH_BASE64字符串;它将被 Docker Hub 的 Docker 配置替换。
在docker-credentials文件夹中,已经存在一个用于此目的的 Docker 配置文件,位于ch02/secret-types/docker-credentials/config.json,其中没有实际凭证:
{
"auths": {
"https://index.docker.io/v1/": {}
}
}
我们将发起登录并使用我们的 Docker Hub 凭证:
$ docker --config ./ login --username=dockerhub-username --password=dockerhub-password
文件将包含连接到 Docker Hub 所需的基本认证:
{
...
"auth": "token"
...
}
我们将使用这个配置将其挂载为 Kubernetes 机密:
$ DOCKER_CONFIG=$(cat ./config.json|base64)
$ cat docker-credentials-template.yaml|sed "s/REPLACE_WITH_BASE64/$DOCKER_CONFIG/" > docker-credentials.yaml
在接下来的步骤中,我们将通过应用我们创建的 YAML 清单将凭证上传到 Kubernetes:
$ kubectl apply -f docker-credentials.yaml
然后,我们将创建一个 Pod,从注册表拉取:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
imagePullSecrets:
- name: docker-credentials
镜像将使用指定的凭证进行拉取。
前面的指令已经通过以下脚本进行编排:ch02/secret-types/docker-credentials/docker-credentials.sh
基本认证
基本认证由用户名和密码的密钥组合组成。它使我们能够在指定基本认证机密时提供更具声明性的方式。
YAML 清单应包含username和password键的值:
apiVersion: v1
kind: Secret
metadata:
name: basic-auth-secret
type: kubernetes.io/basic-auth
stringData:
username: a-user
password: a-password
一旦我们应用前面的 YAML 清单,结果将与不透明机密非常相似。
前面的指令已经通过以下脚本进行编排:ch02/secret-types/basic-authentication/basic-auth-secret.sh
TLS 客户端或服务器
TLS 机密用于存储 SSL/TLS 证书。TLS 机密可以在挂载 TLS 证书时提供更具声明性的方式。然而,TLS 机密在指定 Ingress 时可以直接使用。
Ingress 充当系统的外部负载均衡器,处理 HTTP/HTTPS 流量。流量需要使用 SSL 进行加密。
SSL 机密具有以下格式:
apiVersion: v1
kind: Secret
metadata:
name: ingress-tls
type: kubernetes.io/tls
data:
tls.crt: CRT
tls.key: KEY
通过使用ch02/secret-types/ssh/tls.sh脚本,我们将创建一个证书和密钥,可以在 HTTP 服务器上使用。创建的机密将命名为ingress-tls。作为证书使用的脚本将具有webpage.your.hostname主机。
让我们使用之前创建的 TLS 证书来创建 Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
...
spec:
tls:
- secretName: ingress-tls
hosts:
- webpage.your.hostname
rules:
- host: webpage.your.hostname
...
通过使用 Ingress,我们可以为主机定义 SSL 配置。
关于 minikube 用户的说明
如果你在整个minikube.sh脚本中使用了 minikube,你应该按照以下方式在工作站上启用 Ingress:
$ minikube addons enable ingress
现在我们可以测试 Ingress。请注意,Ingress 需要获得一个 IP 地址:
$ kubectl get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
webpage-ingress nginx webpage.your.hostname 192.168.49.2 80, 443 79s
由于此 IP 可能属于内部虚拟机,我们需要执行minikube tunnel命令,将流量转发到我们的 Ingress:
$ minikube tunnel
通过访问localhost/,我们可以查看证书:

图 2.1 – SSL 证书
此外,如果我们想验证 Ingress 路由,我们可以更改/etc/hosts并将webpage.your.hostname的 DNS 映射到localhost。
Token 数据
这种类型的 Secret 是一个引导令牌。它看起来像我们在 REST API 中使用的常规持有令牌;在 Kubernetes 中,它专门用于 Kubernetes 集群的引导过程。在初始化 Kubernetes 集群时,会创建一个引导令牌,之后可以用它将新节点加入到集群中。
结论
我们深入探讨了 Kubernetes Secrets,识别了不同类型的 Secrets,并运行了每种类型的示例,展示了它们的使用和特点。有关 Secrets 的最新发展,您可以随时参考官方文档(kubernetes.io/docs/concepts/configuration/secret/#secret-types)。在本节中,Secrets 的配置是通过 kubectl 命令行完成的。在接下来的部分,我们将探索管理 Secrets 的选项,创建、删除和修改它们。
在 Kubernetes 中创建、修改和删除 Secrets
之前,我们专注于创建 Secrets 并展示它们的用法。接下来,我们将继续管理 Secrets,并识别可用的命令和选项来配置 Kubernetes Secrets。
data 和 stringData
我们通过使用 YAML 文件或命令行应用明文 Secrets。在幕后,我们应用的明文 Secrets 会被转换为 base64 格式。我们可以选择应用明文 Secrets 或使用 base64 格式应用它们;最终,它们会以 base64 格式存储在 Kubernetes 中。当我们使用明文值应用一个 Secret 时,我们使用 stringData 字段。Kubernetes 会处理我们提供的值的编码和解码。
以以下 Secret 为例:
apiVersion: v1
kind: Secret
metadata:
name: plain-text
type: Opaque
stringData:
value: non-base64
一旦我们创建了 Secret,我们将检索它。它应该是 base64 格式的:
$ kubectl apply -f plain-text.yaml
$ kubectl get secret plain-text -o yaml|grep value
value: bm9uLWJhc2U2NA==
该值以 base64 格式存储。这是 Kubernetes 存储 Secrets 时遵循的一种约定。如果我们考虑到 Secret 可能具有的不同变体,这尤其有用。一个 Secret 可能有一个复杂的值;例如,一个大型 YAML 文件或甚至是二进制文件。
对于之前描述的复杂情况,我们可以选择使用 data 字段。当我们以 base64 格式应用 Kubernetes Secrets 时,我们使用 data 字段:
apiVersion: v1
kind: Secret
metadata:
name: base64-encoded
type: Opaque
data:
value: bm9uLWJhc2U2NA==
现在我们已经掌握了创建 Secrets 的知识,我们将继续进行其他操作,比如 update 和 delete。
更新 Secrets
在 Kubernetes 对象中,有一些基本命令可以帮助我们管理它们。这些命令同样适用于 Secrets,因为它们也是 Kubernetes 对象。
编辑 Secrets
编辑一个 Secret 是通过使用 kubectl 的 edit 命令完成的。kubectl 自带一个预配置的编辑器。默认情况下,编辑器是 Vim:
$ kubectl edit secret plain-text
如我们所见,在编辑 Secret 时,它将以 base64 格式呈现。如果我们尝试使用明文更改 Secret,将会失败。当我们编辑 Secret 时,必须提供一个 base64 值。
使用 kubectl 编辑 secret 时的一个选项是通过 —``record=true 参数记录导致更改的命令:
$ kubectl edit secret plain-text --record=true
$ kubectl get secret plain-text -o yaml
...
kubernetes.io/change-cause: kubectl edit secret plain-text --record=true
...
正如我们所见,我们发出的 edit 命令已被记录下来。
出于备份目的以及跟踪先前状态的需求,在编辑时我们可以使用 —``save-config=true 参数:
$ kubectl edit secret plain-text --save-config=true
$ kubectl get secret plain-text -o yaml
...
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion...."type":"Opaque"}
...
在 last-applied-configuration 字段中,我们将备份先前的配置。
到目前为止,我们已经编辑了 Secrets,并且成功跟踪了导致 Secrets 变化的命令,同时还记录了最后应用的配置。这并不总是如此;有时,我们可能希望 Secrets 是不可变的,这是我们将在下一节实现的目标。
不可变的 Secrets
在某些情况下,我们可能希望我们的 Secrets 保持不变;例如,我们希望防止意外的编辑。以下是我们如何实现这一点:
apiVersion: v1
kind: Secret
metadata:
name: immutable-secret
type: Opaque
stringData:
value: non-base64
immutable: true
如果我们尝试编辑以下 secret,一旦我们尝试保存,就会遇到以下错误消息:
data: Forbidden: field is immutable when `immutable`is set
此外,如果我们将现有的 secret 设置为不可变,那么它将无法被编辑;该 secret 将变得永久不可变。要修改不可变的 secret,唯一的方式是删除该 secret 并重新应用它。接下来,我们将学习如何删除 Kubernetes Secrets。
展示不可变 Secrets 的示例可在 ch02/secret-types/secret-management/immutable/immutable-secret.sh 中找到。
删除 Secrets
删除 Kubernetes 对象的命令同样适用于 Secrets。
以下示例将删除一个 Kubernetes secret(如果它存在的话):
kubectl delete secret immutable-secret
通过删除一个 secret,它将被永久从我们的系统中移除。唯一能够恢复它的方式是恢复一个包含该 secret 的 etcd 备份,或者应用一个通过以下命令手动备份的文件:
kubectl get secret immutable-secret –o yaml
结论
在本节中,我们更进一步地管理了 Kubernetes Secrets。我们更新了 Secrets,跟踪了我们的更改,并且还备份了先前的配置。此外,我们创建了不可变的 Secrets,以防止意外编辑,最后但同样重要的是,我们删除了不再需要的 Secrets。在接下来的章节中,我们将重点讨论在不同环境中使用 secret 的相关问题。
在不同部署场景中的 Kubernetes Secrets 配置
在软件开发生命周期(SDLC)中,一个团队可能会使用不同的环境来测试他们的增量更新,然后再将其发布到生产环境中。就像生产部署一样,其他任何环境中的部署也会有一定的配置要求,包括 Secrets。
环境间的 Secret 使用
当涉及到 Secrets 时,我们需要确保它们在任何环境下的持久性和完整性。在不同环境中对 Secrets 的处理不同可能会导致长期问题,并且团队将无法完全验证对 secret 处理选择的安全影响。
鉴于环境之间可能存在成本节约要求或完整安装带来更多开销的差异,Secrets 需要被安全存储。可能会出现 Secrets 需要共享的情况。例如,某个外部 SaaS 服务的专有密钥需要在不同环境之间共享。另一个例子是,当多租户云账户托管多个环境时。
从开发到部署
要部署一个 secret,敏感信息需要存放在某个地方。这些信息最终会由某个个人插入系统并应用到 Kubernetes。如今,公司将敏感信息存储在专门为此类信息设计的各种系统中。简而言之,安全存储是托管 Secrets 的必要条件。
部署 Kubernetes secret 这类敏感内容的生命周期,从从安全存储中获取证书开始,创建所需的 YAML 文件并应用到 Kubernetes。
对于 CI/CD 任务,大多数 CI/CD 提供商为我们提供了在任务中使用秘密值的选项。这可以帮助我们为 CI/CD 任务提供凭证,以便与秘密存储进行交互。
另一种范式是 GitOps。Argo CD 是一个极受欢迎的工具,可以自定义秘密部署,以便在解密后应用 secret。
结论
在不同环境下,我们对 Secrets 的处理应该保持一致,无论环境如何。这有助于自动化以及一致性。
管理 Secrets 的要求,包括安全存储和访问控制
在责任方面,Kubernetes 集群有责任安全地容纳 Secrets 并防止未经授权的访问。每个托管在 Kubernetes 上的秘密都是由个人或自动化过程存储的。某个时间点,当前存储在 Kubernetes 上的这个秘密曾经存在于另一个系统中。因此,在 Secrets 到达 Kubernetes 之前,确保它们被安全存储非常重要。
安全存储
有许多专门用于安全存储的工具。例如,HashiCorp Vault、Google Cloud Platform(GCP)Secret Manager 和Amazon Web Services(AWS)Secrets Manager。这些是外部的 Secrets 管理解决方案。
这些解决方案的好处是,它们既可以作为独立的 Secrets 管理系统使用,也可以直接从 Kubernetes 中使用。在开发过程中,甚至在 CI/CD 任务中使用安全存储也是可行的。
这些解决方案的共同点是它们处理了跨切关注点,如管理、版本控制、加密和访问控制功能。
访问控制
访问控制对确保我们的秘密存储安全至关重要。持久性、静态加密和传输加密使得我们与安全存储系统的交互更加安全,但这还不够。当涉及到访问 Secrets 时,我们需要具有精细的访问控制。
我们需要区分用户和他们在组织中的角色。此外,权限可能会因环境而异。另一个方面是审计和识别是否发生了未授权访问事件。
Git 和加密
除了使用安全存储系统外,另一种流行的选择是将 Secrets 存储为加密形式。通过将加密后的 Secrets 存储在 Git 仓库中,Git 的功能使得多个方面变得可行。例如,版本控制通过提交历史默认启用,访问控制通过 Git 的访问控制规则得到满足,存储的韧性和持久性依赖于提供商的保障。至于加密,解决方案的优劣取决于所选择的加密方式。数据可以以各种形式加密,从 Pretty Good Privacy (PGP) 密钥(www.openpgp.org/)到硬件安全模块,再到现代 云密钥管理服务 (cloud KMS) 解决方案。基于此的一个非常流行的工具是 Mozilla Secrets OPerationS (SOPS): github.com/mozilla/sops。Mozilla SOPS 使用云提供商提供的 KMS 以及 PGP。
结论
就像每个机密一样,它们的访问应该受到限制,并且在任何情况下都不应允许未授权人员访问。因此,除了存储 Secrets 的位置外,我们还需要提供适当的访问控制。
使用 RBAC 确保对 Secrets 的访问
在 Kubernetes 对象中,一个跨切面的问题是授权访问。总体而言,系统的状态是敏感的。您应当对某些操作有授权访问,例如更改部署的副本数或更改部署的自动扩展规则。Kubernetes 为我们提供的安全机制是 RBAC。
RBAC 介绍
RBAC 由以下 Kubernetes 对象组成:
-
角色
-
角色绑定
-
集群角色
-
集群角色绑定
我们将分别检查每个组件,看看它们如何与 Kubernetes Secrets 结合使用。
角色
角色是一组仅在角色所在命名空间内生效的权限。通过指定一个角色,我们定义了可以对 Kubernetes 资源执行的操作。角色的格式如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: secret-viewer
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get","list","watch"]
verbs 是我们应该能够执行的操作,resources 是这些操作的目标。apiGroups 指向我们将与之交互的资源的 API 组;通过设置空值,它表示核心 API 组。
前面的 Role 对象使得拥有该角色的操作员能够获取、列出并观察默认命名空间中的 Secrets。让我们继续并将该角色绑定给一个操作员。
角色绑定
通过检查如何使用 YAML 表示 Role 对象,我们可以识别出动作和目标。角色绑定帮助我们定义行为者。行为者可以是一个用户(个人或群组)或一个服务账户。角色绑定有以下 YAML 表现:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: secret-viewer-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: secret-viewer
subjects:
- kind: ServiceAccount
name: secret-viewer
namespace: default
在定义角色绑定时,命名空间是必须存在的。这是因为角色绑定只在它们所在的命名空间内生效。在roleRef中,我们定义一个应该位于同一命名空间中的角色。在subjects中,我们定义一个访问该角色的行为者列表。请注意,行为者可以来自不同的命名空间。
集群角色
集群角色与角色非常相似:它们定义了一组权限;然而,它们是集群范围内生效的,而不是仅限于某一个命名空间。它们有以下 YAML 表现:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secret-admin-cluster
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["*"]
这与 Role 对象几乎完全相同,唯一的不同是没有命名空间,因为这些规则适用于整个集群。前面的 ClusterRole 角色使得具有该角色的行为者能够管理 Kubernetes 集群中所有命名空间的 Secrets。我们现在可以继续将该 ClusterRole 对象绑定到一个行为者。
集群角色绑定
通过使用集群角色绑定,我们将一个集群角色绑定到一组用户和服务账户。集群角色绑定有以下 YAML 表现:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: secret-admin-cluster-binding
subjects:
- kind: ServiceAccount
name: secret-admin
namespace: default
roleRef:
kind: ClusterRole
name: secret-admin-cluster
apiGroup: rbac.authorization.k8s.io
现在我们已经了解了 RBAC 以及如何使用它来保护 Secrets,我们可以开始一个端到端的示例。
RBAC 和 Secrets
在之前的示例中,我们为集群的秘密资源创建了角色和集群角色。我们集中讨论了一个用于查看权限的角色和一个用于管理 Secrets 的管理员角色,但值得看看是否还有其他选项。
我们可以通过使用 api-resources 调用来识别与 Secrets 相关的操作:
$ kubectl api-resources -o wide|grep secrets
secrets v1 true Secret [create delete deletecollection get list patch update watch]
现在我们已经知道了有哪些选项,我们将为我们的 Secrets 创建自己的 RBAC 配置。
ClusterRole
我们将使用我们之前创建的 YAML 文件中的 ClusterRole 规范创建一个集群角色,以管理 Secrets:
$ kubectl create sa secret-admin
$ kubectl apply -f ./secret-admin-cluster.yaml
这应该创建一个能够在整个集群范围内管理 Secrets 的集群角色。我们将使用一个附加了 ClusterRole 对象的 Pod,并检查秘密的创建:
apiVersion: v1
kind: Pod
metadata:
name: kubectl-create-secret
spec:
containers:
- name: kubectl
image: bitnami/kubectl:latest
args:
- create
- secret
- generic
- test
- --from-literal=literal1=text-for-literal-1
serviceAccount: secret-admin
serviceAccountName: secret-admin
通过检查日志,我们应该看到以下消息:
secret/test created
该 Pod 被配置为使用服务账户,而服务账户有一个集群绑定到 ClusterRole 对象,该对象具有对 Secrets 的管理员权限。
角色
我们将使用之前创建的角色,该角色提供查看 Secrets 的权限:
$ kubectl create sa secret-viewer
$ kubectl apply -f ./secret-viewer.yaml
我们现在应该在默认命名空间中有一个用于查看 Secrets 的角色。我们将运行一个 Pod 来检索 Secrets:
apiVersion: v1
kind: Pod
metadata:
name: kubectl-get-secrets
spec:
containers:
- name: kubectl
image: bitnami/kubectl:latest
args:
- get
- secret
- secret-toview
serviceAccount: secret-viewer
serviceAccountName: secret-viewer
结论
在本节中,我们介绍了 RBAC 以管理 Secrets。我们确定了集群内对 Secrets 可执行的操作,并区分了 ClusterRole 和 Role 对象。接着,我们着手保障集群内 Secret 的安全使用,并提供了细粒度的授权访问,无论是通过角色限制访问某个命名空间,还是在整个集群中授予访问权限。由于我们已完成授权访问的需求,接下来需要关注的是监控我们的 Secret 使用情况。
审计和监控 Secret 使用情况
为了记录和监控 Kubernetes 集群中的持续活动,我们可以选择进行审计。Kubernetes 集群中的事件会发送到输出流或保存为日志;这使得我们可以识别系统中发生了什么。
在我们的案例中,我们希望监控 Secret 的使用情况。为了避免其他活动的开销,我们将仅专注于为 Secrets 生成的审计日志。
启用审计功能以监控 Secrets 的配置应如下所示:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
在 Kubernetes 安装中,可以通过使用 --audit-policy-file 标志,并在运行 kube-apiserver 时传递它来实现:
kube-apiserver --audit-policy-file=/path/to/audit-policy.yaml
minikube note
在 minikube 的情况下,我们需要在启动 minikube 时传递审计配置。
我们已经在 minikube-script.sh 脚本中总结了这些操作:
minikube start \
--extra-config=apiserver.audit-policy-file=/etc/ssl/certs/audit-policy.yaml \
--extra-config=apiserver.audit-log-path=-
既然我们已经启用了审计功能,接下来检查日志:
$ kubectl logs -f kube-apiserver-minikube -n kube-system | grep audit.k8s.io/v1
要触发审计事件,我们可以执行一个 Secret 操作:
$ kubectl get secret
最终,我们将收到以下日志:
{"kind":"Event",...,"verb":"list","user":{"username":"minikube-user","groups":["system:masters","system:authenticated"]},"sourceIPs":["192.168.49.1"],"...,"responseStatus":{"metadata":{},"code":200},...}
通过启用 Kubernetes 的审计功能,我们成功地跟踪和监控了 Secret 的变化。
摘要
在本章中,我们深入探讨了 Kubernetes Secrets。我们了解了不同类型的 Kubernetes Secrets 及其使用场景,并通过执行代码片段来展示这些用例。此外,由于 Secrets 包含敏感信息,我们进一步探讨了通过应用 RBAC 规则来确保对 Secrets 的访问安全。这帮助我们限制了对 Secrets 的访问,但也为我们的 Pods 提供了授权访问。我们还涵盖了审计这一方面。审计是一个非常重要的环节,因为我们希望能够全面控制对 Secrets 的访问以及其他操作。在下一章中,我们将重点讨论传输中的和静态存储中的 Secrets 加密。
第三章:使用 Kubernetes 原生方式加密 Secrets
在前两章中,我们一起回顾了关于 Kubernetes 架构和设计中Secret对象的基础知识,包括其架构、实现和使用方式。我们还明确了,Secret对象由于其未加密的特性,无论是键值对还是 etcd 数据文件,都在 Kubernetes 平台中不安全,这给您的业务带来了严重的安全隐患。
在本章中,我们将深入了解 Kubernetes 和 etcd,理解它们相关的安全漏洞以及如何缓解或减少这些漏洞。虽然这些解决方案可能被认为与容器平台的部署紧密相关,但由于操作系统和 Kubernetes 分发版的开源特性,几乎所有的内容都可以广泛应用。
本章重点介绍一种平台内方法,首先介绍 Kubernetes 原生加密设计,包括与密钥管理服务(KMS)连接的可能性,并以 etcd 硬化概述作为结尾。
在本章中,我们将涵盖以下主题:
-
无需任何外部组件的原生加密
-
使用外部组件的原生加密
-
etcd 及其他组件的静态加密
技术要求
为了将概念与实际操作示例相结合,我们利用了一系列常用的工具和平台来与容器、Kubernetes 和密钥管理进行交互。在本章中,我们将继续使用在前面章节中使用的相同工具集:
-
在用户层面使用
systemd自动启动容器/Pods。 -
Podman Desktop (
podman-desktop.io) 是一个开源软件,提供图形用户界面,用于构建、启动和调试容器,运行本地 Kubernetes 实例,简化从容器到 Pod 的迁移,甚至连接远程平台,如 Red Hat OpenShift、Azure Kubernetes Engine 等。 -
Golang (
go.dev) 或 Go 是一种在我们的示例中使用的编程语言。请注意,Kubernetes 及其大多数第三方组件都是用 Go 编写的。 -
Git (
git-scm.com) 是一种版本控制系统,我们将使用它来恢复书中的示例,并在探索 Secrets 管理解决方案时加以利用。
此外,以下工具也将被介绍:
-
HashiCorp Vault (
www.vaultproject.io/community) 是一个社区版的 Vault,提供企业级服务,用于安全存储凭证、令牌等。 -
Trousseau (
trousseau.io) 是一个 KMS 提供程序插件,用于利用外部 KMS,例如 HashiCorp Vault、Azure Key Vault 或 AWS 等效服务
以下链接将为您提供与本书相关的数字材料:
Kubernetes 原生加密
在 etcd 中写入的有效负载数据未加密,而是以 base64 编码,几乎等同于明文。加密有效负载中的数据将防护上述保护机制,但不会 取代它们!
有趣的是,我们已经确认 Kubernetes 键值存储(也称为 etcd)除了网络部分外,并未提供任何加密功能,Kubernetes 也不提供像 HashiCorp Vault 或 Azure Key Vault 那样的高级 KMS 功能
然而,Kubernetes 项目在 kube-apiserver 中设计了一个 KMS 框架,该服务用于验证和配置 API 对象的数据,以利用以下加密提供者之一:
-
identity提供者是默认配置,这意味着不会对以 base64 编码的数据字段进行加密 -
aes提供者,具有两个选项aesgcm或aescbc,利用用户生成的随机加密密钥进行本地加密 -
kms提供者插件将kube-apiserver连接到外部 KMS,以利用封装加密原理
配置 KMS 提供者框架的方式是通过在 kube-apiserver Pod(重新)启动时启用其功能
我们通过以下方式启用 kube-apiserver 的此功能:
-
我们通过两个配置标志引用
kube-apiserver;一个是启用此功能并引用配置文件,另一个是启用当配置文件发生更改时自动重新加载 -
配置文件需要部署在每个控制平面节点上,路径和名称在配置标志中定义
让我们从配置文件开始,该文件基于引用 EncryptionConfiguration API 对象的 YAML 清单,内容如下:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- Secrets
- ConfigMap
providers:
- identity: {}
这个 YAML 清单是 kube-apiserver 默认配置的实际显式版本,即使在该功能被禁用时也是如此。该清单可以按如下方式阅读:
-
resources列表;引用要加密的 Kubernetes API 对象,可以是Secrets、ConfigMap或自定义资源,从 Kubernetes 版本 1.25 开始 -
providers列表的优先级;引用加密机制,可以是identity、aesgcm、aescbc或kms
如前所述,providers 列表具有优先级结构。这意味着 kube-apiserver 会按顺序解析列表,这可能会影响您的操作。将在实际操作示例中展示这一点
最简单的方式是使用前面的默认定义来设置我们的第一个 EncryptionConfiguration 文件,并确保它在每个控制平面节点上都正确部署
重要说明
此部署,除了位置和方法外,严重依赖于您的 Kubernetes 发行版,强烈建议您查阅相应的项目/供应商文档。
当使用来自 Kubernetes 项目的 Kind 时,它可以通过一个额外的卷定义进行简单引用,具体细节将在实践示例中展示。为了简化此过程,文件将被命名为 configuration.yaml 并部署在 /etc/kubernetes/encryption 文件夹中。
现在我们已经查看了 EncryptionConfiguration 文件,让我们来看看启用 kube-apiserver 并参考我们配置文件中提到的提供程序所需的标志。
以下是启用 kube-apiserver Pod 加密功能的标志概览:
-
启用并引用配置文件的标志如下:
--encryption-provider-config=/etc/kubernetes/encryption/configuration.yaml -
自 Kubernetes 1.26 起,自动重载配置文件更改的标志如下:
--encryption-provider-config-automatic-reload=true
以下 Pod 定义代码片段显示了如何放置这两个标志:
apiVersion: v1
kind: Pod
...
spec:
containers:
- command:
- kube-apiserver
- --advertise-address=10.89.0.2
- --allow-privileged=true
...
- --encryption-provider-config=/etc/kubernetes/encryption/configuration.yaml
- --encryption-provider-config-automatic-reload=true
现在我们已经了解了如何启用这些功能,让我们深入探讨提供程序选项。
独立的本地加密。
本地加密可以在无需任何额外软件的情况下启用,无论是在控制平面还是在 Kubernetes 集群之外。
identity
该提供程序是默认的 kube-apiserver 配置,相当于在将数据字段有效负载存储到 etcd 中之前不进行加密处理。
以下图示提供了加密工作流的概览:

图 3.1 – Kubernetes KMS 身份提供程序工作流
图示流程可以按以下方式解读:
-
用户创建一个
Secret对象。 -
kube-apiserver检查EncryptionConfiguration提供程序列表。 -
该提供程序指的是
identity。 -
kube-apiserver将 base64 编码的Secret存储在 etcd 中。
该提供程序不加密任何 Secret 数据字段有效负载,并且是 Kubernetes 安装时的默认行为。如果需要,它也可以用于用以下提供程序替换任何加密的 Secret。
aesgcm 和 aescbc。
该提供程序使用 Golang AES 加密库将列出资源的数据字段有效负载转换为加密格式。
该提供程序使用 高级加密标准(AES)并提供两种模式:
-
CBC,虽然被认为较弱但速度较快。
-
GCM,被认为在启用密钥轮换时更快且不那么弱。
从实现角度来看,我们对两者遵循相同的原则:
-
生成一个 32 字节(或更多)随机加密密钥,并以 base64 编码。
-
设置您选择的提供程序,
aescbc或aesgcm。 -
在
EncryptionConfiguration配置文件中引用密钥。 -
如果未启用自动重载,请重启
kube-apiserver。
这相当简单,无论是从流程还是 configuration.yaml 的角度来看:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aesgcm:
keys:
- name: key-20230616
secret: DlZbD9Vc9ADLjAxKBaWxoevlKdsMMIY68DxQZVabJM8=
- identity: {}
以下图表概述了在创建新 Secret 对象时的加密工作流:

图 3.2 – KMS aesgcm/aescbc 提供者的 Kubernetes 工作流
图表流程可以按以下方式解读:
-
用户创建一个
Secret对象。 -
kube-apiserver检查EncryptionConfiguration提供者列表。 -
该提供者指的是
aesgcm。 -
kube-apiserver使用提供的密钥对数据字段负载进行加密,按照提供者定义的方式。 -
kube-apiserver将加密的Secret对象存储在 etcd 中。
aesgcm 和 aescbc 提供者是易于实现的解决方案,用于加密来自已列出资源的数据字段负载。
然而,这种简单性是有权衡的;此解决方案使用的加密密钥再次以 base64 编码,引用在 YAML 清单文件中,并存储在每个控制平面节点的本地文件系统上。如果系统或磁盘/文件系统遭到破坏,恶意黑客可以检索加密密钥并解密在 etcd 数据文件中的负载。
最后,这些提供者面临多种漏洞,从填充 oracle 攻击到生日攻击,或者根据密钥使用的次数猜测加密密钥,这增加了对适当自动化密钥轮换策略的需求。
与外部组件的原生加密
原生加密可以通过利用额外的软件来启用,无论是在控制平面上还是在 Kubernetes 集群外部。
kms
Kubernetes kms 提供者是针对前述 aescbc 和 aesgcm 加密提供者的安全密钥暴露问题而提出的,提出了以下要求:
-
外部 KMS,如 Azure Key Vault、HashiCorp Vault 或 AWS Vault,用于利用信封加密方案的构造。
-
一个插件,称为 Kubernetes KMS 提供者插件,用于将
kube-apiserver与一个或多个外部 KMS 相连接。这种方法减少了为了支持每个 KMS 供应商所需的kube-apiserver开发、集成和维护。
KMS 加密设计采用信封加密方案,使用两把密钥的方式:
-
一个
kube-apiserver与 Kubernetes 集群相连接。 -
密钥加密密钥,也称为 KEK,用于加密 DEK。KEK 在 KMS 上生成并远程托管。
重要说明
虽然可以将一个单独的 KEK 托管在远程 KMS 上以服务多个 Kubernetes 集群,但不推荐这样做,因为如果远程 KMS 遭到入侵,它将成为单点故障和安全暴露的风险。建议为每个 Kubernetes 集群考虑一个专用的 KEK,并可能使用多个远程 KMS。
考虑到多租户需求,为每个租户拥有一个专用的 KEK 也是有意义的,这是一个在撰写本文时尚未实现的功能。
在本文写作时,Kubernetes 项目已经推出了 KMSv2,这是kube-apiserver的最新 KMS 提供者实现。
虽然高层次的功能目标相同,但设计和实现上略有不同。这些差异可能会影响你的合规性和监管需求:
-
在 KMSv1 中,每创建一个
Secret对象就会在事务过程中生成一个专用的 DEK,每个 DEK 都通过调用 KMS 使用 KEK 加密,这会影响在大型 Kubernetes 集群环境中操作时的性能。 -
在 KMSv2 中,
kube-apiserver在启动时(或在EncryptionConfiguration重新加载时)生成一个 DEK,调用 KMS 插件使用 KMS 服务器的远程 KEK 加密它,缓存 DEK,从内存中执行加密和解密,并且仅在重启或密钥轮换期间才会调用 KMS 进行加密。这种重新设计大大提高了大规模环境下的性能和弹性。
下图提供了 KMSv1 加密工作流的概述:

图 3.3 – KMS 插件 v1 提供者的 Kubernetes 工作流
图表流程可以按如下方式解读:
-
用户创建一个
Secret对象。 -
kube-apiserver检查EncryptionConfiguration提供者列表。 -
提供者指的是 KMSv1。
-
kube-apiserver生成一个 DEK。 -
kube-apiserver使用 DEK 加密数据字段负载。 -
kube-apiserver请求 KMS 插件进行 DEK 加密。 -
KMS 插件请求 KMS 使用 KEK 加密 DEK。
-
KMS 使用 KEK 加密 DEK。
-
KMS 插件将加密后的 DEK 返回给
kube-apiserver。 -
kube-apiserver将加密后的Secret和 DEK 存储在 etcd 中。
下图提供了 KMSv2 加密工作流的概述:

图 3.4 – KMS 插件 v2 提供者的 Kubernetes 工作流
图表流程可以按如下方式解读:
-
用户创建一个
Secret对象。 -
kube-apiserver检查EncryptionConfiguration提供者列表。 -
提供者指的是 KMSv2。
-
如果没有现有的 DEK,
kube-apiserver将生成一个。 -
如果生成了 DEK,
kube-apiserver将请求 KMS 插件进行 DEK 加密。 -
KMS 插件请求 KMS 使用 KEK 加密 DEK。
-
KMS 使用 KEK 加密 DEK。
-
KMS 插件将加密后的 DEK 返回给
kube-apiserver。 -
kube-apiserver将加密后的 DEK 存储在 etcd 中。 -
kube-apiserver使用 DEK 加密数据字段负载。 -
kube-apiserver将加密后的Secret存储在 etcd 中。
kms提供者在配置方面增加了复杂性。此方法符合所有需要外部密钥管理的法规,同时解决了我们的大多数(如果不是全部)洋葱层次。
KMS 提供者插件示例
正如前面所述,kms提供程序需要额外的第三方软件,称为kms提供程序插件,以连接kube-apiserver与外部 KMS,如 HashiCorp Vault 或插件使用的任何其他受支持的 KMS。
该插件将部署在控制平面节点上,作为一个本地 UNIX 套接字,直接与kube-apiserver进行交互,而无需通过可能存在安全风险的网络传输。
诸如Trousseau (trousseau.io)等的社区项目提供了这种能力,可以通过 HashiCorp Vault、Azure Key Vault 和 AWS KMS 扩展kube-apiserver的功能。
通过键值数据进行实际操作
在 Git 仓库中的ch03文件夹中,您将找到一个使用 Podman 或 Docker 部署新 Kind 集群的步骤说明,使用特定的集群配置文件可获得一个默认EncryptionConfiguration配置的可用实例。
操作指南包括一个简短的 Kind 配置文件介绍,允许我们为kube-apiserver启用特定标志,并解释如何通过配置文件将特定文件夹挂载到 Pod 中。这将帮助您未来与利用相同原则的其他 Kubernetes 发行版进行交互。
从那里开始,您将有机会实施每个提供程序,并创建和替换使用该提供程序加密的新修订版Secret对象。这种方法将突显从一个提供程序转移到另一个提供程序而不带来主要操作负担的能力。
最后,实际操作示例将帮助您验证每个Secret对象是否已使用适当的提供程序、密钥和版本加密,如果适用,可以直接从 etcd 数据存储中转储条目进行查看。
优先级
如前所述,提供程序列表具有优先级评估或者说在实施过程中要考虑的顺序:
-
创建新的
Secret时,kube-apiserver将使用列出的第一个提供程序来加密指定资源的数据字段有效负载。 -
读取现有的
Secret时,kube-apiserver将检查 Secret 头以定义 KMS 提供程序、其版本和相关联的密钥:-
如果有匹配项,将尝试解密
Secret数据字段的有效负载。 -
如果没有匹配项,将返回错误。
-
-
所有现有的 Secrets 可以通过更改提供程序列表顺序来替换为更新的修订版。最常见的例子是引入一个新的 KMS 提供程序,如
aesgcm,并用新的修订版替换所有未加密的 Secrets,这将使用aesgcm提供程序进行加密。此特定案例在实际操作示例中有所说明。
这个kms提供程序的实现示例展示了如何使用外部 KMS 服务。请注意,kms v1 提供程序在 Kubernetes 1.28 版本中已被弃用,取而代之的是更具韧性的kms v2,它能够承受网络分区。
深入了解如何保护 etcd
上一节描述了 Kubernetes 在应用层提供的原生加密功能,换句话说,就是如何保护敏感数据,以防Secret和ConfigMap对象在 Kubernetes API 服务器处理中被暴露。
根据部署类型,是否是本地部署还是云端部署,其他层级也可以通过加密受益,以避免或减少安全暴露:
-
在本地或云端使用物理或虚拟机进行自部署时,Kubernetes 的
EncryptionConfigurationAPI 对象以文件形式存储在磁盘上;访问此配置文件以及 etcd 数据文件,将导致所有在 etcd 中记录的敏感数据遭到泄露。 -
在使用云服务提供商的托管 Kubernetes 实例时,控制平面成为他们的责任。然而,并不是所有服务都是一样的,一些服务需要仔细检查配置,以确保您选择的云服务提供商在其基础设施层面处理了静态加密,并允许您启用 Kubernetes 的原生加密。
考虑到以下的洋葱图,我们可以将图示的组件列为潜在的暴露点,以应对未授权访问 etcd 中的数据,包括 Secrets 和 ConfigMaps。本节为您提供了关于每个组件的安全风险分析及相关的缓解措施:

图 3.5 – 将 etcd 安全暴露呈现为洋葱层
由于 Linux 和 Kubernetes 发行版的组合非常多,更不用说各种云服务提供商的选择,本章提供了一个关于关键值数据的广泛实践部分,并分享了其他组件的指南,以下参考资料将帮助您为系统实施安全和加固配置:
-
Tevault, Donald A. (2023). 精通 Linux 安全与加固:保护您的 Linux 系统免受网络攻击的实用指南,由Packt Publishing出版。
-
Kalsi, Tajinder. (2018). 实用 Linux 安全食谱:使用实用的配方保护您的 Linux 环境免受现代攻击,由Packt Publishing出版。
还值得注意的是,云服务提供商已经做了大部分的工作,负责加密静态磁盘和文件系统,以减轻相关的攻击向量。然而,他们都建议利用 Kubernetes 的EncryptionConfiguration API 服务器配置进行端到端加密。
Linux 系统加固
操作系统加固的艺术在于将访问漏洞的利用降到零。在我们的背景下,这意味着操作系统不允许远程访问到 etcd 数据文件。
重要的是要理解 Linux 发行版在系统安装的早期阶段就已包含安全配置文件的概念,利用像 SCAP 这样的标准,并根据你组织的需求帮助进行相关和一致的加固。部署时提供了一系列行业特定的配置文件,例如 CIS 基准和 NIST,帮助配置操作系统,使其符合所选的规定。这些规则在使用图形用户界面时会进行说明,或者可以在供应商文档中找到。无论你偏好的安装方式是文本、图形还是 kickstart,都可以从这种加固自动化中受益。
这种方法有助于减轻运维团队的压力。通过自动化相关的 100 多条具体配置规则,遵循像 PCI-DSS 这样的合规配置文件变得简单,而无需阅读其 360 页的要求。这将与 Red Hat 安全加固参考指南中的 190 多页内容相辅相成。
一旦系统部署了与你组织所在行业相关的适当安全策略,OpenSCAP 工具集可以用来扫描整个安装基础,为你提供以下内容:
-
每个 Linux 发行版的量身定制审计
-
一个可共享的审计文件,包含风险评分系统
-
一项缓解策略,涵盖最常见的工具集(Shell 脚本、Puppet、Ansible 等)
参见以下内容作为参考:
-
OpenSCAP:
www.open-scap.org/ -
OpenSCAP PCI-DSS 规则:
static.open-scap.org/ssg-guides/ssg-rhel9-guide-pci-dss.html -
Red Hat Enterprise Linux 安全加固:
access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html-single/security_hardening
加固 Linux 系统包括安装前和安装后的任务。为了避免重新部署操作系统,请在安装过程中启用适当的安全配置文件和磁盘加密。大多数 Linux 发行版,例如 Red Hat Enterprise Linux 9,都提供图形用户界面来设置特定的安全配置文件,并为你提供一份强制性的配置更改列表,以确保符合所选的配置文件。
GitHub 仓库在 ch03 文件夹中提供了两个示例,演示了使用安装程序图形界面和 kickstart 文件加固 Linux 系统。
Linux 数据加密
盗窃磁盘或服务器是一个严重问题,且发生的频率比我们想象的还要高。但这不仅仅是针对本地基础设施;云虚拟磁盘也可能被盗,原因是虚拟化程序的漏洞可能泄露磁盘文件,这意味着所有与你的业务关键系统相关的凭证 也会被泄露。
由于 etcd(截至本文撰写时)不提供任何加密功能,因此将存储在控制平面节点文件系统中的数据文件可以被访问并轻松读取,从而恢复我们的 Secret 对象负载,详见 第一章,理解 Kubernetes 秘密管理。这意味着任何本地部署和边缘计算的物理场景,都会在攻击者盗取磁盘或节点时导致安全漏洞。
为了解决这个问题,磁盘和文件系统需要加密。你可能会问,为什么两者都要加密?因为 Secret 对象用于访问你的云、应用程序和存储账户。
磁盘加密
FDE,有时称为自加密磁盘(SED),是一个有趣的起点,可以提供完全从主机卸载的加密过程,从而减少攻击面。它对操作系统和应用程序透明(无需维护驱动程序或库)。FDE 保证了在不同硬件和软件组合下的高兼容性、可支持性和可移植性。
所有 FDE/SED 磁盘都配有零长度认证密钥/密码,以便简化初始设置,尤其是在没有用户需求的情况下。定义认证密钥或密码时,DEK 会存储在磁盘上,并通过定义的自定义用户密钥进行保护。
该工作流程的好处如下:
-
防止磁盘被物理盗窃
-
在启动之前就保护数据
-
启用重新密钥选项以应对紧急事件和合规需求
陷阱如下:
-
启动时需要用户交互以输入认证密钥
-
丢失密钥意味着丢失数据
-
黑客攻击仍然是可能的
确实,这些磁盘的有效性可能受到挑战,不同的方法展示了如何访问并破坏这些磁盘上的数据,以下两个参考文献提供了相关信息:
-
由 Tilo Müller、Tobias Latzo 和 Felix C. Freilling(来自弗里德里希-亚历山大大学系统安全小组)编写的《硬件基础的全磁盘加密(不)安全性》:
www.cs1.tf.fau.de/research/system-security-group/sed-insecurity/ -
使用以下术语进行互联网搜索:
NSA 磁盘固件黑客
请注意,大多数在服务器和存储阵列中提供的专业级磁盘(无论是机械磁盘还是基于芯片的)都配有此类技术。这是要考虑的第一层硬件保护。
文件系统加密
在加密 Linux 文件系统时,可以考虑多种方法,包括开源和专有选项。在本节中,我们将从需求的角度出发,探讨三种从简单到复杂的开源解决方案。
普通设备映射加密
使用 dm-crypt 进行设备映射加密是一个明显且简单的选择,因为它对未分区的磁盘进行块级加密。该技术提供了磁盘级加密,并且可以通过所谓的垃圾随机数据访问,从而引入一种可否认的加密方法。
使用设备映射加密的好处如下:
-
提供全磁盘加密
-
不会暴露分区表,也不会暴露 UUID 或标签
-
在灾难发生时提供强大的解决方案(在 LUKS 设置中,如果头部被销毁,数据将丢失)
存在以下问题:
-
需要较高的设备映射掌握程度,以确保正确的配置
-
单一密码短语且没有密钥轮换,可能会在特定的合规/监管要求中产生潜在问题
-
没有密钥派生功能,这会降低在生成缺乏熵的密码短语时抵抗暴力破解攻击的能力
-
不支持固态硬盘上的 TRIM 命令
参考以下内容:
-
项目:
www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-crypt.html -
在 Arch Linux 上的实现:
wiki.archlinux.org/title/Dm-crypt
Linux 统一密钥设置(LUKS)
LUKS 可以被认为是一款通用的磁盘加密软件,其核心实现了安全的密码管理。这使我们能够克服与密钥派生、轮换和多重密码功能相关的一些(如果不是全部的话)难题。除此之外,LUKS 与逻辑卷管理(LVM)和软件 RAID 场景兼容,适用于应对不同需求以及合规/监管要求的有趣解决方案。以下是一些可以将 LUKS 与其他解决方案集成的示例:
-
在分区上使用 LUKS
-
在 LUKS 上使用 LVM
-
在 LVM 上使用 LUKS
-
在软件 RAID 上使用 LUKS
LUKS 还可以与其他解决方案互补,以提供额外的安全应对措施。一个普通的设备映射器和一个没有头部的 LUKS 实现将建立一个可否认的加密设备(没有头部)。这种方法也能满足密钥轮换要求,并具备密钥派生和多重密码功能。这可能是两全其美的解决方案,是我在不需要外部 KMS 的情况下最为青睐的选择。
参考以下内容:
-
在 Red Hat Enterprise Linux 上的实现:
access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/security_hardening/encrypting-block-devices-using-luks_security-hardening
设备映射器与 LVM 和 LUKS
如 LUKS 部分所列,使用 LUKS 实现磁盘加密有多种组合。然而,每种设置都有优点和折衷,甚至可能是重大的,比如以下几点:
-
LVM 在 LUKS 上简化了分区操作,并在加密时保护卷的布局,但它依赖于一个单一的加密密钥,所有卷都会被加密。
-
LUKS 在 LVM 上提供了灵活性,可以支持加密和未加密的卷,但由于暴露了卷的布局,它的维护复杂且安全性较低。
对于设备映射器和 LUKS 解决方案,使用 TRIM 配合固态硬盘可能会带来安全风险或作为一种安全响应。
在普通模式下使用 dm-crypt,如果启用了 TRIM,它最终会暴露加密方式,并可能泄露从已释放的块中获得的数据,进而发现加密模式。然而,如果没有对数据和不可否认加密的硬性要求,那么可以安全启用 TRIM,因为这样会显著提高性能。
使用 LUKS 时,头文件存储在设备的开始部分。如果需要更换密码,之前的密码将被撤销,TRIM 会帮助释放这些块。如果没有更换密码,攻击者可能会研究设备,获取旧的头文件并解密磁盘。
为了提高 LUKS 的安全性,可以使用(虚拟)受信平台模块(TPM)来存储并处理驱动器的自动解锁,从而在启动时避免手动输入密码。然而,如果服务器被盗,这可能会暴露密钥,进而暴露数据。
为了避免复杂性,可以依赖风险分析方法来定义在特定环境中的需求,并确保符合相关法规要求。然后,选择一种能够满足需求且与运营团队技能匹配的文件系统加密方法。
网络绑定磁盘加密(NBDE)
密钥管理服务,也称为密钥托管,在此背景下将加密密钥转移到远程服务,以避免之前解决方案中列出的一些陷阱。密钥存储在类似金库的数据存储中,因此需要高可用性和备份策略,以保证加密密钥的可用性和生存性。如果没有,磁盘上的数据将永远丢失。需要注意的是,备份也必须受到保护,以避免通过旁道或机会性攻击泄漏密钥。
网络绑定磁盘加密(NBDE)解决方案通过引入多层安全性来解决这些挑战:
-
它使用 HTTP/HTTPS 协议简化网络配置。
-
一组具有预定义法定人数的服务器,用于提供加密/解密功能。如果未满足法定人数,则不会发生解密,直到该实例重新上线。这就创建了网络依赖关系的概念,即磁盘必须连接到网络上,才能访问所有 NBDE 服务器,从而允许其内容的暴露。
-
对称加密密钥被分割到所有 NBDE 服务器上,并通过易于(重新)分发的公钥进行管理。
-
减少密钥管理,无需保险库,也不需要高可用性和备份。
-
当所有条件满足时,允许透明重启。
-
防止磁盘或服务器及其磁盘被盗,除非整个 NBDE 设置被盗。
该解决方案的实施并不像听起来那么困难。这样的实施可以在短时间内完成,同时满足最严格的合规性和监管要求。
请参见以下参考资料:
-
一个为 NBDE 设计的强韧 KMS 项目示例:
github.com/latchset/tang -
Red Hat Enterprise Linux 上 NBDE 实施的示例:
access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/security_hardening/configuring-automated-unlocking-of-encrypted-volumes-using-policy-based-decryption_security-hardening -
在 Red Hat OpenShift 上使用 Tang 服务器实施的 NBDE 示例:
docs.openshift.com/container-platform/4.13/security/network_bound_disk_encryption/nbde-about-disk-encryption-technology.html
传输
整个 Kubernetes 设计基于 API 驱动的架构。这意味着任何有效负载的交换都需要通过安全通道(使用传输层安全性 TLS)进行。如果没有,交换到 etcd 的数据将会从网络中可读,包括来自 Secrets 的敏感数据。*
大多数 Kubernetes 发行版默认启用 TLS,并为操作员提供 TLS 安全配置文件选项,以确保相互交互的服务和应用程序之间的兼容性。在 Red Hat OpenShift 中,精细化的配置方法允许操作员为入口、kubelet 和控制平面组件(后者包括 etcd)配置特定的 TLS 安全配置文件。
请注意,服务端点也可以通过由外部网络设备或软件(如负载均衡器)处理的 TLS 终止来强制执行。虽然这种方法可以确保从最终用户通过完全限定域名访问 API 服务器时的网络流安全,但如果内部 Kubernetes 网络流没有任何 TLS 终止,它将无法保护。因此,应该同时考虑这两者,以确保提高的安全态势。
请参阅以下内容作为参考:
-
etcd 传输安全模型:
etcd.io/docs/v3.5/op-guide/security/ -
Red Hat OpenShift:
docs.openshift.com/container-platform/4.13/security/tls-security-profiles.html#tls-profiles-kubernetes-configuring_tls-security-profiles
概述
虽然安全措施取决于组织的合规性和监管要求,但基于风险的评估将定义适当的行动,以加固您的信息系统。然而,保护 Kubernetes Secrets 不是可选的,而是必须的。
鉴于当前采用混合多云模式的趋势,无论是云端还是自管理的集群,一旦其中一个集群的 etcd 被攻破,可能会导致整个环境的安全受损。这类攻击利用集群内部的网络连接或一个集群管理工具,令令牌被记录在被攻破的 etcd 中。这样的场景将导致病毒式攻击,感染每一个连接的终端。
作为修复措施,原生 Kubernetes 加密——更具体地说,kms 提供程序——是所有主要云和软件提供商支持的最佳实践安全模式。
请记住,安全性不是一个有限的游戏,而是一个持续的努力。定期对您不断变化的环境进行审计和扫描,将为您提供最新的合规状态。它们还将帮助您建立一个任务积压列表,以减轻已知的漏洞和配置错误。
在下一章中,我们将讨论调试和故障排除技术,以分析在配置和使用 Kubernetes Secrets 时遇到的意外行为。
第四章:调试和故障排除 Kubernetes Secrets
到目前为止,我们已经确定了 Kubernetes Secrets 的攻击面。其中两个是静态加密和传输加密。之前,在 第三章,Kubernetes 本地方式加密 Secrets 中,静态加密帮助我们增强了静态和传输中的安全性。在本章中,我们将重点讨论可能与 Secrets 相关的调试问题。Secrets 在存储和提供 Kubernetes 环境中运行的应用程序和服务所需的敏感信息方面起着至关重要的作用。它们对我们的应用程序至关重要,了解如何有效地调试与 Secrets 相关的问题可以节省大量的时间和精力。
在本章中,我们将扩展以下主题:
-
讨论 Kubernetes Secrets 的常见问题
-
调试和故障排除 Secrets
-
调试和故障排除 Secrets 的最佳实践
到本章结束时,我们将掌握处理与密钥相关挑战的知识。同时,我们应该能够遵循一些解决方法,避免在调试与 Secret 相关问题时常见的陷阱。
技术要求
为了将概念与实践相结合,我们将使用一系列常用的工具和平台来与容器、Kubernetes 和密钥管理进行交互。对于本章,我们需要以下工具:
-
Docker (
docker.com) 或 Podman (podman.io) 作为容器引擎。 -
Golang (
go.dev),或 Go,是我们示例中使用的编程语言。请注意,Kubernetes 及其大多数第三方组件都是用 Go 编写的。 -
minikube (
minikube.sigs.k8s.io/docs/) 允许我们在个人电脑上运行单节点的 Kubernetes 集群,非常适合用于学习和开发目的。 -
Git (
git-scm.com) 是一个版本控制系统,我们将使用它来恢复书中的示例,同时利用我们对 Secrets 管理解决方案的发现。 -
Helm (
helm.sh) 是一个用于 Kubernetes 的包管理工具,我们将使用它来简化 Kubernetes 资源的部署和管理。 -
GnuPG (
gnupg.org/download/) 是 OpenPGP 的免费开源实现。OpenPGP 为数据通信提供加密隐私和身份验证。
以下链接提供了本书中使用的数字材料:
请注意,将提到 Kubernetes 发行版,如 Azure Kubernetes Engine、Rancher Kubernetes Engine 和 Red Hat OpenShift,但你不需要这些工作实例来执行动手练习。
讨论 Kubernetes Secrets 中常见的问题。
在前面的章节中,我们通过直接命令或使用 YAML 文件与 Kubernetes 进行交互。在应用这些 YAML 规范并执行命令时,沿途很容易出现一些错误。错误的 Secret 名称或 YAML 定义可能会导致大量的故障排除工作,以确定最初导致问题的原因。
基于这些原因,需要遵循某些原则:
-
YAML 文件是结构化的,可以作为可信数据源。
-
Secret 的可重用性最小化了错误。
-
自动化消除了容易出错的人为干预。
每次我们通过命令行想要使用 Secret 时都迫切地应用它,容易在规范中引入错误。通过 YAML 文件定义 Secret,使得通过编辑器检查结构变得容易,并确保我们得到了期望的结果。此外,YAML 文件提供了多次应用相同 Secret 的灵活性。如果发生错误,可以修复相同的文件并重新应用。
另一个原则是 Secret 的可重用性。可重用性可以通过多种方式实现。每次应用相同的 Secret 创建时,你遇到错误的可能性就越大。可以驻留在命名空间中的 Secret 可以被不同的组件使用。
Helm 和 Helm Secrets
当涉及到为 Helm 图表上的 Secrets 提供加密和解密功能时,我们可以选择 Helm Secrets 插件。使用 Helm Secrets,我们可以加密敏感数据以及驻留在 Kubernetes Secrets 中的机密信息,如密码、证书、密钥等。在 Helm Secrets 中有多种加密选项。包括 AWS KMS 和 GCP KMS 等云 KMS 选项。还有一种非供应商依赖的选项,即通过 gpg 命令行工具进行加密,它是 OpenPGP 标准的一个实现。
创建 PGP 密钥
使用 Helm Secrets 加密文件的一种选择是使用 PGP 密钥。PGP 使用公钥加密;使用公钥和私钥对。公钥用于加密数据,私钥用于解密数据。公钥可以分发,而私钥应保持机密。任何拥有公钥的人都可以加密信息;然而,解密只能通过持有加密密钥的参与者进行。
如果没有 PGP 密钥,我们可以按照以下步骤生成一个:
$ gpg --generate-key
该命令将生成带有一些默认选项的密钥,例如一年后过期和默认密钥大小。通过使用 --full-generate-key 功能,密钥创建时有更多选项。
会出现一个提示要求输入密码短语;忽略此提示并留空。如果未留空密码短语,则在每次解密时都需要输入,这使得操作难以自动化。
我们可以列出密钥并检索我们已经创建的密钥:
$ gpg --list-keys
00FFFE11421E1F1EED1EEE811E11E111D1111111
uid [ultimate] test-key-2 <kubernetes@secrets.com>
sub rsa3072 2023-08-27 [E] [expires: 2025-08-26]
现在我们已经生成了密钥,可以继续加密 Secrets。
加密 Secrets
假设我们有这个包含 Helm 值的文件:
jwt-key:
value: secret-key
假设我们已经拥有 GPG 密钥,Helm Secrets 将使用它来加密敏感值。我们需要创建一个 .sops.yaml 文件,指定要使用的 GPG 密钥:
creation_rules:
- pgp: >-
gpg-key
然后我们可以加密这些值:
helm secrets enc values.yaml
最终,我们的文件应该看起来像这样:
jwt-key:
value: ENC[AES256_GCM,data:9/0gmkCNm2DbEw==,iv:dbthtzx1t8KUHazh7v48T7ASep0rTbYJBrl/jEw6zWE=,tag:MLVXlruHpkMxYihPvNieVQ==,type:str]
sops:
...
pgp:
enc: |
-----BEGIN PGP MESSAGE-----
-----END PGP MESSAGE-----
version: 3.7.3
Helm 和 Helm Secrets 是许多工具中的两个,它们可以帮助我们以结构化格式组织我们的 Secrets,并将其保存在磁盘上加密和安全。Helm 和 Helm Secrets 是将 Secrets 保存在 YAML 格式中并通过模板化重用的例子。
总结来说,自动化和适当的 Secret 组织不仅能提高我们的生产力,还能大大减少对人工干预的需求。大多数时候,当出现错误时,很可能是人为干预引起的。
Secret 应用陷阱
在创建 Kubernetes Secrets 时,我们可能会遇到错误,这些错误可能由多种原因引起。错误可能来自无效的 YAML 语法、无效的 Secret 类型、缺失数据或编码错误。幸运的是,未能正确应用 Secret 会立即给我们反馈。干运行可以帮助我们在执行操作之前验证我们的操作。
重要说明
请注意,所有执行的 Kubernetes 命令都会对默认命名空间生效,除非另行指定或通过 Kubernetes 上下文进行配置。以下命令将对默认命名空间或已配置的命名空间生效。
干运行
在应用 Kubernetes Secret 之前,我们可以使用 --dry-run 选项来模拟创建或更新 Secret。通过将 --dry-run 与 kubectl 命令一起使用,我们实际上并不会执行任何操作。这是一个有用的功能,可以帮助我们在应用之前测试和验证我们的 Secret 配置,从而节省排查问题的时间。
例如,我们想从字面值创建一个 Secret:
$ kubectl create secret generic opaque-example-from-literals --from-literal=literal1=text-for-literal-1 --dry-run=client
secret/opaque-example-from-literals created (dry run)
这个输出可以确认我们的操作将会成功。我们可以更进一步,生成一个 YAML 格式的响应:
$ kubectl create secret generic opaque-example-from-literals --from-literal=literal1=text-for-literal-1 --dry-run=client -o yaml
secret/opaque-example-from-literals created (dry run)
apiVersion: v1
data:
literal1: dGV4dC1mb3ItbGl0ZXJhbC0x
kind: Secret
metadata:
creationTimestamp: null
name: opaque-example-from-literals
干运行可以帮助我们验证操作而不实际应用它,并且我们可以验证操作的结果,而操作不会产生任何效果。尽管 --dry-run 选项非常有用,但它无法帮助我们解决已成功应用但包含无效数据内容的 Secrets。一个常见的问题是 Secret 的 Base64 格式问题。
Base64
Base64 格式用于表示 Secret 值。默认情况下,在应用 Secret 时,如果值是纯文本,它将被编码并以 Base64 格式存储。另外,除了提供纯文本的值外,我们也可以提交已经编码为 Base64 格式的值。如果我们要提交的值本身是 Base64 编码的,这可能会导致问题。
比如说,一个 AES-256 密钥:
$ secretKey=$(openssl rand -hex 32)
$ echo "$secretKey"
80a3284da641ac728b5585fd913b0e60e9c4f61ffe2cfb6f456c16a312552e11
$ echo "$secretKey" |md5sum
6a59e95805ea05ff21a708038be9b130
echo "$secretKey"
打印出来的 AES 密钥已经使用 Base64 编码。通过 Base64 编码,二进制数据被表示为可打印的 ASCII 字符格式。这使得我们可以更轻松地在代码库中传递,例如通过环境变量。另外,我们打印了 Secret 的 MD5 哈希,稍后我们将使用该哈希进行故障排除。
现在让我们尝试使用之前通过openssl命令创建的 AES-256 密钥来创建一个 Secret:
$ kubectl create secret generic aes-key --from-literal=key=$secretKey -o yaml
apiVersion: v1
data:
key: ODBhMzI4NGRhNjQxYWM3MjhiNTU4NWZkOTEzYjBlNjBlOWM0ZjYxZmZlMmNmY jZmNDU2YzE2YTMxMjU1MmUxMQ==
kind: Secret
metadata:
..
type: Opaque
如我们所见,Secret 已经被编码。如果我们尝试将 Secret 挂载到 Pod 中,它将包含正确的值:
apiVersion: v1
kind: Pod
...
command: ["/bin/sh","-c"]
args: ["echo $(key) | md5sum"]
envFrom:
- secretRef:
name: aes-key
我们已经应用了它,现在来查看日志:
$ kubectl logs print-env-pod
6a59e95805ea05ff21a708038be9b130 -
校验和匹配。如你所见,我们更喜欢使用 MD5 哈希,而不是直接打印变量。在生产环境中打印变量可能导致数据泄漏。
我们将通过 YAML 文件尝试相同的操作,但我们不会对密钥进行编码:
apiVersion: v1
kind: Secret
metadata:
name: aes-key
type: Opaque
data:
key: 80a3284da641ac728b5585fd913b0e60e9c4f61ffe2cfb6f456c16a312552e11
一旦我们通过 Pod 中的环境变量尝试使用该密钥,我们就会遇到一个问题:
$ kubectl apply -f base_64_example.yaml
$ kubectl logs -f print-env-pod
fc7d115eb58e428c53b346659e7604d6
MD5 哈希不同。这是因为密钥是以二进制格式传递给 Pod 的。Kubernetes 识别到该 Secret 是 Base64 格式的;因此,在创建 Pod 时,它解码了 AES 密钥并将二进制数据作为环境变量放置。这可能会导致混淆并耗费数小时进行调试。
特定类型的 Secret
Kubernetes 为我们提供了特定类型的 Secret。这可能会让我们产生一种印象,即在应用 Secret 之前,会进行某些检查,检查 Secret 的格式。此行为可能会根据 Secret 的类型而有所不同。
TLS Secret
当我们应用 TLS Secrets 时,Kubernetes 不会对 Secrets 的内容执行任何检查。例如,我们将尝试使用无效的证书创建 TLS Secret:
apiVersion: v1
kind: Secret
metadata:
name: ingress-tls
type: kubernetes.io/tls
data:
tls.crt: aW52YWxpZC1zZWNyZXQ=
tls.key: aW52YWxpZC1zZWNyZXQ=
我们可能会认为应用 Secret 时会进行检查;然而,这种情况不会发生。Secret 会被创建,最终,当资源尝试挂载并使用它时,可能会导致问题。
基本认证 Secrets
基本认证 Secrets 与 TLS Secrets 属于同一类别。当应用基本认证 Secrets 时,不会进行验证检查:
apiVersion: v1
kind: Secret
metadata:
name: basic-auth-secret
type: kubernetes.io/basic-auth
stringData:
no-username: a-user
password: a-password
another-key: some-value
Secret 已被应用,尽管它显然是错误的。basic-auth Secret 应该包含用户名和密码;然而,我们添加了不同名称的变量。
docker 配置 Secret
对于 docker 配置 Secrets,Kubernetes 会进行验证,并在 Kubernetes 配置无效时发出错误。如果我们尝试应用一个包含无效 Docker 配置的 Secret:
apiVersion: v1
kind: Secret
metadata:
name: docker-credentials
type: kubernetes.io/dockercfg
data:
.dockercfg: |
UkVQTEFDRV9XSVRIX0JBU0U2NA==
当我们运行以下命令时,文件将不会被应用。我们提供的 docker 配置是错误的:
kubectl apply -f docker-credentials.yaml
The Secret "docker-credentials" is invalid: data[.dockercfg]: Invalid value: "<secret contents redacted>": invalid character 'R' looking for beginning of value
应用 Secret 时我们可能会遇到不同的错误。有时,我们甚至会创建一个错误的 Secret,而没有收到任何反馈。
到目前为止,我们已经识别了应用 Secrets 时可能遇到的问题,以及如何通过 dry run(模拟运行)来帮助我们。我们还识别了那些 Secret 应用正确但实际 Secret 内容错误的情况。最后但同样重要的是,我们对 Kubernetes 可能不对其进行任何验证的 Secrets 类型进行了概览。这引出了下一个关于故障排除 Secrets 的章节。
调试和故障排除 Secrets
调试 Kubernetes Secrets 是困难的。这主要是因为 Secrets 的问题通常是在其他依赖它们的组件失败时才会显现。例如,假设有一个 Ingress 部署使用了错误的证书。识别实际问题将是一个检查多个组件的过程,直到找到根本原因。在本节中,我们将学习调试常见 Secret 问题的工具和方法,例如不存在的 Secrets、配置错误的 Secrets 等。
describe命令
到目前为止,kubectl get一直是我们用来检索 Kubernetes 资源信息的主要命令。作为一个命令,它提供了一种快速获取感兴趣资源及其状态信息的方式。然而,kubectl get功能只能满足我们的某些需求。一旦需要更多信息,我们应该使用kubectl describe命令。通过使用kubectl describe命令,我们可以获取 Kubernetes 资源的详细信息。
假设我们在 Kubernetes 集群中有一个 Nginx 部署 Pod,我们将按如下方式描述该部署:
$ kubectl describe deployment.apps/nginx-deployment
Name: nginx-deployment
Namespace: default
CreationTimestamp: Wed, 28 Jun 2023 23:53:22 +0100
...
Pod Template:
Labels: app=nginx
Annotations: test-annotation: nginx
Containers:
nginx:
...
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-deployment-544dc8b7c4 (1/1 replicas created)
Events: <none>
kubectl describe命令为我们提供了所有关于资源的必要信息。如我们所见,它可以列出事件、标签、注解,甚至是仅适用于被检查资源的属性。
不存在的 Secrets
我们将使用describe命令来检查一个尝试使用不存在的 Secret 的部署。我们的示例将尝试挂载一个不存在的 Secret 卷:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
...
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: users-volume
mountPath: /users.json
volumes:
- name: users-volume
secret:
secretName: user-file
由于我们指定了一个部署,错误不会立即出现。我们的部署将进入持续的ContainerCreating状态,直到 Secret 可用。
在这种情况下,通过使用describe命令,我们将获取有助于识别问题的信息:
kubectl describe pod nginx-5d66b7fbc-7cb7g
...
Warning FailedMount 36s (x9 over 2m44s) kubelet MountVolume.SetUp failed for volume "users-volume" : secret "user-file" not found
由于describe命令,我们可以看到 Secret 不可用。
错误挂载的卷反馈不会立即显现。将 Secrets 映射到环境变量时,情况则不同。
举个例子,假设一个 Nginx Pod 通过一个不存在的 Secret 获取其环境变量:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx
envFrom:
- secretRef:
name: does-not-exist
restartPolicy: Always
让我们应用配置。几秒钟后,我们将看到 Pod 无法启动:
$ kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-pod 0/1 CreateContainerConfigError 0 3s
我们将使用describe命令深入探讨:
$ kubectl describe pod nginx-pod
Warning Failed 3s (x8 over 87s) kubelet Error: secret "does-not-exist" not found
Normal Pulled 3s kubelet Successfully pulled image "nginx" in 968.341209ms
正如预期的那样,由于 Secret 缺失,Pod 无法启动。这是我们通过describe命令检测到的问题。
配置错误的 Secrets
配置错误的 Secrets 可能是导致你花费数小时调试的原因。它们可能会影响应用程序的多个组件,导致广泛的故障排除。
一个复杂的场景可能是带有无效 SSL 证书的 ingress。我们之前创建了一个包含无效证书的 SSL 证书 Secret。我们将创建一个 ingress,使用这些 SSL 证书并尝试识别问题。
我们将应用的 Secrets 如下:
apiVersion: v1
kind: Secret
metadata:
name: ingress-tls
type: kubernetes.io/tls
data:
tls.crt: aW52YWxpZC1zZWNyZXQ=
tls.key: aW52YWxpZC1zZWNyZXQ=
ingress 如下所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
tls:
- hosts:
- bad-ssl
secretName: ingress-tls
rules:
- host: bad-ssl
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-service
port:
number: 80
在某个时刻,ingress 将开始工作。我们可以使用 kubectl get ing 命令来检查:
$ kubectl get ing
NAME CLASS HOSTS ADDRESS PORTS AGE
nginx-ingress nginx bad-ssl 192.168.49.2 80, 443 2m36s
最终,通过浏览器访问 ingress,我们将看到一个由 Kubernetes 生成的证书。如果我们没有提供自己的证书或提供了错误的证书配置,Kubernetes 将自动生成一个证书。
第一阶段是检查 Kubernetes 事件:
$ kubectl get events
...
11m Normal Pulled pod/webpage Container image "nginx:stable" already present on machine
此外,根据我们之前的工作,我们可以仅查看 ingress 的事件:
$ kubectl describe ing
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 35s (x2 over 37s) nginx-ingress-controller Scheduled for sync
最终,我们将通过查看控制器日志来识别问题:
kubectl logs -f ingress-nginx-controller-755dfbfc65-vf7v6 -n ingress-nginx
...
W0701 10:18:30.928989 7 backend_ssl.go:45] Error obtaining X.509 certificate: unexpected error creating SSL Cert: no valid PEM formatted block found
...
W0701 10:18:35.203316 7 controller.go:1348] Unexpected error validating SSL certificate "default/ingress-tls" for server "bad-ssl": x509: certificate is not valid for any names, but wanted to match bad-ssl
日志将引导我们找到配置错误的证书。我们的重点将在此。
故障排除和可观察性解决方案
到目前为止,我们的故障排除过程涉及了 kubectl 的使用,但这并非总是可行的。根据组织的不同,Kubernetes 安装中可能集成了各种可观察性解决方案。例如,Datadog 是一个流行的可观察性解决方案。在基于云的 Kubernetes 服务中,云服务提供商提供的可观察性工具与 Kubernetes 服务集成。这意味着,像 CloudWatch 和 Google Cloud Monitoring 这样的工具可以帮助我们在不使用 kubectl 的情况下识别问题。我们将在 第八章、《探索 AWS 上的云秘密存储》、第九章、《探索 Azure 上的云秘密存储》以及 第十章、《探索 GCP 上的云秘密存储》中看到更多相关内容。
我们看到了一些配置错误的 Secrets 场景。为了识别原因,我们遵循了一个流程,并使用了如describe等工具,同时查看了 Kubernetes 的日志。
调试和故障排除 Secrets 的最佳实践
当一个 Secret 错误时,它可能会以不易察觉的方式影响我们。我们可以采取自上而下的方法,从检查实际受影响的应用程序开始。最终,我们将找到日志指向配置错误的 Secret。一旦找到该 Secret,我们应该确认它是否已正确应用,或者它是否是错误的 Secret。
在评估 Secret 时,我们可以制作一个检查清单:
-
确保 Secret 存在。
-
检查 Secret 的值。
-
解码 Secret,查看它是否为期望的那个。
-
使用 MD5 哈希。
-
避免将 Secrets 下载到本地。
接下来要检查的是 Secret 是否应用错误。假设某个 Secret 错误地挂载到某个部署的 Pod 上。虽然可以不断尝试修改部署,最终找出问题所在,但这可能不会带来最佳结果。通过不断尝试直到发现不工作之处虽然简单,但会浪费时间,并且很难辨别实际发生了什么。
应用中可能出现的错误有很多,这会带来噪音,难以辨别问题所在。排除 Secrets 不是问题的可能性,让我们更接近问题的解决。Secrets 本身就是一个复杂的概念。
一种排除 Secret 使用错误的方法是使用一个简单的 Docker 容器并将 Secrets 挂载到该容器上。一个更简单的容器复杂度较低,可以最小化出错的可能性:
-
将 Secrets 挂载为环境变量到 Pod。
-
将 Secrets 挂载为文件到 Pod。
-
使用你选择的哈希算法确保它们是预期的 Secret。
通过遵循这种方法,可以最大限度地减少由 Secrets 引起的任何 Kubernetes 问题的可能性。
避免泄露 Secrets
在故障排除时避免泄露任何 Secrets 是至关重要的。当你遇到 Secret 问题时,通常很容易打开终端会话,连接到 Kubernetes Pod 并运行排查命令。容器中生成的日志会被写入标准输出(stdout)和标准错误(stderr)流中。Kubernetes 与许多流行的日志解决方案(如 AWS CloudWatch、Datadog 和 Google Cloud Monitoring)集成。通过在 Pod 上打印 Secrets,这些 Secrets 会被写入这些流中,并最终出现在集成的日志解决方案中。日志解决方案可能在一个组织内非常容易访问——甚至比直接访问 Kubernetes 集群中的 Secrets 还要容易。这种行为的结果是数据泄露,一旦发生,Secrets 必须被撤销,带来额外的工作量。
另一个最佳实践是在故障排除时避免将 Secrets 下载到本地。将 Secrets 下载到本地可能会导致违反组织已制定的信息安全政策。
小结
在本章中,我们深入探讨了调试 Kubernetes Secrets 的细节,并重点介绍了在 Kubernetes 集群中解决和调试与 Secrets 相关的常见问题。我们了解到保持 Secrets 有序和遵循最佳实践的重要性,以及人为错误可能带来的数小时排查工作。我们还介绍了如何识别 Secrets 问题的过程以及可以使用的工具来找出问题的根源。在下一章中,我们将重点讨论 Kubernetes Secrets 的安全性和合规性。
第二部分:高级主题 – 在生产环境中的 Kubernetes Secrets
在这一部分,你将探索与 Kubernetes Secrets 相关的更高级话题,包括安全性和合规性考虑、风险缓解策略以及灾难恢复和备份计划。最后,你将学习如何缓解安全风险,以及如何为 Kubernetes Secrets 建立灾难恢复计划和备份策略。
本部分包括以下章节:
-
第五章,安全性、审计与合规性
-
第六章,灾难恢复与备份
-
第七章,管理 Secrets 中的挑战与风险
第五章:安全性、审计与合规性
在前几章中,我们从设计、实施和运营的角度,建立了应对 Kubernetes Secrets 管理挑战的基础。我们还通过逐层剖析完整的基础设施,突出了关键关注点,同时考虑减少或缓解安全暴露的路径。然而,无论我们投入多少努力,以下问题始终会浮现:
-
我们的 IT 环境如何才能足够安全?
-
从控制和审计的角度,最佳实践是什么?
-
我的 首席信息安全官 (CISO) 要求是什么?
本章通过基于最后一个问题的逆向方法,开启本手册的高级主题,“我的 CISO 团队的要求是什么?” 这个问题的答案通常是以另一个问题的形式出现,比如“我的组织必须遵守哪些规定?”,这暗示了一个法律视角。
本章将扩展以下主题:
-
理解网络安全与网络风险管理的区别
-
最常见的合规标准
-
控制、审计和缓解安全风险的最佳实践
到本章结束时,你将知道如何通过一个简单的可重用蓝图,在人员、流程和技术层面解决安全问题。
技术要求
为了完成本章的实践部分,我们将利用一系列常用工具和平台,这些工具和平台通常用于与容器、Kubernetes 和 Secrets 管理进行交互。在本章中,我们将继续使用相同的工具集:
-
systemd在用户级别用于自动启动容器/Pods。 -
Podman Desktop (
podman-desktop.io) 是一个开源软件,提供图形用户界面,用于构建、启动和调试容器,运行本地 Kubernetes 实例,简化从容器到 Pod 的迁移,甚至能够与远程平台如 Red Hat OpenShift、Azure Kubernetes Engine 等连接。 -
Git (
git-scm.com) 是一个版本控制系统,我们将用它来展示本书中的示例,并在发现 Secrets 管理解决方案的过程中加以利用。 -
Kube-bench (
github.com/aquasecurity/kube-bench) 是一个社区工具,用于根据 CIS 基准对 Kubernetes 集群进行测量。 -
合规操作员 (
github.com/ComplianceAsCode/compliance-operator) 是一个社区工具,用于测量并修复 Kubernetes 集群上的安全控制。 -
HashiCorp Vault (
www.vaultproject.io/community) 是一个社区版 Vault,提供企业版服务,用于安全存储凭证、令牌等信息。 -
Trousseau (
trousseau.io) 是一个 KMS 提供商插件,利用外部 KMS,如 HashiCorp Vault、Azure Key Vault 及其 AWS 等效服务。
本书的 GitHub 仓库包含与本书相关的数字资料:github.com/PacktPublishing/Kubernetes-Secrets-Handbook。
网络安全与网络风险
尽管(网络)安全和网络风险的出版物多得足够,但它们常常被误认为是相同的概念。
本节的目的是帮助你反思如何将传统的做安全方式从以 IT 为中心的视角转变为实践安全,并全面理解组织的需求与要求。这将帮助你进行动态风险管理评估,以便实施适当的安全措施。
网络安全
大多数组织将安全领域划分给各自的 IT 部门,指定一组几乎是孤立工作的有限人员。这样做会给他们的网络安全任务带来三个限制:
-
仅限于与基础设施堆栈相关的有限范围,且对关键业务应用的考虑很少或没有考虑
-
对组织业务连续性计划的了解有限
-
运维团队应对安全事件的响应能力有限
该模型有双重方法:
-
以事件为驱动或反应式
-
封锁一切以应对监控和发现安全漏洞的能力减少
由于安全性被所有 IT 人员视为一种约束,从开发人员到各个领域的运维人员,都将会在内部事故发生时或当同一行业内有公开参考时做出剧烈的变化。
有些人认为他们已部署了监控和流程,能够帮助识别并推动事件解决。他们也同意缺少应急演练来训练 IT 人员,这与灾难恢复计划(DRP)类似。
在我们快速发展的数字经济中,这种网络安全模型无法有效回应我们的三个问题:
-
我们的 IT 环境能被安全到什么程度?
-
从控制和审计的角度来看,最佳实践是什么?
-
我的 CISO 有哪些要求?
确保该模型安全的一个典型例子是实施强密码政策,包括 30 天更换密码并附加多重身份验证,且与组织资源的集成有限。这种解决方案可能适用于开发团队,但不适用于主要使用短时效网页解决方案的业务团队。
网络风险
在这里必须考虑一种不同的方法:从业务角度进行风险管理,以应对各个组织部门的治理和合规要求。这将帮助在设计和实施安全措施时进行优先级排序,同时确保在组织层面获得适当的技能。这种方法要求对组织的运作有深入了解,从财务到售后支持,以防技术专家对安全工具的投资过少或过多,导致不同业务单位的安全工具效率低下。
像灾难恢复计划(DRP)一样,网络风险管理的目标是定义最小的要求集,以使组织能够在遭遇网络攻击时依然实现其业务目标。在构建这种治理框架时,我们不仅要回答前面提到的三个问题,还需要让董事会要求每个高管回答以下问题:
-
首席执行官(CEO):安全漏洞会对品牌造成多大损害?
-
首席财务官(CFO):安全漏洞会带来多大的财务影响?
-
首席信息安全官(CISO):我们是否在优先投资以改善我们的安全态势?
-
首席运营官(COO):我们的客户/合作伙伴是否愿意信任与我们组织做生意?
尽管网络安全被视为一项成本,网络风险管理通过提供报告能力,可以展示安全投资的商业价值以及与之相关的态势,并能提供接近实时的响应。
该模型中的一个典型安全示例是实施强大的登录凭证策略,采用一次性密码方法。此解决方案为使用基于 Web 的解决方案的组织员工提供了足够的安全性。员工如果需要访问或推送更改到应用程序源代码时,可以额外实施多因素认证并进行安全审计。
与传统网络安全方法相比,网络风险评估从安全、合规性和可用性的角度评估不同的风险级别,并根据与关键资产交互时的需要采取相应措施。
结论
单纯为了勾选安全(网络安全)选项而进行安全工作不会给组织带来任何好处,反而会让包括 IT 部门在内的所有员工感到沮丧,导致安全措施被禁用,影子基础设施被构建。
治理框架提供了关于如何在数字经济中应对网络攻击的愿景、使命和执行策略。添加一个风险管理框架将自然而然地将安全部分融入到你的 DevOps 实践中。此时,我们可以采用一种模型,帮助我们在考虑组织每个风险所需努力的同时,识别、保护、检测、响应并从安全漏洞中恢复。
作为本话题的后续,以下参考资料提供了有关此领域的更多框架细节:
-
国家标准与技术研究所(2018 年),改善关键基础设施 网络安全 框架。自行出版。
-
欧洲网络安全局(2023 年),供应链网络安全的良好实践。自行出版。
-
Brown, J. (2023 年),高管网络安全计划手册,Packt Publishing。
合规标准
合规概念是关于组织如何在尊重的基础上运作,考虑到特定行业、总部所在地和可能与之合作的国家的一套法律和政策,以确保隐私和安全。合规要求将决定组织治理的大部分要求。
任何企业可能需要遵守多个法规,取决于其活动。一个很好的例子是总部位于美国的软件公司,在全球医疗实践中利用人工智能 (AI),提供软件即服务 (SaaS) 和本地解决方案。这意味着以下的法律合规:
-
美国健康保险移植与责任法案 (HIPAA),用于处理患者记录
-
欧盟通用数据保护条例 (GDPR),欧盟关于收集、处理和存储来自第三方的个人用户数据的法规
-
支付卡行业数据安全标准 (PCI-DSS),用于处理支付卡时的国际行业标准
-
额外的法律合规要求,如欧盟人工智能法案,规范人工智能使用,网络弹性法案,涉及安全软件供应链,数据法案,支持跨云服务提供商的数据可携带性,以及网络信息系统 2 (NIS2) 指令,确保关键基础设施的安全和事件管理最佳实践
-
请勿(间接)(再)销售任何列入美国商务部制裁国家的解决方案
理解整体合规义务可能会令人不知所措。然而,大多数法规有重叠的安全控制规则,可用于简化或统一方法。这也是互联网安全中心 (CIS) 针对数字平台(操作系统、移动设备、服务器软件、云、虚拟化、容器等)创建综合基准的原因。
CIS 基准帮助任何组织的 IT 部门从安全控制和相关技术配置的角度衡量当前的安全态势,包括漏洞和缓解措施的详细信息。这些控制措施与最常见的监管框架(如 HIPAA、PCI DSS、SWIFT、ISO 27000、SOC 2 和 NIST)相对应。以下是 CIS 基准与 NIST 800-53 的映射示例:www.cisecurity.org/insights/white-papers/cis-controls-v8-mapping-to-nist-800-53-rev-5。
从 Kubernetes 的 CIS 基准视角(www.cisecurity.org/benchmark/kubernetes),截至本文撰写时的版本为 1.7.0,以下是需要考虑的控制措施,用以保障机密数据的安全:
-
--encryption-provider-config参数已根据需要进行适当设置:-
此控制描述涉及通过使用我们在第三章中探索的修复方法来加密
etcd键值存储,具体内容见键值 数据部分,以 Kubernetes 原生方式加密机密数据。 -
基准测试还包括通过检查
kube-apiserver是否使用--encryption-provider-config标志运行的审计。
-
-
aescbc、aesgcm或kms -
基准测试还包括一个审计,检查由
--encryption-provider-config标志引用的文件中的配置内容。 -
Secret、ConfigMap和TokenReview通过Pod和Service*Secret作为Pod环境变量,考虑到它在节点内存和应用日志中的潜在暴露* 基准测试还包括审计概览,帮助你在日志中执行与机密相关的环境变量搜索*etcd键值存储,这个主题将在第十一章中探讨,探索外部 机密存储 基准测试还包括审计概览,帮助你审查使用第三方解决方案的适当机密管理实施
请注意,一些法规可能包括物理安全控制措施,例如数据中心和物理服务器访问,这些内容不包括在 CIS 基准中。然而,从我们在第三章中对其安全性分析的洋葱视角出发,以 Kubernetes 原生方式加密机密数据,这些控制措施是相关的。
现在是更新我们的洋葱图表的时候了,以便新增表示持续努力以确保符合组织治理中描述的监管义务和标准的层级:

图 5.1 – Kubernetes 机密管理的安全态势框架
考虑到组织的治理结构和这个网络风险评估模型,这些附加层提供了持续的发现、评估、分析和(缓解)执行,因此我们可以定义并改善当前的安全性和合规性态势。
采用 DevSecOps 思维模式
在前面的章节中,我们起草了一系列最佳实践,可以通过以下关键点总结:
-
考虑组织的治理结构,包括他们必须遵守的监管框架
-
采用网络风险管理思维模式,定制适合每个业务单元的安全控制措施,而不是死板的一刀切的网络安全政策
-
将安全视为一种持续改进的努力,涵盖所有运营活动中的发现、分析和报告
我们在本手册中提到过几次安全态势这个术语。让我们在使用 NIST 800-37 规范时定义一下它:“企业网络、信息和系统的安全状态,基于信息保障资源(例如人员、硬件、软件和政策)以及用于管理企业防御和应对变化情况的能力。这与 安全状态同义。*”
一旦你根据像 CIS Kubernetes 基准这样的框架建立了当前的安全态势,那么就更容易识别差距,并通过当前和未来缺失的控制措施不断改善你的安全态势。
接纳这种方法意味着思维方式的转变,有助于持续审查组织的安全态势并突出需要缓解的差距。这也被称为 DevSecOps 实践。
在这一阶段,DevSecOps 的采用将自然而然地将治理和合规要求融入到整个 DevOps 周期中。这将缩短相关团队的反馈循环,从而在部署到生产环境之前改善合规性、安全响应和安全态势。
不要将 DevSecOps 实践视为一个独立的框架;而是将其视为对 DevOps 模型的补充。所有安全实践在每个步骤中都紧密相连:
-
规划:这包括在初步项目范围内的安全性、治理和合规要求。误区是将这些要求视为发布应用程序前的最后验证。
-
开发:将安全性融入组织的编码治理中,帮助减轻常见的安全暴露风险。它包括代码分析工具,帮助在代码提交之前发现漏洞。
-
CI/CD:这涉及执行安全测试,扫描代码和容器镜像中的漏洞。这有助于在漏洞进入生产环境之前发现并修复这些暴露点。
-
持续监控/审计:有助于在近实时中检测和响应安全暴露。这也有助于基于风险的方法,其中某些安全暴露可能被认为是可以接受的,但应密切监控。
在 DevOps 流程中加入安全性将鼓励所有团队合作,并促进共享责任和问责文化,从而在开发过程中更早发现安全暴露,推动高质量和安全的软件交付。
工具
从 Kubernetes 的角度来看,并根据其专门的 CIS 基准,Secrets 管理部分需要我们执行以下操作:
-
启用
etcd服务的加密功能。 -
为敏感数据负载(如
Secret以及ConfigMap对象)定义并执行资源访问策略。 -
考虑采用外部 Secrets 管理解决方案,以增强平台的安全态势。
-
审计所有与这些对象的交互,包括平台内外的操作。
为此,您需要一套工具,帮助您发现、分析并缓解潜在的安全暴露。我们将探讨五种工具:
-
Trivy,来自 Aqua Sec:这不仅可以帮助您扫描容器镜像的漏洞,还能发现与 Secrets 相关的安全暴露。
-
kube-bench:用于评估 CIS 基准。
-
合规操作员:用于评估支持的 OpenSCAP 配置文件并提供缓解路径。
-
StackRox:用于评估集群、容器镜像和网络流量的安全配置文件。StackRox 提供了一个全面的用户界面,并带有一个有用的基于风险的仪表板,按风险级别对暴露进行排名。
-
Kubernetes 日志记录:用于收集与平台、应用程序和用户之间交互相关的所有信息和审计痕迹。
让我们来看看这些工具的功能,并展示它们的使用方式。所有这些工具都能帮助您遵守法规,并启动 DevSecOps 模型,同时不断提升您的安全态势。
Trivy
由于容器镜像的不可变性,持续跟踪漏洞至关重要,可以使用像 Trivy 这样的扫描工具。该工具也特别关注 Secrets 管理,因为它能够针对与 Secrets 相关的特定安全严重性进行扫描。
使用 Trivy Kubernetes CLI,可以通过以下命令专门触发针对 Secrets 的扫描过程:
trivy k8s –severity-checks=secret –report=summary cluster
请参见本书 GitHub 仓库中的第五章文件夹,了解如何在本地 kind Kubernetes 集群上实现 Trivy 的示例。
kube-bench
您的 IT 部门可能已经为整个组织的基础设施投资了一个安全套件,但我们还是要考虑使用外科工具与通用工具的区别。
在将您的环境与 CIS 基准进行对比时,来自 Aqua Security 软件的kube-bench可能是您在 Kubernetes 集群中运行的最有趣工具之一。基准作业可以通过标准的 YAML 清单进行配置;您可以自定义它,使其适应您组织的治理需求。
以下是检查的一个示例:
---
controls:
id: 1
text: "Master Node Security Configuration"
type: "master"
groups:
- id: 1.1
text: API Server
checks:
- id: 1.1.1
text: "Ensure that the --allow-privileged argument is set (Scored)"
audit: "ps -ef | grep kube-apiserver | grep -v grep"
tests:
bin_op: or
test_items:
- flag: "--allow-privileged"
set: true
- flag: "--some-other-flag"
set: false
remediation: "Edit the /etc/kubernetes/config file on the master node and
set the KUBE_ALLOW_PRIV parameter to '--allow-privileged=false'"
scored: true
该控制示例评估特权容器——即绕过cgroup控制器限制的容器——是否能够提升为容器内的 root 用户,这也意味着操作系统中的 root 权限。
本示例展示了如何通过引入skip定义来禁用控制:
---
controls:
id: 1
text: "Master Node Security Configuration"
type: "master"
groups:
- id: 1.1
text: API Server
checks:
- id: 1.1.1
text: "Ensure that the --allow-privileged argument is set (Scored)"
Type: "skip"
scored: true
请参阅本书 GitHub 存储库中的chapter 5文件夹,了解如何在本地kind Kubernetes 集群上实现kube-bench的示例。
Compliance Operator
另一种在支持操作符的 Red Hat OpenShift 容器平台和 Kubernetes 发行版上利用kube-bench功能的方法是Compliance Operator。此工具根据 CIS 基准和其他安全内容自动化协议(SCAP)配置文件进行合规性检查,例如澳大利亚网络安全中心(ACSC)的 Essential Eight。
该工具不仅评估安全暴露,还提供修复功能。与扫描类似,每个缓解都有一个 YAML 定义,可以逐一或批量应用,这允许进行详细的测试和验证阶段。这也可以在现有集群上进行,以便在后期实施安全规则。
重要说明
kube-bench和 Compliance Operator 都需要特定的提升权限才能运行,这可能不适合生产环境。一种好的做法是在一个反映生产环境的测试环境中运行这些扫描和修复,然后再将安全规则应用到生产环境中。
StackRox
开源的 StackRox 项目旨在解决与 Kubernetes 编排的容器化应用程序使用增加相关的安全挑战。其快速采用创造了一个日益增长的安全暴露空间,这需要一个平台提供一系列全面的功能,以便能够做到以下几点:
-
通过修复路径管理容器镜像的漏洞。这在运行时减少已知的安全暴露。
-
通过监控应用模式并检测任何潜在的恶意进程迹象(也称为突变),来确保容器运行时的安全。响应可以是信息性的,或者与工作负载隔离。
-
通过提供图形化视图,确保网络流量的安全,展示同一命名空间和不同命名空间中的容器如何进行通信,并通过网络策略提供缓解路径,以限制恶意工作负载的横向移动。
-
通过扫描与顶级行业和监管标准进行对比进行审计和合规,帮助组织设定基准、改进并维持其安全姿态。
-
通过利用基于 API 的方法与开发管道进行集成,从而在开发过程中尽早启用持续的安全检查。
作为一个开源项目并集成在 Red Hat 的 OpenShift 容器平台中,StackRox 在综合平台中发挥着重要作用,帮助简化 DevSecOps 工作流中的安全部分。
请参阅本书 GitHub 仓库中的chapter 5文件夹,查看如何在kind Kubernetes 集群上实现 StackRox 的示例。
Kubernetes 日志
从合规性和最佳实践的角度来看,这是必须做到的,然而,市场上有各种各样的解决方案来完成这些任务,令人感到不知所措。因此,我们将通过 Kubernetes 标准 API 中的 Audit 对象定义来讨论这个问题。
Kubernetes 提供了一种简单的方法,通过启用相关资源的监控,在其审计日志中提供与安全相关的记录。以下是Secret和ConfigMap对象的示例:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
将前面的代码保存到名为auditlog-secrets.yaml的文件中,并使用kubectl apply -f auditlog-secrets.yaml命令将此配置应用于您的集群。
总结
虽然安全和合规性可能让人感到不知所措,并且被视为具有约束性,但本章草拟了一个组织在标准和集成工具支持下采纳 DevSecOps 思维方式的指导方针。
重要的是要认识到,安全性和合规性并不是一个固定的状态,没有单一且简单的方案可以保护您的组织免受数字威胁。 (网络)安全在使用风险评估方法量身定制并融入业务中时,会被整个组织所欢迎,从而消除了在所谓的安全环境中操作时所带来的约束感。当设计得当时,所有利益相关者将自然而然地参与到持续的评估、分析、执行和发现新政策、漏洞以及潜在持续攻击的工作中。
在下一章中,我们将通过介绍和实施 Kubernetes Secrets 的灾难恢复和备份策略来解决业务连续性问题。
第六章:灾难恢复和备份
本章将深入探讨 Kubernetes Secrets 的灾难恢复和备份。考虑到 Secrets 的敏感性质以及数据完整性和可用性的需求,强大的备份和恢复策略的重要性不容忽视。本章结束时,你将理解灾难恢复和备份在 Secrets 管理中的关键作用,涵盖服务可用性、潜在的安全影响以及严格的合规要求。此外,你还将能够制定符合组织基础设施和战略的灾难恢复计划,提升应对潜在灾难和挑战的能力。
本章将涵盖以下主题:
-
为什么灾难恢复和备份很重要
-
备份策略
-
备份中的安全性
-
设计灾难恢复计划
本章结束时,你将深入理解最佳实践、策略和工具,能够有效地保护 Kubernetes Secrets 免受灾难影响。这些见解将帮助你确保 Secrets 的安全性、可用性和弹性,为构建一个更安全高效的 Kubernetes 环境奠定基础。
技术要求
本章将深入探讨 Kubernetes Secrets 的备份和恢复策略,这是构建弹性应用程序的重要组成部分。为了全面理解和执行所讨论的概念,我们将使用一套行业标准工具。这些工具代表了现实场景中常用的技术栈,了解如何利用它们进行 Secrets 管理将增强你设计一个强大、安全的 Secrets 系统的能力。
以下是我们需要的工具:
-
minikube (
minikube.sigs.k8s.io/docs/):😃 这个工具允许我们在个人计算机上运行单节点 Kubernetes 集群,非常适合学习和开发用途 -
HashiCorp Vault (
www.vaultproject.io/):😃 我们将使用 Vault,这是一个安全的外部 Secrets 管理系统,演示如何备份和恢复 Secrets -
Helm (
helm.sh/):😃 Helm 是 Kubernetes 的包管理工具,我们将用它来部署应用程序和服务
Secrets 灾难恢复和备份简介
Kubernetes 生态系统提供了多种管理敏感数据和凭证信息的可能性,这些通常被称为 Secrets。在这个动态复杂的环境中,灾难恢复和备份的概念变得极为重要。在谈到备份时,根据 Secrets 管理架构的不同,可能涉及多种含义。
在 Kubernetes 中管理 Secrets 的一种方法是使用 Kubernetes 内置的 键值存储。所有集群数据,包括 Secrets,都存储在 etcd 中。如果发生灾难,etcd 的备份可以帮助恢复整个集群的状态,包括 Secrets。然而,在这种情况下,恢复过程可能会复杂,并且可能存在一些限制,尤其是在考虑到 Secrets 的敏感性质时。
另一种方法是使用集中式的 Secrets 管理系统,如 Hashicorp Vault,它可以与 Kubernetes 进行接口。在这种系统中,Secrets 不会存储在 etcd 中,而是存储在由相应工具保护和管理的中央存储中。在这种情况下,备份策略将涉及备份中央存储而不是 etcd。此方法提供了对 Secrets 更精细的控制,通常包括用于灾难恢复的复杂功能。
无论选择哪种 Secrets 管理方法,备份的本质都是相同的——在灾难发生时恢复 Kubernetes 系统到正常工作状态的方法。备份应该是全面的、定期更新的,并且安全存储,以确保在需要时能发挥作用。
Secrets 管理中灾难恢复和备份的重要性
Secrets 在任何环境中通常被视为 tier-0 服务。这意味着它们的重要性至关重要,因为像 API 密钥、密码、令牌、证书等 Secrets 在 Kubernetes 集群内的服务交互和安全通信中起着关键作用。如果它们丢失或泄露,影响可能是显著的并且广泛的。
服务可用性
首先,运行在 Kubernetes 平台上的用户应用程序高度依赖这些 Secrets 来进行操作。它们需要 Secrets 来连接数据库、对内部或外部服务进行身份验证、与第三方 API 接口等。如果 Secrets 管理系统发生故障或 Secrets 丢失,这些应用程序可能无法正常工作。这可能导致操作完全停滞,直到 Secrets 恢复,从而造成严重的业务中断。如果没有专门为 Secrets 定制的灾难恢复计划,恢复过程可能会比可接受的时间更长,导致延长的停机时间。在某些情况下,这种延迟可能会导致显著的业务和财务影响。
注意事项
Kubernetes 的外部秘密存储可能仍然会创建 Kubernetes 本地的秘密资源,并且可以被应用程序使用。在这种情况下,秘密存储故障不应直接影响服务的短期可用性。
娱乐挑战
如果没有合适的备份,秘密可能会在灾难性事件中永久丧失,比如系统崩溃或灾难。重新创建秘密往往是一个具有挑战性的过程。在许多情况下,这些秘密不能简单地重新生成,而必须通过额外的复杂程序。例如,从第三方供应商获得的秘密通常需要经过某些手续和安全检查才能重新发行。有些秘密可能基于特定硬件或定时信息,无法轻易重建。这突出了拥有可靠备份的必要性,以便在丢失时能够快速恢复秘密。
安全影响
此外,从安全角度来看,一个强大的备份和灾难恢复策略至关重要。在发生安全漏洞,秘密被泄露的情况下,能够通过信任的备份恢复并动态更新秘密,或者基于短有效期(TTL)将其旋转至安全状态,是至关重要的。这可以最大限度地减少暴露窗口和由泄露秘密的滥用可能造成的损害。
监管要求
最后,在许多行业中,恢复关键系统的能力(包括秘密管理)不仅是良好的实践,还是监管要求。未能遵守规定可能导致巨额罚款,更不用说由严重故障或数据丢失引起的声誉损害。
实际案例研究 – 备份秘密的重要性
不充分的秘密备份策略可能会导致安全风险和漏洞。没有妥善管理秘密可能会引发可用性和安全性方面的问题。为了理解备份的重要性,让我们深入探讨一些实际的案例研究。
案例研究 1 – 由于缺乏适当的备份导致的服务中断
想象一家依赖 Kubernetes 来管理应用部署的快速增长的电子商务公司。它遭遇了一个严重的系统故障,导致其 Kubernetes 集群崩溃。尽管它设法从备份中恢复了大部分服务,但它意识到存储在 Kubernetes 中的秘密(如支付网关的 API 密钥和数据库凭证)并未单独备份。
结果,电子商务平台下线,导致成千上万的交易被中断。公司需要几天时间才能重新生成秘密、重新配置它们并让平台重新上线。在此期间,由于销售损失和客户信任下降,它遭受了重大财务损失。
案例研究 2 – 由于备份安全不足导致的安全漏洞
在另一个场景中,考虑一家全球技术 SaaS 公司,它使用了一个与其 Kubernetes 集群集成的集中式秘密管理系统。虽然公司有一个强大的备份策略,但备份本身并未得到加密或适当的保护。
在 2022 年,一群网络犯罪分子设法突破了其备份存储并获取了未加密的机密信息。这些黑客利用这些机密信息进行未经授权的操作,从数据盗窃到向公司服务注入恶意代码。此次事件导致了大规模的安全漏洞,严重损害了公司的声誉,并造成了显著的财务损失。
尽管公司已经制定了备份策略,但由于缺乏访问控制以及备份本身没有加密和安全措施,导致了严重的事件。这突显了在管理机密信息的同时,对备份过程进行适当安全防护的必要性。
在 Kubernetes 环境中,备份和灾难恢复策略是机密管理的关键组成部分。通过从备份中快速恢复丢失或无法访问的机密信息,应用程序可以在系统故障或灾难发生时,最小化干扰,迅速恢复正常运行。此外,安全性得到了增强:在发生安全漏洞的情况下,系统可以通过可信的备份恢复到安全状态,从而最小化潜在的损害。一个健全的备份和灾难恢复策略能够降低风险,确保机密信息的可用性、完整性和保密性,对维持整体系统的运营至关重要。
我们将继续下一部分,深入了解 Kubernetes Secrets 的备份策略。
Kubernetes Secrets 的备份策略
在 Kubernetes 中,机密管理涉及处理高度敏感的数据,如 API 令牌、证书、密钥等。尽管这些数据的体量相对较小——例如,一个大型运输公司可能管理不到一 GB 的机密数据——但这些数据的敏感性和价值却非常巨大。
鉴于机密信息的至关重要性,拥有健全的备份策略至关重要,以最小化在备份过程中数据丢失的可能性。然而,值得注意的是,完全避免数据丢失可能具有挑战性,因此减轻策略应尽量将数据丢失降到最低。
需要做出一个重要的区分,即备份不应与审计日志混淆。备份策略专注于保持数据本身的价值,而审计日志则用于追踪谁访问了哪些数据,并提供一份事件的时间顺序记录,以确保问责制和可追溯性。在本节中,我们将探讨各种备份策略,并重点介绍它们的优势及潜在挑战。
地理复制/跨区域复制
地理复制确保数据在不同地理位置之间可用。主要有两种模式需要考虑:
-
主动-主动复制:在这种模式下,数据同时写入多个区域。它提供了高可用性,适用于拥有全球用户群的应用程序。它确保在灾难发生时能够快速恢复数据,并将数据丢失降到最低。
-
主动灾难恢复(DR)复制:在这种方式下,一个区域是主要活动区域,另一个是备用灾难恢复区域。如果活动区域发生故障,灾难恢复区域会被激活。与主动-主动复制相比,这种策略更具成本效益,但根据同步频率,可能会导致稍微更高的数据丢失。
在机密管理系统中,地理复制有几个优势,例如地理冗余,它可以防止特定区域的灾难,并提高可访问性,允许用户从最近的区域访问数据,从而改善延迟。然而,尽管有这些好处,地理复制也带来了挑战。跨多个区域管理数据可能会增加复杂性,特别是在主动-主动模式下,数据复制的成本可能相当高。
时间点快照到不可变存储
该策略包括在特定时间间隔拍摄机密快照,并将其存储在不可变存储系统中。这种方法在备份类型上提供了灵活性:
-
完全备份:每次备份时都会捕获整个数据集。尽管这是最全面的方法,但如果频繁进行,会消耗大量资源并增加成本,因为每次备份时都会备份所有数据。
-
增量备份:与完全备份不同,增量备份方法仅备份自上次备份以来发生变化的数据。这意味着,如果用户从增量备份恢复数据,用户需要最新的完全备份和所有后续的增量备份。它比完全备份在定期备份时更节省存储空间,但恢复过程可能更复杂。
-
差异备份:这种方法备份自上次完全备份以来所有的更改,无论在此期间是否进行了增量备份。它在前两种方法之间达到了平衡。恢复比增量备份更简单,因为用户只需要最新的完全备份和最新的差异备份。然而,随着时间的推移,存储的数据量可能会比增量备份更大。
数据管理中的时间点快照具有显著的优势,例如数据完整性,其中不可变存储确保一旦数据写入后,无法更改,从而防止恶意攻击或意外删除。此外,它们允许从任何先前的时间点恢复数据。然而,采用时间点方法也带来了挑战。该方法的粒度意味着恢复点仅限于最新的快照,这可能导致部分数据丢失。此外,随着时间推移,维护多个快照会显著增加存储成本。
在传输过程中写入多个位置
在秘密数据到达最终存储目的地之前,它会首先被加密并写入事件流(例如 Kafka 或 Pub/Sub 系统)。然后,这些数据会被批量持久化到不可变存储中。采用实时事件流的方式来管理秘密数据具有明显的优势,例如冗余,其中数据的多个副本确保了高可用性,以及事件驱动的备份,这确保所有对秘密的更改都能即时备份。然而,这种方法也带来了一些挑战。管理实时事件流和批处理过程可能比较复杂,并且存在数据重复的潜在风险,这可能导致由于数据存储在多个位置而增加存储成本。
秘密版本控制和备份考虑事项
秘密版本控制是在数据管理中面临的独特挑战,尤其考虑到秘密数据的敏感性以及它们可能频繁变化的特性。以下是关于在备份过程中对秘密进行版本控制的一些考虑因素和策略:
-
版本控制的价值:
-
审计与合规:保留多个版本的秘密可以帮助更好的审计和合规性。它提供了不可变的变更历史,这在追踪未经授权的修改或理解变更时具有重要价值。
-
回滚:如果发生配置错误或新的秘密值出现问题,保留以前的版本可以快速进行回滚。
-
-
备份所有版本:通过实施包括所有版本的全面备份策略,用户可以确保完整的历史记录。这种方法在那些需要保持详细审计记录以符合合规要求的环境中特别有价值。这种方法的主要缺点是增加了存储开销。然而,考虑到秘密数据通常比较小,这额外的存储需求对于大多数情况下来说可能是微不足道的。
-
仅备份最新版本:采用高效的存储策略可以减少存储需求并简化备份过程,但它也带来了可能丢失历史记录的风险。如果只备份最新版本的数据,那么所有先前的版本及其变更历史将在数据丢失事件中丢失。如果历史数据不被认为是至关重要的,这种妥协可能在某些环境中是可以接受的。
-
混合方法:选择性版本控制允许用户根据秘密的优先级定制备份策略,选择保留某些秘密的多个版本,同时只保留其他秘密的最新版本。此外,实施保留策略可以进一步细化这一过程,在特定时间段后或达到某个版本数量时,将较旧版本归档或删除。
-
元数据和注释:这些用于跟踪与 Secrets 相关的信息,具有跟踪、审计和使用分析等功能。在备份过程中,无论用户是归档最新版本还是仅归档特定版本,备份相关的元数据和注释都是有益的。这些备份在恢复过程中提供了必要的上下文和额外数据。元数据和注释能够提供有价值的上下文和见解,供跟踪、审计和分析使用,因此应包括在备份中,以确保有效的恢复。
总之,高效地管理秘密版本需要平衡审计合规性和存储问题。从全面备份所有版本以详细记录到针对特定需求的选择性或高效方法,策略各异,但始终需要考虑将元数据纳入备份,以确保有效的恢复。
选择备份策略
为 Kubernetes Secrets 设计一个强健的备份策略至关重要。一项有效的策略能够在灾难发生时保护 Secrets,帮助防止服务中断和潜在的安全漏洞。策略的选择主要取决于 Secrets 的存储位置。本节将概述在决定备份策略时需要考虑的因素,分为共享考虑因素和存储特定因素:
-
备份粒度:在某些情况下,能够单独恢复特定的 Secrets 而不影响其他 Secrets 是至关重要的。
-
加密:确保备份数据加密至关重要,这可以防止未授权访问和潜在的数据泄露。
-
访问控制:实施严格的访问控制。将备份的访问权限限制为仅对那些绝对需要访问的人员,如特定的管理员组。
-
合规性要求:某些法规,如通用数据保护条例(GDPR)或健康保险流通与问责法案(HIPAA),可能会规定具体的备份程序。在设计备份策略时,了解并遵守这些法规非常重要。
-
存储开销:完整备份使用更多存储空间,但它是全面的。增量/差异备份通过只捕获变化来节省存储,但恢复过程会稍微复杂一些。
-
数据保留:定期修剪超过法规或业务需求的旧备份,以管理存储,或者使用数据压缩和去重技术来减少备份的大小。
-
长期存储成本:定期备份(例如,每 10 分钟一次)并长期保存(例如,13 个月)会累积大量存储。根据机密的关键性和变更频率进行调整。值得定期分析存储费用,并在成本超出收益时调整备份策略。对于有多个版本的机密,定期删除较旧且不必要的版本。根据备份的年龄和重要性使用不同的存储层级。将较旧的备份转移到成本效益较高的存储中。选择提供可预测成本的存储解决方案,以有效管理预算。
我们备份机密的方式受到我们管理机密方式的深刻影响。无论是将机密存储在 etcd 中还是在外部机密存储中,解决方案都会有显著不同。
存储在 etcd 中的机密
默认情况下,Kubernetes 中的机密存储在 etcd 中,但备份方法将更加面向 Kubernetes:
-
Kubernetes 集群备份:当机密存储在 etcd 中时,备份整个 Kubernetes 集群将涵盖所有机密。像 Velero 这样的工具可以备份和恢复整个集群,包括机密。
-
etcd 快照:定期的 etcd 快照是另一种选择,尽管这需要更多的技术专长和谨慎,因为它可能对整个 Kubernetes 集群产生影响。
对于存储在 etcd 中的机密,有两种备份方法:完整集群备份和 etcd 快照,每种方法都有独特的技术特性和影响。
存储在外部机密存储中的机密
许多外部机密管理解决方案,如 HashiCorp Vault 或 AWS Secrets Manager,都具有自己的备份功能。了解这些功能并将其集成到策略中非常重要。如果机密存储是云服务,使用相同提供商的备份服务可能会提供更好的集成和支持。
备份的安全指导
一旦我们决定了备份策略,必须遵循一些准则,以确保备份的安全:
-
隔离环境与访问控制:为了防止未经授权的访问,限制对备份环境的访问。可以通过以下方式实现访问限制:
-
IP 白名单:仅允许特定的 IP 地址访问备份存储,尤其是当它是外部存储时。
-
用户访问控制:区分普通用户和管理员。使用角色和权限授予不同的访问权限。
-
通过 VPN 访问:应该允许处于同一网络中的人员访问包含备份的资源。
-
零信任模型:实施零信任安全模型,默认情况下不信任任何用户或设备,无论是内部还是外部网络。
-
-
静态加密与可旋转的加密密钥:存储时对备份进行加密。始终使用现代加密标准。定期旋转加密密钥,并确保用户拥有安全的密钥管理流程。
-
传输中的安全性:在备份传输过程中确保其安全。采用如 HTTPS 或 TLS 等协议对数据进行加密传输。使用 VPN 或专用线路确保连接的安全。
-
审计和警报:实施访问模式监控,并为异常活动设置警报。记录所有访问和修改尝试。为可疑行为设置即时警报。
-
数据泄露的即时响应:如果备份遭到泄露,执行以下操作:
-
评估影响
-
轮换并更改所有密钥和凭证以防止解密
-
-
使用不可变存储保障:确保备份数据保持不变,使其不可修改。使用如 AWS S3 的 Object Lock 或 WORM 存储等功能。考虑保护时长与存储成本之间的平衡。
现在我们已经了解了有关 Secrets 备份的安全指南,接下来将探讨我们可以使用的工具,以备份存储在 Kubernetes 集群中的 Secrets。
用于备份 Kubernetes Secrets 的工具和解决方案
存在多种工具和解决方案,可以帮助用户备份 Kubernetes 环境中的 Secrets。适合用户的工具取决于具体的设置、需求和偏好。我们将探讨可用的工具以及如何将它们与我们的基础设施决策相结合。
Velero
Velero是一个流行的开源工具,用于管理灾难恢复和迁移 Kubernetes 集群资源与持久化卷。Velero 允许用户备份和恢复 Kubernetes 对象及持久化卷。
Velero 备份的示例配置如下:
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: aws
namespace: velero
spec:
provider: aws
objectStorage:
bucket: myBucket
config:
region: us-west-2
有关更多详细信息,请参阅 Velero 文档(velero.io/docs/v1.11/)。
etcdctl
etcdctl是一个命令行工具,用于与 etcd 配合使用,etcd 是一个分布式键值存储,提供可靠的方式在一组机器之间存储数据。Kubernetes 使用 etcd 来存储所有数据,包括 Secrets。
一个用于备份的 etcdctl 命令示例如下:
ETCDCTL_API=3 etcdctl snapshot save snapshot.db
查阅 etcdctl 文档(github.com/etcd-io/etcd/tree/main/etcdctl#snapshot-subcommand)以了解更多信息。
HashiCorp Vault
HashiCorp Vault是一个提供控制访问敏感数据和 Secrets 的产品,适用于分布式应用程序。它包括一个功能,用于快照其内部状态以进行备份。
对于 Vault 的开源版本,用户可以利用 Vault 操作员 Raft 快照功能。以下是创建 Vault 数据快照的示例命令:
vault operator raft snapshot save snapshot.hcl
然而,这需要额外的步骤,比如在 Vault 部署过程中将其集成到 sidecar 部署中。此外,必须手动实现一个周期性上传数据到指定存储位置的机制。
对于 Vault Enterprise,过程简化了,因为它包含了一个内置的备份功能。用户可以配置备份目标为 AWS S3、GCS 等。例如,在提供 AWS 凭证并设置间隔之后 (developer.hashicorp.com/vault/api-docs/system/storage/raftautosnapshots#interval),Vault Enterprise 会自动处理数据上传过程。有关更多详情,请参阅配置 developer.hashicorp.com/vault/api-docs/system/storage/raftautosnapshots#storage_type-aws-s3。
尽管如此,备份数据指标的监控依然至关重要。如果备份失败,应设置警报以触发提醒。有关更多信息,请参见 HashiCorp Vault 文档 (developer.hashicorp.com/vault/docs/commands/operator/raft#snapshot)。
以下是一个示例图,显示了备份侧车容器,它会拍摄快照并将其上传到 S3:

图 6.1 – Hashicorp Vault 备份侧车
因此,本节描述了 HashiCorp Vault 的备份功能;开源版本使用手动快照和数据上传,而 Vault Enterprise 通过 AWS S3 集成功能等来自动化这些过程。
AWS Secrets Manager
AWS Secrets Manager 是一个密钥管理服务,帮助用户保护对应用程序、服务和 IT 资源的访问。在 2021 年 3 月 3 日,AWS 推出了 AWS Secrets Manager 的一项新功能,使用户能够在多个 AWS 区域之间复制密钥,这可以自动复制密钥,并从恢复区域访问它,以支持灾难恢复计划。有关更多信息,请参见如何在多个 AWS 区域之间复制密钥,访问 aws.amazon.com/blogs/security/how-to-replicate-secrets-aws-secrets-manager-multiple-regions/。
Azure Key Vault
Backup-AzKeyVaultSecret cmdlet 通过下载指定密钥并将其存储在文件中来备份 Azure Key Vault 中的密钥。如果该密钥有多个版本,则所有版本都包含在备份中。
HashiCorp Vault、AWS Secrets Manager 和 Azure Key Vault 都是密钥存储的形式。总体而言,它们具有相同的特性,可以通过下图呈现:

图 6.2 – 备份密钥存储
每个工具和解决方案都有其独特的优点,适用于不同的密钥存储。请选择最适合您需求的工具,并在配置这些工具时始终考虑安全性和合规性要求。
接下来,我们将讨论 Kubernetes Secrets 的灾难恢复程序。
Kubernetes Secrets 的灾难恢复
Kubernetes Secrets 管理中的一个主要考虑因素是 灾难恢复。这包括为可能会极大影响 Kubernetes 环境,特别是其中存储的 Secrets,做准备并恢复。在本章中,我们讨论 灾难恢复计划(DRPs)、Secrets 恢复程序、相关工具和解决方案,并研究一个真实的灾难恢复场景。
Kubernetes 环境中的灾难恢复计划(DRP)
灾难恢复计划(DRP)是一个预定义和文档化的指令集,旨在指导组织从潜在灾难事件中恢复。在管理的背景下,精心设计的 DRP 对于减轻 Secrets 丢失或泄露的影响至关重要。
一个完善的 Kubernetes 环境灾难恢复计划(DRP)应包含以下元素:
-
明确的角色和职责:这涉及到指定恢复团队,并明确每个成员的角色和任务。
-
定义的通信策略:必须拥有一套健全的通信计划,涵盖内部(恢复团队)和外部(利益相关者)的通信。
-
准备的事件响应:这包括一套协议,用于迅速有效地响应,以最小化停机时间并限制损害的程度。
-
恢复程序:这些是针对特定场景的步骤,旨在从各种来源(例如,etcd、外部秘密存储)恢复 Secrets。请记住,这些程序需要经常测试,以确认它们在实际灾难中的有效性。
此外,由于这些 Secrets 的敏感性,它们的恢复应仅限于授权人员,并且在恢复过程中的操作应可审计,以保持安全性和合规性标准。
定期测试和更新
在 Kubernetes 环境中管理 Secrets,无论是存储在 etcd 内部还是外部,都是一项敏感且至关重要的操作。确保备份和灾难恢复系统的功能、安全性和更新性至关重要。定期测试和更新这些系统有助于实现以下目标:
-
确保系统的稳健性:通过模拟真实世界故障场景的游戏日(gamedays),经常测试备份和灾难恢复计划(DRP)系统。这种方法有助于识别 Secrets 恢复过程中潜在的缺陷或低效之处。测试应涵盖一系列场景,例如存储在 etcd 中的 Secrets 整个集群故障、外部存储位置的灾难性事件,或者备份系统本身的故障。游戏日提供了一种实际操作的方法,用于评估和改善系统的弹性和响应策略。
-
保持最新的备份:Secrets 经常发生变化,因此备份系统应设计为在 Secrets 发生变化时自动更新。定期测试有助于确保最新版本的 Secrets 始终被备份并能够正确恢复。
-
安全保障:鉴于 Secrets 的敏感性质,安全性至关重要。应定期对 Secrets 存储和备份系统进行渗透测试和审计,以发现潜在的漏洞。同时,应设置警报,通知相关人员安全问题或系统故障。
-
DRP 相关性:Kubernetes 环境是动态的,变化可能影响现有 DRP 的有效性。定期审查和更新 DRP 是确保其与不断发展的环境保持相关性的必要措施。尽可能地,自动化更新备份的过程,以便在 Secrets 发生变化时及时更新。
-
跨团队参与:定期让相关团队(如 DevOps、安全、IT 等)参与测试和更新过程,以确保全面审查系统。
Kubernetes 中的灾难恢复工具和解决方案
存在许多工具可以帮助 Kubernetes 中的灾难恢复。值得注意的包括 Velero、Kubestr 和 Kasten K10:
-
Velero:支持备份和恢复操作,并能够处理 etcd 数据,适用于备份 Kubernetes Secrets。
-
Kubestr:可以验证备份和恢复策略的有效性。
-
Kasten K10:提供一个全面的平台来管理 Kubernetes 应用数据,包括备份和恢复。
接下来,我们将通过检查灾难恢复场景来了解所有部分如何整合在一起。
有效的 Secrets 恢复场景:危机中的恢复
让我们来看一个简化的示例,描述一个成功管理灾难恢复场景的组织,OrgX,特别是处理 Kubernetes Secrets 的情况:
-
DRP:OrgX 已经制定了 DRP,并定期进行模拟或“演练日”,以验证其有效性。这一做法为团队应对实际灾难场景做好了准备。
-
团队协作:在灾难发生期间,一个跨职能团队激活了 DRP。不同部门的集体努力是有效执行恢复计划的关键。
-
保护 Secrets:作为恢复的一部分,团队从 AWS S3 存储桶中检索了加密的 Secrets 备份。由授权人员使用安全流程进行解密和恢复。Secrets 被恢复到密钥存储中,确保在整个过程中 Secrets 的完整性得到保持。
-
服务连续性:迅速采取行动并遵循 DRP,使 OrgX 能够迅速恢复其 Kubernetes Secrets。这一迅速响应最大限度地减少了服务中断,确保应用程序使用的是最新的密钥值。
OrgX 案例突出了在灾难恢复过程中,DRP、团队协作、安全措施和定期测试在恢复 Kubernetes Secrets 中的重要性。
总结
在本章中,我们深入探讨了 Kubernetes Secrets 的灾难恢复和备份。你了解了各种备份策略,如地理复制、时间点快照以及在传输过程中写入多个位置,并了解它们如何应用于不同的场景。我们讨论了在创建强健备份策略时,访问控制、加密和数据保留的重要性。我们强调了备份 Kubernetes Secrets 的关键安全措施,以及如何迅速有效地应对数据泄露事件。
通过我们的实践示例和详细讨论,你现在已经掌握了在 Kubernetes 环境中规划和执行全面灾难恢复计划(DRP)的知识。你还理解了定期测试和更新 DRP 以及备份系统的重要性。
随着我们进入下一章,我们将讨论在 Kubernetes 环境中管理 Secrets 时所面临的挑战和安全风险。这将帮助你了解潜在的陷阱,并为你提供克服这些挑战的策略,进一步增强应用程序的安全性和可靠性。
第七章:管理 Secrets 的挑战与风险
在 Kubernetes 环境中管理 Secrets 是保护敏感信息(如 API 密钥、密码和凭据)的关键组成部分。有效管理 Secrets 可以帮助防止未经授权访问重要信息,并确保 Kubernetes 集群中服务的正常运行。然而,这项任务也伴随着一系列挑战和潜在的安全风险,需要得到妥善处理。在本章中,我们将探讨管理 Kubernetes 中 Secrets 的各种挑战和风险,并讨论缓解策略,以增强 Secrets 的安全性。
到本章结束时,您将全面了解与 Kubernetes 中 Secrets 管理相关的挑战和风险。更重要的是,您将学到实际的策略来减轻这些风险,从而能够在 Kubernetes 环境中以安全高效的方式管理 Secrets。
在本章中,我们将涵盖以下主要主题:
-
理解 Kubernetes Secrets 及其安全风险
-
不同阶段管理 Secrets 时的挑战和风险
-
安全风险的缓解策略
这些信息将使您能够有效应对 Kubernetes 中 Secrets 管理的复杂性,确保敏感数据的机密性和完整性,并最终增强您 Kubernetes 应用的整体安全性。
技术要求
要跟随本章内容并实施所讨论的策略和实践,您需要以下技术和安装:
-
Kubernetes 集群:您将需要一个工作中的 Kubernetes 集群来管理环境中的 Secrets。您可以使用托管的 Kubernetes 服务,如 Amazon Elastic Kubernetes Service(Amazon EKS)、Azure Kubernetes Service(AKS)或 Google Kubernetes Engine(GKE),或者使用 minikube 或 Kind 设置本地集群。
-
kubectl:这是 Kubernetes 命令行工具,可以让您与 Kubernetes 集群进行交互。它是部署和管理集群资源的必备工具。
-
Secrets 管理工具:需要具备如何使用内部或外部工具管理 Secrets 的基本了解,或熟悉如 HashiCorp Vault、CyberArk、AWS Secrets Manager、Azure Key Vault 和 GCP Secret Manager 等工具。此外,建议您在阅读 第八章、第九章 和 第十章 后,重新阅读本章作为参考。
掌握 Secrets 管理系统的复杂性
Secrets 管理系统从 简单工具到复杂实体 发展,过程中面临独特的挑战和风险。这个过程包括多个阶段:
-
设置 Secrets 管理系统
-
实施细粒度访问控制
-
与目录服务集成
-
解决跨领域的关注点,如弹性、可用性和审计
-
符合合规性和监管要求
在 Secrets 管理系统的初始阶段,主要挑战是建立一个具有安全存储和加密的基本结构。在这一阶段,访问控制仅限于专属的管理访问,主要的安全风险涉及安全存储和加密的基本问题。这里的挑战是明确划分谁有管理员访问 Secrets 的权限。
随着系统的扩展以适应用户和服务调用者,挑战变得更加复杂。实施细粒度访问控制至关重要;这一点必须在不造成安全漏洞的情况下完成。此阶段还引入了认证风险,特别是在管理人工认证与机器或服务认证时。
下一阶段涉及与各种平台的集成,如活动目录、轻量级目录访问协议(LDAP)或特定操作符。这带来了新的挑战和风险。集成挑战包括确保无缝集成而不产生新的漏洞。同步数据时,尤其是在与像 LDAP 这样的系统进行同步时,数据同步也存在风险,特别是在保持数据完整性方面。委托授权,即将授权委托给其他系统并确保其安全,也提出了另一个挑战。
第四阶段引入了可用性功能、弹性、可用性和审计。在这一阶段,挑战包括安全地加密和访问远程备份,设计强大的灾难恢复计划(DR),以及实施安全和合规的审计跟踪。平衡可用性与安全性也成为需要解决的风险。
最后,第五阶段涉及遵守法规。这里的挑战是与法规对接,而不妨碍功能性。还存在与管理长期存储的法律要求相关的长期存储风险。这个阶段对于确保 Secrets 管理系统的持续合法性和安全性至关重要。
Secrets 管理系统的开发历程在每个阶段都会带来新的挑战和风险。从最初关注基本安全到复杂的集成、可用性增强、弹性、审计和合规,每一步都增加了复杂性。理解这些方面对于管理 Secrets 至关重要,特别是在像 Kubernetes 这样的环境中,Secrets 管理必须足够强大和灵活,以满足各种需求。
秘密管理中的一般安全风险
在第一部分探讨了秘密管理系统的基本机制和复杂性之后,我们将转向详细审视这一领域内具体的安全风险。本部分深入探讨了组织在管理秘密时面临的实际挑战。从单一主密钥问题到访问控制的增长与委派,再到与其他平台的集成,这些挑战需要周密的策略和解决方案。跟随我们一起分析这些安全风险,提供对其复杂性的洞察,并给出缓解策略的指导。
秘密零
秘密零指的是所有秘密都受到一个单一主密钥或最终秘密的保护,这个密钥控制着一切,就像传说中的“王国的钥匙”。
想象一下将所有密码存储在云端,并由一个单独的密码保护。然后,为了增加便利性,你将这个主密码放在云中的一个文本文件里。现在,黑客只需要发现或猜出这个密码,就能解锁一切。这个主密钥被称为“秘密零”。它就像是将所有鸡蛋放在一个篮子里,或者将所有钥匙锁在抽屉里,而抽屉的钥匙则在你口袋里。仅仅保护你的秘密是不够的;对这些秘密的访问也必须得到保护。
这种设置会形成一个重要的攻击点。这个密钥如果被泄露,攻击者将可以不受限制地访问,因为它能解锁其他所有内容。想象一下,你所有的密码和敏感数据都被锁在一扇门后,而这扇门的钥匙就放在门垫下。这就是秘密零的严重性。
风险与挑战
秘密零存在一些风险,缓解这些风险是具有挑战性的:
-
单点故障(SPOF):主密钥成为恶意攻击者的诱人目标。如果他们获取了这个密钥,就能访问一切。
-
管理主密钥的复杂性:保护这个主密钥变得至关重要且富有挑战性,因为其泄露可能会带来灾难性的后果。
-
潜在的内部威胁:拥有主密钥访问权限的员工或利益相关者可能会故意或无意地滥用该密钥。
-
合规问题:根据不同的监管环境,拥有单一主密钥可能会违反某些标准或最佳实践。
秘密零的解决方案
为了增强主密钥的安全性,一项有效措施是使用多因素认证(MFA),它通过要求多种身份验证方式才能授权访问,增加了安全性。另一种策略是密钥分割,即将主密钥分成几部分并分开存储,确保泄露一部分不会导致完全访问。除此之外,硬件安全模块(HSM)可以用来存储主密钥或其碎片,增加了一层物理保护以防止未经授权的访问。此外,夏密尔秘密共享(SSS)提供了另一种强大的技术。这种方法高效地将私密信息(“秘密”)分配给一个小组,确保除非小组中的多数成员共同行动,否则秘密无法泄露。最后,持续监控未经授权的访问尝试,以及定期审计访问日志,有助于及早发现可疑活动。
此外,对于像 AWS、Azure 和 GCP 这样的公共云提供商,由于现有的身份和访问机制管理角色和权限,秘密零问题通常不存在。但在本地或私有云环境中,问题依然存在,需要采取措施确保单一的秘密不会被窃取。
一些供应商选择使用 API 密钥和其他机器参数(如 CPU ID 或 IP 地址)进行机器身份验证。但这些方法可能会受到攻击——API 密钥可以被窃取,IP 等参数可能会被伪造,从而无法解决秘密零问题。
秘密访问膨胀
秘密访问膨胀指的是 IT 组织中对秘密信息访问的无理扩展。最初,秘密可能只由一个特定的 LDAP 组访问;然而,随着时间的推移,该组的规模可能会扩大,甚至包括其他子组,从而将访问人数从最初的几个个体膨胀到数百人。这种情况可能发生在外部工程师需要一次性访问某些秘密时,但并没有仅授予临时访问权限,而是某人将整个组添加为管理员,导致组人数急剧增加。以下示例说明了这种情况的发生:
-
组增长示例:一个最初由 10 人组成的 LDAP 组,增长到 400 人,包含多个子组,失去了其细粒度和特定性。
-
管理员组扩展示例:外部工程师被添加为临时管理员,但仍然留在该组中,导致管理员组呈指数级增长。
风险与挑战
对秘密访问的无差别扩展会随着时间的推移带来不断增加的风险:
-
细粒度控制丧失:如第一个示例中所示,从 10 人增长到 400 人,导致对谁有权访问秘密失去精确控制。
-
暴露于威胁的增加:将外部工程师增加为永久管理员会增加秘密被意外披露或滥用的风险。
-
管理复杂性:管理膨胀的访问权限变得越来越复杂且耗时。
-
合规性问题:访问权限的爆炸性增长可能导致违反合规性规定。
解决秘密访问膨胀问题
为了管理日益增加的秘密访问风险,可以采取几种策略。一项有效的措施是实施严格的组策略,例如防止在管理员组内创建子组,因为这有助于保持对秘密访问的控制。此外,定期扫描访问权限和组成员身份,可能每隔几周或几个月一次,可以帮助在问题变得严重之前识别出访问权限膨胀的情况。此外,实施基于角色的访问控制(RBAC)可确保用户仅能访问与其角色相关的信息,从而进一步减少秘密访问膨胀的风险。同时,即时访问(JIT)是一种安全方法,它限制了对应用程序或系统的访问,仅在特定的、需要的时间段内进行。另一方面,逐步认证要求用户进行与保护资源需求相等或更高级别的认证。
最后,将访问增长作为衡量指标进行追踪,反映任何增加的情况,并将这些指标与既定标准进行对比,可以作为访问膨胀的早期预警信号。这可以使组织在访问膨胀成为安全风险之前采取行动。
秘密代客泊车
在现代技术环境中,秘密代客泊车概念展示了一种常见的委托管理模式,用于管理机密信息。类似于将车钥匙交给代客泊车员,秘密被交给一个集成的子系统,该子系统将秘密传输到主机的文件系统中,以供特定工作负载或任务使用。
例如,在持续集成(CI)系统中,这可能涉及获取构建、测试和部署代码所需的所有秘密。虽然这种方法简化了工作流程,但必须小心管理,确保在秘密的用途完成后将其安全删除,就像代客泊车员停车后归还车钥匙一样。
这种便利性与安全性之间的微妙平衡反映了机密管理的复杂挑战,并生动地展示了在处理如秘密等关键资产时所需的信任、责任和小心。
风险与挑战
与秘密代客泊车概念相关的主要关注点和风险:
-
委托身份问题:信任子系统正确管理秘密需要信任该子系统在不再需要时能够安全删除文件中的秘密。如果未能做到这一点,秘密将暴露。
-
缺乏强制执行机制:如果没有适当的检查,无法保证集成系统在使用后成功删除秘密,从而可能导致未授权访问。
-
爆炸范围问题:如果密钥被提取并存储在整个主机上,而不仅仅是当需要时提取,它可能在主机或特定模块被攻击时导致广泛曝光的风险。
秘密代客停车的解决方案
为了降低与秘密代客停车相关的风险,可以采取几种策略。一个有效的方法是实施动态密钥,这些密钥按需生成,并且仅在短时间内有效,只有在特定工作负载需要时才提取密钥。使用后,这些密钥会被失效,从而最小化未授权访问和泄露的风险。
另一种方法是建立一个监控和验证系统,该系统持续观察并确认秘密的删除,确保符合预期的秘密处理协议。另一种方法是仅在特定工作负载需要时提取秘密。这可以最小化潜在的爆炸范围,减少不必要的暴露以及未授权访问的风险。最后,定期进行秘密管理流程的审查和审计可以确保遵守最佳实践,并识别在管理秘密方面的潜在改进。通过结合这些策略,组织可以减少与秘密代客停车相关的风险,并改善其整体安全态势。
秘密泄露
当提到秘密泄露时,我们指的是秘密在我们基础设施各个部分的广泛分布。这些秘密被分散并存储在许多地方,导致在管理和审计合规性时面临重大挑战。通常,您可能会发现一个数据库的用户名和密码被硬编码在应用程序的源代码中。它也可能以明文形式出现在配置文件中、配置管理中、版本控制中、Dropbox 账户中或 Wiki 中。从本质上讲,这些秘密分散在整个基础设施中,存在于不同的位置。
风险与挑战
阅读前面的内容就足以开始识别当秘密分散在整个基础设施中时可能出错的地方,包括以下几个方面:
-
缺乏知识:秘密无处不在,追踪它们几乎不可能。
-
访问控制有限:传统系统没有保持详细日志,也没有提供足够的访问控制,导致安全风险。
-
泄露响应:在发生泄露时,找出源头并处理问题变得复杂,尤其是当秘密被硬编码在应用程序源代码中时。
秘密泄露问题的解决方案
解决方案在于集中化。通过将密钥集中到一个具有严格控制的单一位置(例如 HashiCorp Vault 和 CyberArk),其管理变得更加安全。可以根据需要限制访问,并且审计日志提供详细信息,简化了对泄露的响应以及凭证管理的整个生命周期。
秘密岛屿
秘密岛屿指的是一种工具或平台,配备了用于管理密钥、访问控制、审计、合规性等的内置组件,但与其他工具缺乏互操作性,也没有集中管理策略和数据。
风险与挑战
在使用具有上述特征的工具时,以下风险和挑战可能会显现:
-
隔离:它将子系统隔离开来,使得整体密钥管理变得更加困难。如果没有细粒度的访问控制或安全存储,你必须处理零散的密钥,而没有集中式的监督。
-
缺乏整合:在安全岛屿中,你失去了整合审计和控制的能力。管理子系统变得混乱,缺乏对安全环境的统一视图。
-
可扩展性问题:例如,在 Jenkins CI/CD 流水线和 AWS 中使用不同的密钥来部署应用程序,最初可能会有效,但随着复杂性增加,可能变得难以管理且不安全。
-
人类安全岛屿:也叫做影子 IT,当安全变得过于复杂时,团队可能会绕过官方政策,进一步恶化安全态势。例如,想象在 Jenkins 中为暂存和生产环境使用不同的凭证,同时使用 AWS Secrets Manager 管理数据库密钥和 API 密钥。扩展这种配置变得十分困难,尤其是在委派、管理和审计方面。增加另一个团队、多个云平台,或者处理密钥一致性问题,只会加剧这一问题。
现在,让我们来看一下应对这一挑战的解决方案。
解决秘密岛屿问题的方案
为了降低与秘密岛屿相关的风险,实施集中式密钥管理至关重要。集中管理密钥使组织能够执行一致的策略,简化操作,并清晰地了解其整体安全环境。此外,制定标准化的安全协议,并强制各个秘密岛屿遵循,可以确保一致性并减少漏洞。利用允许不同秘密岛屿进行沟通和互动的集成工具和 API,可以帮助打破孤岛,推动更加统一的方法。定期进行安全审计和持续监控对于发现和纠正秘密岛屿中的不一致性和漏洞至关重要。此外,促进管理秘密岛屿的团队之间的沟通与协作,能够进一步增强组织的整体安全态势。通过这些缓解策略,组织能够有效应对秘密岛屿带来的挑战。
在管理秘密的关键领域,本部分深入探讨了几个既独立又相互关联的挑战和风险。从秘密零的问题,其中单点故障(SPOF)可能会危及整个系统,到秘密访问膨胀的问题,其中对访问控制的管理可能变得难以掌控,这些复杂性具有多面性。秘密代客泊车突出了集成系统中的信任与委托问题,而秘密蔓延和秘密孤岛则探讨了在日益复杂的环境中保持系统一致性和互操作性的问题。总的来说,这些话题强调了处理秘密时所需的细致且常常脆弱的平衡,突出了战略规划、警觉性和强大解决方案的必要性。
在上一节中,我们已经概述了与一般秘密管理相关的广泛挑战和风险,现在我们将重点讨论 Kubernetes(也称为 K8s)环境中的具体问题。
管理 Kubernetes 秘密的挑战和风险
Kubernetes 在秘密管理方面呈现出一系列独特的挑战和风险。本节将深入探讨在 Kubernetes 环境中管理秘密的具体细节,分析这一广泛使用的编排平台的独特特性和漏洞。请加入我们,一起探索 Kubernetes 秘密的复杂性,认识到其中的独特难题和应对这些问题的定制策略。
在进一步讨论之前,澄清一些与 Kubernetes 和秘密管理相关的概念是很重要的。Kubernetes 提供了一种名为“秘密”的原生资源类型,但使用 Kubernetes 的原生秘密资源并不是在 Kubernetes 环境中管理秘密的唯一方式。
在本节中,我们将讨论 Kubernetes 中两种不同的秘密管理方法:
-
直接使用 Kubernetes 原生秘密:这种方法涉及将 Kubernetes 内置的秘密资源作为秘密管理的主要机制。
-
利用 Kubernetes 原生秘密作为最终状态:这种方法使用 Kubernetes 的原生秘密资源作为 Kubernetes 平台内秘密消费的最终状态。从 Kubernetes 作为平台的角度来看待安全风险至关重要。
Kubernetes 的原生 Secrets 资源存储在 etcd 中,这是 Kubernetes 对象的主要数据存储。默认情况下,这些 Secrets 使用 base64 编码;它们没有加密,使得任何能够访问 etcd 的人都可以解码。未经授权访问集群的 API 服务器或运行使用这些 Secrets 的工作负载的节点,也会带来风险。为了增强安全性,Kubernetes 允许为 etcd 配置静态加密。有关启用和配置此加密的详细说明,请参阅官方 Kubernetes 文档:加密静态数据 (kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)。
此外,还有一些场景,其中外部 Secrets 管理系统在运行时直接将 Secrets 提供给 Kubernetes 工作负载。在这种情况下,大多数安全问题通常由 Secrets 管理工具处理,相关的安全风险可能会根据具体情况有所不同。有关 Secrets 管理的通用安全风险的更多信息,请参阅上一节。
管理 Kubernetes Secrets 的安全风险
Kubernetes Secrets 面临多个安全风险,包括在集群的 API 服务器或节点中的暴露。默认情况下,Secrets 存储在 etcd 中,这是 Kubernetes 对象的主要数据存储,使用 base64 编码,但没有加密。这使得任何可以访问 etcd 的人都可以轻松读取它们。此外,未经授权访问集群的 API 服务器或节点也可能导致 Secrets 的暴露。
Root 利用是另一个重要的风险。Kubernetes 并不会按 知情必要 的原则发送 Secrets。因此,任何具有根权限的节点用户都可以通过伪装成 kubelet 来读取任何秘密。
Secrets 通常会在 Kubernetes 清单中暴露。它们通常使用 JSON 或 YAML 文件配置,且密钥以 base64 编码。如果这些文件被共享或提交到仓库中,秘密就会被泄露。
通常,控制平面到工作节点 kubelet 的通信采用 TLS 模式,但 Kubernetes 没有原生功能加密跨节点传输的数据。重要的是在直接使用 Pod 时使用 Secrets,而不是在不传输的情况下转移 Secrets。
在 Kubernetes 中,虽然默认的 Secrets 管理系统允许使用 RBAC 创建自定义角色和绑定,但必须小心避免授予过于宽泛的权限,例如对所有资源包括 Secrets 的 "*" 权限。自定义角色应专门设计用于控制对 Secrets 的访问,定义谁有权查看、创建、编辑或删除它们。
日志记录和审计问题带来了额外的挑战。一旦机密被访问,它可能以明文形式记录,或者被传输给不受信任的方,从而使其易受攻击。此外,Kubernetes 并未提供针对 Secrets 的直接审计或变更管理功能。
最后,在 Kubernetes 中,由于缺乏零信任机制,机密访问通常是以未加密的形式由授权人员访问。这种情况表明需要更严格的访问模型。在这种模型中,即使是授权人员也应以确保安全的方式处理 Secrets,以符合零信任原则,即在每个阶段都进行验证。
缓解策略
在 Kubernetes 环境中保护和管理机密信息时,应考虑多个策略。
首先,推荐使用具有先进安全功能的集中式机密存储来管理 Kubernetes Secrets。这种方法不仅可以减少未经授权访问的风险,还可以简化管理过程,并提供全面的安全态势视图。常用的工具包括 HashiCorp Vault、CyberArk、AWS Secrets Manager 和 Azure Key Vault。
此外,应使用 Kubernetes 平台特定的配置来限制潜在的风险因素:
-
禁用 Pod 的 root 用户,方法如下:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-sample labels: app: nginx environment: production spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:latest ports: - containerPort: 80 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsNonRoot: true restartPolicy: Always -
加密 Secrets,无论是在传输中还是静态存储中,都是至关重要的。Kubernetes 原生的机密加密或第三方工具可以实现这一点。加密确保即使发生未经授权的访问,机密数据仍然无法被解密,除非拥有适当的解密密钥。
以下是启用
EncryptionConfiguration的示例用法:-
将您的
EncryptionConfigurationYAML 文件放置在 Kubernetes API 服务器运行的主节点上。 -
修改 API 服务器的启动参数,包含
--encryption-provider-config,并指向您的EncryptionConfigurationYAML 文件的文件路径。
执行以下命令启用它:
kube-apiserver --encryption-provider-config=/etc/kubernetes/encryption-config.yaml启用 API 服务器的加密配置后,您现在可以配置在 Kubernetes 集群中使用加密资源,如 Secrets:
--- # # CAUTION: this is an example configuration. # Do not use this for your own cluster! # apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - providers: - aesgcm: keys: - name: key1 secret: c2VjcmV0IGlzIHNlY3VyZQ== - name: key2 secret: dGhpcyBpcyBwYXNzd29yZA== resources: - secrets -
-
严格的访问控制至关重要。推荐通过像 RBAC 这样的机制来限制对 Secrets 的访问。重要的是定义细粒度的权限,明确谁可以访问、创建或修改 Secrets。
在 Kubernetes 中,针对秘密访问的具体审计涉及详细记录每次与秘密资源交互的日志。审计捕获关键信息,例如谁访问了秘密,何时访问,以及访问的性质。审计帮助管理员确定每个操作的具体细节,如发生了什么、何时发生、谁发起了操作,以及其来源和目的地。监控和审计对于监督秘密访问、时机和目的至关重要,有助于及时调查可疑活动以保护 Secrets。标准审计记录应显示谁在何时访问了什么。在 Kubernetes 中,所有对 Secrets 的访问都应进行记录,这些日志可用于在潜在泄露的情况下进行事件应对。
一个针对秘密访问的审计日志条目示例如下:
{ "kind": "Event", "apiVersion": "audit.k8s.io/v1", "metadata": { "creationTimestamp": "2023-12-01T12:34:56Z" }, "level": "Metadata", "timestamp": "2023-12-01T12:34:56Z", "auditID": "abcd1234", "stage": "ResponseComplete", "requestURI": "/api/v1/namespaces/default/secrets/mysecret", "verb": "get", "user": { "username": "admin", "groups": ["system:masters", "system:authenticated"] }, "sourceIPs": ["192.0.2.0"], "objectRef": { "resource": "secrets", "namespace": "default", "name": "mysecret", "apiVersion": "v1" }, "responseStatus": { "metadata": {}, "status": "Success", "reason": "" } } kube-apiserver --audit-policy-file=/etc/kubernetes/policy.yaml在 Kubernetes API 服务器中启用审计策略后,用户可以配置并指定特定资源访问的输出日志。以访问 Secrets 为例:
# Log secrets access within request and response. apiVersion: audit.k8s.io/v1 kind: Policy rules: - level: RequestResponse resources: - resources: - secrets apiVersion: audit.k8s.io/v1 kind: Policy rules: - level: RequestResponse resources: - group: "" resources: ["secrets"]可以使用命名空间隔离来分离敏感的工作负载。此外,可以使用网络策略来限制这些隔离命名空间之间的通信,从而减少 Secrets 暴露的潜在风险:
apiVersion: v1 kind: Secret metadata: name: test namespace: test type: Opaque data: password: dGVzdA== username: dGVzdA==
此外,避免将秘密存储在配置文件中(如 JSON 或 YAML 文件),这些文件可能会被提交到版本库或共享也是非常重要的。相反,应该使用环境变量或专用的秘密管理工具来存储秘密。
采纳零信任系统的概念也是一个明智的选择。实施仅在必要时解密秘密的解决方案,并防止任何人直接解密秘密是至关重要的。
在访问 Secrets 后,必须采取预防措施,确保秘密数据不会以明文形式记录或传输给不信任的方。
最后,定期轮换密钥和 Secrets 对于安全至关重要,组织通常遵循审计和合规政策,在设定的间隔(如 30、60 或 90 天)内进行轮换。国家标准与技术研究院(NIST)提供了关于密钥管理的详细指南,包括最佳实践和管理策略,详见其《特别出版物 800-57 第一部分,第 5 版》和《第二部分,第 1 版》。这些指南有助于确保即使密钥被泄露,其风险暴露也能最小化,因为它会在长时间内保持不可用状态。
通过采取这些策略,组织可以在 Kubernetes 环境中实现一个强大且安全的秘密管理系统。
总结
在本章中,我们集中讨论了机密管理背后的关键概念及其在确保数据保护和安全访问资源中的重要性。我们讨论了机密是如何创建、管理和在应用程序和服务之间共享的。我们探讨了管理机密时面临的关键安全风险、这些风险对机密管理构成的挑战,以及应采取的有效缓解策略。接下来,我们深入分析了 Kubernetes 机密的安全风险,包括在集群的 API 服务器或节点中的暴露、根用户漏洞、传输中缺乏加密、不足的访问控制等问题。随后,我们重点讨论了缓解策略,如使用机密管理工具、加密机密、实施访问控制以及监控和审计对机密的访问。在接下来的几章中,我们将看到如何处理在流行云服务提供商的机密管理和第三方机密管理工具中遇到的敏感问题。
第三部分:Kubernetes 机密提供者
在本部分中,您将了解外部机密存储及其在 Kubernetes 中管理机密的优势,以及如何将它们与 Kubernetes 集成。完成后,您将理解不同类型的外部机密存储,如何在 Kubernetes 中配置外部机密存储,并将其与现有的机密管理解决方案集成。
本部分包含以下章节:
-
第八章**,探索 AWS 上的云机密存储
-
第九章**,探索 Azure 上的云机密存储
-
第十章**,探索 GCP 上的云机密存储
-
第十一章**,探索外部机密存储
-
第十二章**,与机密存储的集成
-
第十三章**,案例研究与真实世界示例
-
第十四章**,总结与 Kubernetes 机密管理的未来
第八章:探索 AWS 上的云秘密存储
在云中存储秘密的一个非常常见的方法是利用云服务提供商提供的基础设施。主要的云服务提供商有两个重要的基础设施组件,可以帮助我们在 Kubernetes 上高效地管理秘密:秘密存储和 KMS。
本章将探讨 AWS Secrets Manager、弹性 Kubernetes 服务(EKS)的集成,以及使用 KMS 进行秘密加密。具体来说,我们将涵盖以下主题:
-
AWS Secrets Manager 概述
-
秘密存储 CSI 驱动程序
-
AWS EKS 集群和 AWS Secrets Manager
-
KMS 用于 AWS Kubernetes 加密
到本章结束时,我们应该能够将 AWS Secrets Manager 作为外部 Kubernetes 秘密存储来使用,使用 AWS KMS 对 Kubernetes 上的秘密进行加密,并通过 AWS CloudTrail 和 AWS CloudWatch 搜索秘密操作的审计日志。
技术要求
为了将概念与实践相结合,我们将使用一系列常用的工具和平台来与 AWS API 和 Kubernetes 交互:
-
需要一个 AWS 免费套餐账户。免费套餐对于新的 AWS 客户在限定时间内可用。一旦该期限到期或使用量超过免费套餐使用限制,将按需付费。
-
AWS CLI (
aws.amazon.com/cli/) 是一个统一的工具,用于管理 AWS 服务。通过 AWS CLI 执行的命令会被转换为对 AWS API 的 API 调用。 -
Terraform (
www.terraform.io/) 是一款基础设施即代码软件,可用于在云中配置和管理基础设施。 -
kubectl (
kubernetes.io/docs/reference/kubectl/) 是一个命令行工具,用于通过 Kubernetes API 与 Kubernetes 集群进行通信。
AWS Secrets Manager 概述
秘密是一个存在于 Kubernetes 范畴之外的概念。任何类型的应用程序都会在某个时刻需要将敏感信息与每个部署集成。在云中部署的应用程序需要安全地处理秘密。因此,云服务提供商提供了秘密存储组件。
在 Kubernetes 中,我们在 第一章,《理解 Kubernetes 秘密管理》中看到,秘密信息存储在 etcd 中。本质上,etcd 是 Kubernetes 的默认秘密存储。关键问题是,除了 etcd,是否有可能在 Kubernetes 上使用外部存储来存储秘密。
这是可行的,前提是你主动使用云服务提供商的秘密存储,或者考虑利用它并在 Kubernetes 上使用它。由于容器存储接口和工作负载身份的存在,我们可以利用现有的秘密存储。
AWS Secrets Manager (aws.amazon.com/secrets-manager/) 是 AWS 提供的秘密存储服务。使用 AWS Secrets Manager,我们可以存储多种类型的凭证,如数据库凭证、应用程序凭证和安全令牌。接下来,我们将重点介绍使 AWS Secrets Manager 成为处理密钥的优选方案的功能,从加密开始。
加密
AWS Secrets Manager 提供静态加密和传输加密功能。它使用 AWS KMS 执行封套加密。存储在 AWS Secrets Manager 中的值通过数据密钥加密,而数据密钥则使用 AWS KMS 进行加密。数据加密密钥是 AES-256 密钥。当 AWS Secrets Manager 中的值发生变化时,系统会生成一个新的数据加密密钥,并用它加密新值。我们还可以配置 AWS Secrets Manager 使用不同的 KMS 密钥。AWS Secrets Manager 还提供传输加密功能。与 Secrets Manager 的 API 调用通过安全的私有端点进行,每个调用都需要使用 X.509 证书或 Secrets Manager 秘密访问密钥进行签名。
版本控制
版本控制是使用 AWS Secrets Manager 的另一个好处。如果我们在 AWS Secrets Manager 中创建一个秘密,系统会为其分配一个版本号。这将是该秘密的第一个版本。一旦我们更新该秘密,就会获得更新后的版本,而旧版本仍然可用。当我们访问该秘密时,将检索到其最新版本。可以配置将先前的版本在某个日期自动删除,这样就无法再检索到它。如果我们想要检索先前的版本,必须先将其从待删除状态中移除。
密钥轮换
AWS Secrets Manager 还支持密钥轮换。可以配置一个 AWS Lambda 函数来执行定期的密钥轮换;AWS 文档提供了详细的指南(docs.aws.amazon.com/secretsmanager/latest/userguide/rotating-secrets.html)。在调用时,Lambda 函数会根据我们提供的自定义代码片段进行密钥轮换;函数模板可以在 GitHub 上找到(github.com/aws-samples/aws-secrets-manager-rotation-lambdas)。在轮换过程中,将会测试密钥的最新版本。如果失败,轮换将会重试。
基于云的功能
除了满足敏感信息存储需求外,AWS Secrets Manager 还是一个托管的 AWS 组件。每个 AWS 组件都有其特定功能:
-
AWS IAM 集成
-
使用日志记录和审计
-
高可用性和灾难恢复
-
与其他 AWS 组件的集成
让我们逐一看看这些功能。
AWS IAM 集成
通过 AWS 身份与访问管理(IAM),我们可以指定可以访问 AWS 服务和资源的实体。这些实体可以是 AWS 用户或 AWS 角色。
AWS 用户旨在与实际的人类用户关联,这些用户希望与 AWS 服务互动并配置资源。AWS 角色是一种更灵活的身份。AWS 角色用于委派对服务、EC2 机器和 Kubernetes 工作负载的访问权限。
AWS 上的身份可以被授予细粒度的 AWS 服务权限。
日志记录与审计
AWS 提供了 CloudWatch,这是一个监控和可观测性解决方案。CloudWatch 从其他 AWS 组件收集实时日志和指标。在我们的案例中,通过 CloudWatch,我们可以通过日志识别 Kubernetes 操作;同时,我们可以基于开箱即用的指标创建自定义警报和仪表板。
CloudTrail 是 AWS 的一项面向审计的服务。通过 CloudTrail,我们可以跟踪 AWS 用户或 AWS 角色执行的操作。这些操作将被记录并通过 AWS CloudTrail 进行访问。例如,假设 EC2 机器上附加的角色检索一个密钥。运行在 EC2 机器上的代码被授权接收该密钥,密钥被检索,并且此操作会被记录到 AWS CloudTrail 中。EC2 机器具有与 AWS Secrets Manager 交互权限的角色;该角色是一个 AWS 身份,类似于 AWS 控制台用户。
在下图中,我们可以看到显示审计信息的 CloudTrail 屏幕:

图 8.1 – AWS CloudTrail 屏幕
借助 CloudTrail,我们可以识别在 AWS Secrets Manager 中发生的操作、执行该操作的身份以及执行时间。
高可用性和灾难恢复
在 Secrets Manager 上配置的密钥是区域性高度可用的。这意味着密钥操作将跨越一个 AWS 区域的三个可用区。如果某个可用区变得不可用,密钥的请求将由另一个可用区提供服务。
此外,使用 AWS Secrets Manager,我们可以实现灾难恢复。默认情况下,当我们创建一个密钥时,我们会选择一个可用区,这个密钥将驻留在该区域。为了保护我们的工作负载免受某个区域丢失的情况,我们会以灾难恢复可行的方式配置我们的基础设施。AWS Secrets Manager 通过允许我们将密钥复制到另一个区域,使这一过程变得更加简单。
与其他 AWS 组件的集成
使用云提供商提供的组件的一个好处是它们的高度集成性。AWS Secrets Manager 可以轻松与其他 AWS 组件集成。在我们的案例中,我们感兴趣的是 EKS,它是 AWS 上 Kubernetes 的托管版本。
我们已经概述了 AWS Secrets Manager,并了解了它如何与其他 AWS 组件集成。接下来,我们将继续介绍如何将 AWS Secrets Manager 与 EKS 集成。为此,我们将概述一款促进这一集成的工具——Secrets Store CSI 驱动。
Secrets Store CSI 驱动
Kubernetes CSI 是 Kubernetes 的标准化接口,允许我们使用不同的存储提供商与 Kubernetes 配合使用。我们不仅限于使用 Kubernetes 上的默认存储,还有一个接口提供了一个规范,我们可以基于此规范构建存储驱动程序。通过这种方式,我们可以通过实现符合 CSI 接口的新驱动程序,使用多种存储类型。
这里是一些常用的 CSI 驱动:
-
AWS 弹性文件系统
-
Azure 文件
-
Google Cloud Filestore
这种方式适用于多种存储形式,也适用于云服务提供商提供的 Secrets 存储组件。
CSI 驱动由云服务提供商提供,用于秘密存储。Secrets Store CSI 驱动是一个专注于 Secrets 管理的 CSI 接口。我们可以通过它将 Secrets 挂载到 Kubernetes 中,利用另一种形式的存储。通过 Secrets Store CSI 驱动,我们可以从不同的外部来源获取 Secrets,而不是从 etcd 中消费 Secrets,在我们的案例中,这些来源正是由云服务提供商提供的 Secrets 存储解决方案。
在本节中,我们将重点介绍 AWS Secrets Manager,并探讨如何通过使用相应的 Secrets Store CSI 驱动将 Kubernetes 集群与 AWS 集成,从而使用云服务提供商的原生 Secrets 管理解决方案中的 Secrets。
Secrets Store CSI 驱动的工作原理
我们有一个应用程序需要托管在 Kubernetes 上。该应用程序必须使用存在于云服务提供商 Secrets 存储解决方案中的秘密。该应用程序将会驻留在 Pod 上,因为 Pod 是 Kubernetes 的主要计算构建块。
一旦 Pod 被创建、启动或重启,Secret Store CSI 驱动通过 Secret Store CSI 提供者将与云服务提供商的 Secrets 存储进行通信,并检索凭证。这些凭证将作为卷挂载到 Pod 中。挂载的卷将会附加到指定的目录。
下一个问题是 Secret Store CSI 驱动如何工作。
Secret Store CSI 驱动是一个DaemonSet。DaemonSet 会在 Kubernetes 的每个节点上存在。一个 DaemonSet 可以包含多个 Pod。
对于 Secret Store CSI 驱动,我们有以下 Pods:
-
node-driver-registrar -
secrets-store -
liveness-probe
node-driver-registrar
node-driver-registrar 将会把 CSI 驱动注册到 kubelet。会创建一个注册套接字,并通过 kubelet 插件注册表中的主机路径暴露出来。
secrets-store
secrets-store 组件负责在 Pod 创建过程中挂载机密卷,以及在 Pod 删除时卸载卷。它基于 gRPC 实现了来自 CSI 规范的 CSI 节点服务。
liveness-probe
这个 Pod 监控 CSI 驱动程序的健康状况。健康存活探针将检测驱动程序的任何问题,并会重启 Pod 来修复问题。在下图中,我们可以看到所有组件如何协同工作:

图 8.2 – Secrets Store CSI 与 AWS Secrets Manager 的集成
现在我们了解了 CSI 驱动程序,接下来将继续将其与 EKS 集成。
将 AWS Secrets Manager 与 EKS 集成
为了将 AWS Secrets Manager 与 Kubernetes 集成,我们将使用 Secrets Store CSI 驱动程序。正如预期的那样,AWS 为我们提供了 Secrets Store CSI 驱动程序 (github.com/aws/secrets-store-csi-driver-provider-aws)。为了将驱动程序与 Kubernetes 集成,我们将创建一个 Kubernetes 集群。
AWS 上的 EKS 集群
如前所述,AWS 中的 EKS 代表 弹性 Kubernetes 服务。我们有选项在 AWS 云上设置 Kubernetes 集群,就像在其他云平台上一样。为了本章的目的,我们将重点关注托管服务,因为它需要更少的维护和设置开销。
如果已有现成的 EKS 集群,则可以忽略 Terraform 配置说明,因为 AWS 的相应命令行参数会提供。
在 AWS 上实施 Kubernetes 集群可能需要不同程度的努力,具体取决于您想要实现的安装类型。一种选择是创建完全私有的集群,这些集群无法连接到互联网,所有服务都通过 AWS 内部提供。另一种选择是创建一个公有网络上的集群。如今,比较常见的选项是将集群的节点部署在私有子网中,并确保通过 NAT 网关实现与互联网的连接。这就是我们将要遵循的选项。
配置 Terraform 项目
在 AWS 上的 Terraform 项目要求我们存储状态。我们可以将状态存储在本地文件系统中,但这对于面向生产环境的基础设施即代码(IaC)来说并不可行。我们的选择是使用 安全存储服务 (S3) 桶来存储状态:
terraform {
...
backend "s3" {
bucket = "state-bucket"
key = "eks-state"
region = "eu-west-1"
}
默认情况下,Terraform 代码库将使用为 AWS CLI 配置的默认凭证。
我们首先需要使用 init 初始化 Terraform 项目:
$ terraform init
这个命令将初始化我们的项目。
使用 Terraform,我们可以使用 plan 选项,它类似于 kubectl 的 dry-run 命令。我们不会直接创建资源,而是使用 plan 来识别如果应用了 Terraform 脚本,基础设施的状态会是什么样:
$ terraform plan
为了应用基础设施,我们将使用 apply 命令:
$ terraform apply
Terraform 基础
在使用 Terraform 时,基础设施是通过 .tf 文件定义的。一个 Terraform 项目包含以下操作:
-
init -
plan -
apply -
destroy
到目前为止,我们已经看到 init,它用于初始化项目并下载现有状态;plan,它用于评估我们所做的更改;以及 apply,它是用来执行更改的命令。若要销毁已配置的资源,我们可以使用 destroy 命令。
这些操作可以在项目中执行,也可以针对 Terraform 文件中定义的特定资源执行:
$ terraform apply -target=aws_kms_key.a
对于现有资源,可以选择将这些资源导入 Terraform 状态并在 .tf 文件中提供资源定义:
$ terraform import aws_kms_key.a 136c1dcb-42b0-4b9a-a569-152b9aba63e1
Terraform 项目有推荐的结构。main.tf 是定义资源的主要入口点。随着基础设施的复杂性增加,可以使用更多的 .tf 文件。variables.tf 包含我们希望在配置基础设施时动态设置的变量,outputs.tf 应包含我们在使用 Terraform 配置基础设施时希望提取的信息。
Terraform 项目已设置完成,我们将通过 Terraform 配置 VPC。
创建 VPC
我们的集群节点将驻留在 AWS VPC 中。我们将使用 terraform-aws-modules 项目中的 VPC 模块(https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest):
module "eks_ksm_vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
name = "eks-ksm-vpc"
cidr = "10.0.0.0/16"
azs = slice(var.availability_zones, 0, 3)
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
...
}
通过此配置,我们将在三个可用区中扩展 VPC,从而实现高可用集群。
我们将在每个可用区创建一个私有子网以及公共子网。
Kubernetes 工作负载将托管在私有子网上,外部流量将通过公共网络访问 Kubernetes 工作负载。为了让 Kubernetes 集群能够访问互联网,我们将配置一个 NAT 网关。
由于我们已经配置了 VPC,现在可以继续配置 EKS 集群。
配置 EKS 集群
我们已经配置了 VPC,现在我们将继续配置 EKS 集群。我们将从 terraform-aws-modules 项目中选择 EKS 模块(registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest):
module "eks" {
source = "terraform-aws-modules/eks/aws"
...
cluster_name = var.cluster_name
vpc_id = module.eks_ksm_vpc.vpc_id
subnet_ids = module.eks_ksm_vpc.private_subnets
cluster_endpoint_public_access = true
eks_managed_node_group_defaults = {
ami_type = "AL2_x86_64"
}
create_cloudwatch_log_group = true
eks_managed_node_groups = {
one = {
...
min_size = 1
}
}
}
这将创建一个 EKS 集群。主节点由 AWS 管理。在创建集群时,我们指定节点跨越之前在 VPC 部分指定的私有子网。我们还将为 EKS 集群创建一个单独的日志组。
为了便于与 EKS 主节点交互,我们将配置集群端点为公开可访问。此选项可以进一步优化,并指定一组受限的 IP 地址,以便通过 kubectl 工具与集群进行交互。
一旦集群启动并运行,我们可以进行测试,甚至运行一些 kubectl 命令。
要对集群进行身份验证,我们将使用以下命令:
$ aws eks --region eu-west-1 update-kubeconfig --name eks-ksm-cluster
该命令会根据集群名称和选择的区域有所不同。完成后,我们可以使用 kubectl 指向最近配置的集群:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-10-0-2-231.eu-west-1.compute.internal Ready <none> 6m5s v1.24.15-eks-a5565ad
既然我们已经配置了 EKS 集群,现在可以继续与 AWS Secrets Manager 配合工作。
在 Secrets Manager 中创建机密
让我们使用 AWS Secrets Manager 创建一个机密:
resource "aws_secretsmanager_secret" "ksm_service_token" {
name = "service-token"
replica {
region = "eu-central-1"
}
recovery_window_in_days {
...
}
}
在 replica 块中,我们指定了复制机密的区域。这将为我们提供灾难恢复能力。同时,recovery_window_in_days 块定义了在设置删除操作后,我们能在多少天内恢复该机密。
现在,让我们为指定的机密添加一个版本:
resource "aws_secretsmanager_secret_version" "ksm_service_token_first_version" {
secret_id = aws_secretsmanager_secret.ksm_service_token.id
secret_string = "a-service-token"
}
这是我们与 AWS Secrets Manager 的第一次交互。我们已创建一个机密,并包含一个字符串的机密版本。
我们还将创建一个具有所需 IAM 绑定的角色:
resource "aws_iam_role" "eks_secret_reader_role" {
name = "eks-secret-reader"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
"Principal": {
"Federated": "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${module.ksm_eks.oidc_provider}"
}
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"${module.ksm_eks.oidc_provider}:aud": "sts.amazonaws.com",
"${module.ksm_eks.oidc_provider}:sub": "system:serviceaccount:default:service-token-reader"
}
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "esrrs" {
policy_arn = aws_iam_policy.ksm_service_token_reader.arn
role = aws_iam_role.eks_secret_reader_role.name
}
注意这个角色,它与我们惯用的角色非常不同。这个角色是专为 EKS 创建的,实际上它是映射到 Kubernetes 集群中角色的工作负载身份。
该角色在可执行的操作上有所限制。我们无法使用该角色对其他 AWS 资源执行操作。
或者,我们也可以使用 AWS CLI 来配置 Secrets:
$ aws secretsmanager create-secret --name service-token --secret-string a-service-token --add-replica-regions Region=eu-central-1
这将创建一个机密及其版本。
我们还可以使用 AWS CLI 创建角色和策略:
...
$ aws iam create-role --role-name eks-secret-reader --assume-role-policy-document file://eks-reader-trust-policy.json
...
$ aws iam create-policy --policy-name get-service-token --policy-document file://policy.json
...
$ aws iam attach-role-policy --role-name eks-secret-reader --policy-arn arn:aws:iam::$account_id:policy/get-service-token
我们有一个可以附加到 Kubernetes 并从 Secrets Manager 中获取机密的角色。下一步是安装 CSI 插件。
EKS 上的 AWS Secrets Manager CSI 提供程序
现在是时候在 EKS 上安装 AWS Secrets Manager CSI 提供程序了。首先要做的是将 Secrets Store CSI 驱动插件添加到 EKS 安装中。
让我们先在 EKS 上安装驱动程序,因为 secrets-store-csi 驱动程序不能作为附加组件使用:
$ helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
$ helm install -n kube-system csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver
$ helm repo add aws-secrets-manager https://aws.github.io/secrets-store-csi-driver-provider-aws $ helm install -n kube-system secrets-provider-aws aws-secrets-manager/secrets-store-csi-driver-provider-aws
以下命令将安装 secret-store-csi-driver DaemonSet。我们可以使用以下命令验证 secret-store-csi-driver 是否已正确安装:
$ kubectl get daemonset -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE <none> 47m
...
secrets-provider-aws-secrets-store-csi-driver-provider-aws 1 1 1 1 1 kubernetes.io/os=linux 44s
我们可以通过 Kubernetes 从机密存储中获取凭证。
从 Kubernetes 获取机密
首先,我们需要通过机密提供者类来映射机密。在对象中,我们指定要获取的机密名称:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: service-token
spec:
provider: aws
parameters:
objects: |
- objectName: "arn:aws:secretsmanager:eu-west-1:1111111:secret:service_token-IJ2VLg"
我们将 Kubernetes 密钥映射到 AWS Secrets Manager 提供的机密。
现在我们需要一个具有获取此机密权限的 Kubernetes 服务帐户:
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::11111:role/eks-secret-reader"
name: service-token-reader
namespace: default
在 eks.amazonaws.com/role-arn 部分,我们指定具有权限的 AWS 角色。
正如我们所见,服务帐户已用 AWS 角色进行了注解,这个角色是我们之前创建的 AWS 角色。
只要该服务账户附加到 Pod,它就会通过 Kubernetes OIDC 提供程序假设这个角色身份。有关 OIDC 和 Kubernetes 服务账户的更多信息,请参阅官方文档(docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html)。
当 Pod 创建时,它会使用该服务账户并挂载该秘密:
kind: Pod
apiVersion: v1
metadata:
name: nginx
spec:
serviceAccountName: service-token-reader
containers:
- image: nginx
name: nginx
volumeMounts:
- name: secret-from-asm
mountPath: "/mnt/secrets-store"
readOnly: true
volumes:
- name: secret-from-asm
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "service-token"
我们可以通过打印秘密来测试这个:
$ kubectl exec -it nginx cat /mnt/secrets-store/arn:aws:secretsmanager:eu-west-1:274402012893:secret:service_token-IJ2VLg
总结一下,我们创建了一个带有服务账户的 Pod,该账户映射到 AWS 角色。这个服务账户有权限与 AWS Secrets Manager 交互。因此,我们可以看到之前挂载的秘密。在接下来的章节中,我们将重点介绍如何通过审计跟踪 AWS Secrets Manager 中发生的操作。
审计
我们已经成功创建了 Secrets,并通过利用 AWS IAM 确保我们安全地接收它们。由于我们已经解决了安全访问问题,并且拥有细粒度权限,因此我们还希望记录对我们的 Secrets 管理实例的使用和访问。
有两种方式可以评估 Kubernetes 中发生的秘密访问:
-
Kubernetes Secrets 访问日志
-
AWS Secrets Manager 日志
当发生审计时,日志消息会打印在程序输出中。这个功能与 AWS 提供的日志解决方案 CloudWatch 和审计解决方案 CloudTrail 集成。
Kubernetes 日志在 CloudWatch 上
通过使用 CloudWatch,我们可以浏览 Kubernetes 日志。假设我们创建了一个名为 audit-test 的秘密。我们可以在 Logs Insights 中搜索任何操作(console.aws.amazon.com/cloudwatch/home?logsV2%3Alogs-insights=#logsV2:logs-insights)。我们可以使用以下日志:
fields @timestamp, @message, @logStream, @log
| filter @message like 'audit-test'
| sort @timestamp desc
| limit 20
结果条目将显示实际的结果:
| 字段 | 值 |
|---|---|
@``消息 |
{"kind":"Event",...,"verb":"get","user":{"username":"kubernetes-admin", "uid":"aws-iam-authenticator:274402012893:AIDAILH3OPGRUQEOHAR3O", "groups":["system:masters","system:authenticated"], "extra":{"accessKeyId": ["AKIAI5INYQBL233Y7J6Q"],"arn": ["arn:aws:iam::274402012893: user/ gkatzioura"],"canonicalArn": ["arn:aws:iam:: 274402012893: user/gkatzioura"], "principalId": ["AIDAILH3OPGRUQEOHAR3O"],"sessionName": [""]}},"sourceIPs":["90.221.185.67"],"userAgent": "kubectl/v1.25.4 (darwin/arm64) kubernetes/872a965","objectRef":{"resource": "secrets","namespace":"default", "``name":"audit-test", "apiVersion":"v1"},"responseStatus":{"metadata":...} |
@``时间戳 |
1692687754591 |
这个结果看起来很熟悉。它是我们之前在 第二章 中看到的日志,走进 Kubernetes Secrets 管理概念,当时我们正在审计 Kubernetes Secrets。
此外,CloudWatch 允许我们创建警报。如果存在意外的日志条目,表明访问或尝试访问某个密钥,我们可以配置 CloudWatch 创建警报,并通过我们选择的通信渠道通知我们,例如通过电子邮件。
AWS Secrets Manager 在 AWS CloudTrail 上的日志记录
AWS CloudTrail 是一个专门用于审计日志的服务。通过 CloudTrail,我们可以识别 Kubernetes 密钥的检索操作。
例如,我们可以使用此 URL 查看我们 AWS 账户中最近检索的密钥:console.aws.amazon.com/cloudtrail/home?#/events?EventName=GetSecretValue。
如果我们访问该 URL,AWS Secrets Manager 中的检索信息将会显示:
{
"userIdentity": {
"type": "AssumedRole",
"principalId": "id:secrets-store-csi-driver-provider-aws",
"arn": "arn:aws:sts::1111111:assumed-role/eks-secret-reader/secrets-store-csi-driver-provider-aws",
...
}
},
"eventSource": "secretsmanager.amazonaws.com",
"eventName": "GetSecretValue",
...
"requestParameters": {
"secretId": "arn:aws:secretsmanager:eu-west-1:11111111:secret:service_token-93z7he"
},
"responseElements": null,
...
}
用户身份是我们之前创建的 AWS 角色。事件名称是 GetSecretValue 事件。r``equestParameters 是执行操作的资源。
AWS Secrets 加密的 KMS
Kubernetes 允许我们在将密钥存储到 etcd 时指定特定的加密提供者。AWS KMS 可以作为加密提供者来加密托管在 etcd 上的密钥。
配置 KMS
我们将使用 Terraform 配置一个 KMS 密钥:
resource "aws_kms_key" "ksm_kms_key" {
description = "ksm_kms_key"
deletion_window_in_days = 30
enable_key_rotation = true
}
该密钥现在可以单独使用,也可以与其他 AWS 组件一起使用。
在 EKS 中使用 KMS
创建了 EKS 集群后,我们可以指定将解密密钥的凭证:
module "ksm_eks" {
...
create_kms_key = false
cluster_encryption_config = {
resources = ["secrets"]
provider_key_arn = aws_kms_key.ksm_kms_key.arn
}
...
}
如果我们尝试从 Kubernetes 获取凭证,密钥将使用我们指定的 KMS 进行解密。我们可以使用 AWS CloudTrail 来识别使用情况。我们可以通过以下链接访问 CloudTrail 事件:console.aws.amazon.com/cloudtrail/home?#/events?EventName=Decrypt。
如果我们访问该页面,解密操作将显示在屏幕上。同时,它将显示进行解密操作的用户,在我们的案例中是与 Kubernetes 集群关联的角色。
信息将以 JSON 格式呈现:
{
"eventSource": "kms.amazonaws.com",
"eventName": "Decrypt",
"awsRegion": "eu-west-1",
"sourceIPAddress": "secretsmanager.amazonaws.com",
"userAgent": "secretsmanager.amazonaws.com",
"requestParameters": {
"encryptionContext": {
"SecretARN": "arn:aws:secretsmanager:eu-west-1:1212222223:secret:service_token-93z7he",
"SecretVersionId": "278A157C-EA85-4211-9854-D329D3C9089F"
},
"encryptionAlgorithm": "SYMMETRIC_DEFAULT"
},
"resources": [
{
"ARN": "arn:aws:kms:eu-west-1:111111111:key/aaaaaaa-aaaa-458f-b8d1-aefa58b68d8a"
}
],
"eventType": "AwsApiCall",
}
我们通过使用 AWS KMS,将我们的密钥以加密形式存储在 etcd 上。存储在 etcd 上的密钥将被加密,我们应该能够监控任何加密/解密操作。
总结
在本章中,我们创建了一个跨越三个可用区的 VPC 网络。在该网络之上,我们配置了一个 EKS 集群。我们成功地将 EKS 集群与 AWS Secrets Manager 集成。这是通过在 AWS 上实现精细粒度的角色映射到我们的 Kubernetes 工作负载来安全完成的。接着,我们通过检查 CloudWatch 和 CloudTrail 日志,确定了与 AWS Secrets Manager 上存储的密钥相关的操作。最后,我们通过使用托管的 KMS 对存储在 EKS 集群上的密钥进行了加密。在下一章中,我们将重点介绍另一个流行的云服务提供商及其密钥处理能力:微软 Azure。
第九章:探索 Azure 上的云秘密存储
之前,我们深入探讨了 AWS 上的 Secrets Manager。本章中,我们将关注另一个受欢迎的云服务提供商——微软 Azure。我们将学习 Azure 提供的解决方案 Azure Key Vault,该方案用于存储 Secrets 并执行加密和解密操作。我们将利用 Azure Key Vault 来存储 Secrets,并在我们的 Kubernetes 工作负载中使用它们,还将使用 Key Vault 来加密存储在 etcd 上的 Secrets。
在本章中,我们将涵盖以下主题:
-
Azure Key Vault 概览
-
工作负载身份简介
-
AKS 集群与 Azure Key Vault 集成
-
审计和日志记录
-
Azure Key Vault 用于秘密加密
到本章结束时,我们应该能够在 Azure Key Vault 中存储我们的 Secrets,在 Kubernetes 部署中检索它们,通过审计监控秘密访问,并使用 Azure Key Vault 加密 Kubernetes Secrets。
技术要求
为了将概念与实践示例结合,我们使用了一系列常用的工具和平台,这些工具和平台通常用于与 Azure API 和 Kubernetes 交互:
-
Azure (az) CLI (
learn.microsoft.com/en-us/cli/azure/) 是一套多平台的命令行工具,用于创建和管理 Azure 资源 -
Terraform (
www.terraform.io/) 是一个基础设施即代码的软件解决方案,可用于在云中预配和管理基础设施 -
kubectl (
kubernetes.io/docs/reference/kubectl/) 是用于通过 Kubernetes API 与 Kubernetes 集群通信的命令行工具
Azure Key Vault 概览
Azure Key Vault 是一项多功能服务。它可以作为秘密存储。在 Azure Key Vault 上,我们可以存储加密密钥。同时,Azure Key Vault 还可以用于执行加密和解密操作。
由于它是一个托管的 Azure 服务,因此它享有 Azure 默认提供的服务特性。
关注的特性如下:
-
Azure RBAC 和访问策略
-
高可用性
-
日志记录、审计和监控
-
与其他 Azure 组件的集成
我们将花一些时间来审视这些对我们秘密安全至关重要的特性。
Azure RBAC 和访问策略
Azure 上的每个服务都通过 Azure 提供的身份访问管理层来保护免受未经授权的使用。该层通过 Azure 的 RBAC 和访问策略形式提供。安全主体是一个在 Azure 上具有身份的实体,可以是用户账户、组账户或计算机账户。为安全主体分配权限的传统方法是通过 访问策略。推荐的权限分配方式是通过 Azure 基于角色的访问控制(Azure RBAC)。随着本章的进行,Azure RBAC 将是我们保护 Azure Key Vault 的选择。通过使用 Azure RBAC,我们将通过创建角色分配来控制对资源的访问。
高可用性
当我们创建一个 Key Vault 时,必须指定 Key Vault 所在的区域。Key Vault 的内容将在该区域内进行复制。同时,Key Vault 的内容还将复制到辅助区域。Azure Key Vault 的内容在区域内具有高可用性,同时也支持开箱即用的灾难恢复。
假设某个区域不可用的场景。一旦该区域不可用,指向 Azure Key Vault 的请求将会被自动路由到辅助区域。这一过程是自动发生的,无需额外配置任何 Azure Key Vault 资源或将 Key Vault 配置为在另一区域的备用。
日志记录、审计和监控
在 Azure 中,我们可以选择审计 Key Vault 的使用情况。通过启用审计,我们可以识别谁访问了托管在 Azure Key Vault 上的数据。我们可以通过 诊断设置 收集日志来实现这一点。Azure 上的资源会生成日志,这些日志包含有关资源及其相关操作的信息。根据资源的不同,日志的内容可能会有所不同。
诊断设置 让我们能够将这些日志流式传输到不同的位置。默认情况下,日志将流式传输到 Azure 存储账户。其他选项包括将日志流式传输到 Log Analytics 工作区或 Azure 事件中心。
与其他 Azure 组件的集成
Azure Key Vault 的一个重要优点是可以与其他 Azure 组件集成。Azure Key Vault 可以与 Azure 应用网关集成进行流量加密,或者与 Azure 的 SQL Server 服务集成,对数据进行加密。一个值得关注的组件是 Azure Kubernetes 服务。
这可以通过 Kubernetes Secrets Store CSI 驱动程序与 Azure Key Vault 配合启用。我们在上一章中已经了解了 CSI Secret Store 的工作原理。
以下图示展示了集成是如何工作的:

图 9.1 – Azure Key Vault 集成
在本章中,我们将利用 Azure CSI Key Vault 插件并将其与 Azure 安全集成。该集成将涉及 Azure 上的 Kubernetes 集群,使用 Azure Kubernetes Service(AKS)。要将这两个组件——Azure Key Vault 和 Kubernetes 集群——集成起来,必须确保集群对 Azure Key Vault 具有细粒度的权限。为此,Azure 为我们提供了工作负载身份的概念。
工作负载身份简介
在 Azure 上的 AKS 上使用工作负载身份可以让我们为 Kubernetes 工作负载分配权限,从而使它们能够与 Azure 资源进行交互。例如,我们有一个用于存储敏感信息的 Azure Key Vault。为了与 Azure Key Vault 进行交互,我们需要某种形式的凭证。工作负载身份是代表需要身份以与 Azure 资源交互的软件工作负载的机器身份。我们可以通过手动将凭证附加到服务上,使用工作负载身份,而无需创建身份或服务主体。这样,每个服务都可以拥有自己的身份并进行自我认证。
在 Kubernetes 中,我们可以为 Pod 分配工作负载身份。通过授予该身份 RBAC 权限,我们将能够与 Azure Key Vault 进行交互。
下面是工作负载身份(Workload Identity)如何工作的示例:

图 9.2 – 工作负载身份背后的机制(来源:learn.microsoft.com/en-us/azure/aks/workload-identity-overview?tabs=dotnet)
Azure 官方文档详细概述了工作负载身份如何从 Azure Active Directory 请求令牌,并使用该令牌与 Azure 资源进行交互(learn.microsoft.com/en-us/azure/aks/workload-identity-overview)。
由于我们已经理解了工作负载身份(Workload Identity)的概念,现在可以继续了解 Azure 如何使 AKS 与其他 Azure 资源(例如 Azure Key Vault)之间的交互。
将 AKS 集群与 Azure Key Vault 集成
要将 Kubernetes 与 Azure Key Vault 集成,我们需要先设置一个集群。创建集群的选项有很多,每种选择适用于不同的场景。我们将创建一个简单的 AKS 集群;主节点将公开可用,但节点将位于虚拟网络的私有子网中。
我们将提供一些用于创建集群的 Terraform 代码。此外,如果 Terraform 不适用,我们也将提供所需的命令。
配置 Terraform 项目
在创建 Terraform 项目时,我们将配置状态。状态可以保存在存储账户中:
terraform {
...
backend "azurerm" {
resource_group_name = "resource-group"
storage_account_name = "storage-account"
container_name = "tfstate"
key = "aks.tfstate"
}
}
通过设置 Terraform 配置,我们可以继续在 Azure 上配置资源。
至关重要的是,在一个资源组下为本章配置资源:
resource "azurerm_resource_group" "ksm_resource_group" {
name = "ksm-resource-group"
}
通过使用资源组,我们可以将我们的资源从 Azure 账户中的其他资源逻辑上分开,专门用于我们要实现的解决方案。
同样,我们将创建一个存储账户,以持久化来自我们服务的日志:
resource "azurerm_storage_account" "ksm_storage_account" {
name = "ksmlogs"
resource_group_name = azurerm_resource_group.ksm_resource_group.name
…
}
如Azure Key Vault 概述部分所述,通过诊断设置,我们可以启用将 Azure 资源的日志流式传输到存储账户的功能。我们所配置的存储账户将用于此目的。
我们现在可以继续创建网络。
配置网络
我们将创建一个虚拟网络,并分配一个子集的私有 IP。我们还将创建一个子网,在该子网上我们能够托管 Kubernetes 集群节点:
resource "azurerm_virtual_network" "ksm_virtual_network" {
name = "ksm-virtual-network"
...
address_space = ["10.1.0.0/16"]
}
resource "azurerm_subnet" "ksm_subnet" {
name = "ksm-private-subnt"
...
address_prefixes = ["10.1.0.0/24"]
enforce_private_link_endpoint_network_policies = true
}
启用了enforce_private_link_endpoint_network_policies选项。通过此选项,托管在此子网中的应用程序可以通过内部网络访问 Azure 组件。
配置 AKS 集群
我们将通过创建主节点并添加默认节点池来创建 AKS 集群:
resource "azurerm_kubernetes_cluster" "ksm_aks" {
name = "ksm-aks"
...
dns_prefix = "private-aks-cluster"
private_cluster_enabled = false
oidc_issuer_enabled = true
workload_identity_enabled = true
role_based_access_control_enabled = true
...
default_node_pool {
name = "default"
node_count = 1
vm_size = "Standard_A2_v2"
vnet_subnet_id = azurerm_subnet.ksm_subnet.id
}
}
需要注意的一点是,我们启用了OpenID Connect(OIDC)功能和工作负载身份。这使我们能够为我们的 Kubernetes 工作负载分配角色,以便它们能够与 Azure Key Vault 交互。
使用terraform apply命令后,集群将被配置:
$ terraform init
...
$ terraform apply
我们有选择通过private_cluster_enabled选项实现完全私有的集群。在代码库中,您可以找到创建堡垒主机以启用此操作的设置(github.com/PacktPublishing/Kubernetes-Secrets-Handbook/blob/main/ch09/bastion.tf)。
或者,如果我们不想通过 Terraform 配置集群,我们可以使用命令行:
$ az aks create -n ksm-aks -g ksm-resource-group --enable-addons azure-keyvault-secrets-provider --enable-oidc-issuer --enable-workload-identity
我们现在可以成功登录到集群:
$ az aks get-credentials --name ksm-aks \
--resource-group ksm-resource-group \
--subscription $subscription \
--admin
通过执行上述命令,我们将为 kubectl 命令设置配置。该配置保存在用于执行 kubectl 命令的工作站的本地 ~/.kube/config 路径中。现在我们应该能够向集群执行命令。
创建 Key Vault
我们将继续创建一个 Key Vault 资源;然后,在该 Key Vault 上,我们将创建一个密钥和一个机密。我们将分配细粒度的权限,使其能够通过 RBAC 权限与 Key Vault 进行交互。
首先,我们创建 Azure Key Vault:
resource "azurerm_key_vault" "ksm_key_vault" {
name = "ksm-key-vault"
...
sku_name = "standard"
enable_rbac_authorization = true
soft_delete_retention_days = 7
}
如您所见,我们启用了 RBAC 选项。由于启用了 RBAC,我们将创建一个可以与我们的 Kubernetes 工作负载一起使用的身份:
resource "azurerm_user_assigned_identity" "keyvault_reader" {
name = "keyvault-reader"
...
}
resource "azurerm_role_assignment" "ksm_key_vault_reader" {
scope = azurerm_key_vault.ksm_key_vault.id
role_definition_name = "Key Vault Reader"
principal_id = azurerm_user_assigned_identity.keyvault_reader.principal_id
}
...
我们已创建身份并附加了权限,允许我们使用机密并查看它们。下一步是为联邦身份设置凭据。我们需要使用我们之前配置的集群的 OIDC 发行者 URL:
resource "azurerm_federated_identity_credential" "cred" {
name = "ksm-reader-identity"
...
Issuer =azurerm_kubernetes_cluster.ksm_aks.oidc_issuer_url
audience = ["api://AzureADTokenExchange"]
parent_id = azurerm_user_assigned_identity.keyvault_reader.id
subject = "system:serviceaccount:default:service-token-reader"
}
或者,我们可以通过命令行创建 Key Vault:
$ az keyvault create -n ksm-key-vault -g ksm-resource-group -l eastus --enable-rbac-authorization
az identity create --name keyvault-reader --resource-group ksm-resource-group
...
az identity federated-credential create \
--name "ksm-reader-identity" ...
通过配置身份凭证,我们现在可以通过 Kubernetes 与 Azure Key Vault 进行交互。联合身份凭证使我们能够访问受 Active Directory 保护的资源。我们使用的联合凭证建立了与 AKS 集群和 Active Directory 身份提供者之间的信任关系。我们允许service-token-reader服务账户模拟ksm-reader-identity。
从 Key Vault 读取 Secrets
我们已经设置好了 AKS 集群,并且我们的kubectl命令已准备好执行集群命令。到目前为止,我们没有安装任何插件。这是因为插件在创建 AKS 集群时已启用。
我们可以通过在 CSI 插件上运行以下命令来检查这一点:
kubectl get pods -n kube-system -l 'app in (secrets-store-csi-driver,secrets-store-provider-azure)'
NAME READY STATUS RESTARTS AGE
aks-secrets-store-csi-driver-t6n7h 3/3 Running 0 80m
aks-secrets-store-provider-azure-htmqk 1/1 Running 0 80m
可以使我们从 Key Vault 获取凭证的 CSI 驱动程序已经启用。与 CSI 驱动程序一起,我们在 Kubernetes 中创建了一个新的对象类型:SecretProviderClass。这个自定义资源用于为 CSI 驱动程序提供驱动程序配置和供应商特定的参数。在 Kubernetes 中,自定义资源是 Kubernetes API 的扩展。我们指定了一种新的对象类型,通过 Kubernetes API 访问,就像访问所有其他 Kubernetes 资源一样。我们可以通过官方文档了解有关自定义资源的更多信息(kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)。
我们将创建SecretProviderClass:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: keyvault-secrets
spec:
provider: azure
parameters:
usePodIdentity: "false"
clientID: #the identity provisioned
keyvaultName: #keyvault name
...
objects: |
array:
- |
objectName: secret1
objectType: secret
- |
objectName: key1
objectType: key
tenantId: #kubernetes tenant id
我们现在可以配置将附加身份的服务账户:
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
azure.workload.identity/client-id: #identity with Key Vault access
labels:
azure.workload.identity/use: "true"
name: service-token-reader
namespace: default
本质上,所使用的身份是我们之前配置的身份,目的是与 Azure Key Vault 进行交互。
应用以下配置后,我们可以设置一个 Pod,使用 Key Vault 凭证:
kind: Pod
apiVersion: v1
metadata:
name: nginx
spec:
serviceAccountName: service-token-reader
containers:
- name: nginx
image: nginx
volumeMounts:
- name: keyvault-secrets
mountPath: "/mnt/secrets-store"
readOnly: true
volumes:
- name: keyvault-secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "keyvault-secrets"
到目前为止,我们已经实现了主要目标,即通过 Azure Key Vault 使用 Key Vault Secrets。我们确实已访问了 Key Vault Secrets,接下来要检查的是是否可以对 Key Vault 访问进行审计。
审计和日志记录
在前面的部分中,我们在 Azure 上创建了 Key Vault。Azure 提供了为我们所创建的资源启用审计的选项。
在我们的案例中,我们添加了一个块,使我们能够将 Key Vault 访问的审计日志存储到一个存储账户中。
让我们执行 Terraform 代码:
resource "azurerm_monitor_diagnostic_setting" "ksm_key_vault_logs" {
name = "ksm-key-vault-logs"
target_resource_id = azurerm_key_vault.ksm_key_vault.id
storage_account_id = azurerm_storage_account.ksm_storage_account.id
log {
category = "AuditEvent"
enabled = true
retention_policy {
enabled = false
}
}
...
}
我们已经启用了 Azure 通过存储账户捕获所需的审计日志。
重要提示
请注意,我们已经为日志设置了保留策略。撰写本文时,已宣布诊断设置存储保留功能将被弃用,因此,日志和指标的保留应通过 Azure 存储生命周期管理进行配置。
如果我们导航到存储桶,应该会在该存储帐户中创建一个容器。容器将命名为 insights-logs-auditevent,并且容器中的文件将是 JSON 格式。
让我们查看其中一个文件:
{
"time": "2023-08-30T09:07:19.8593007Z",
"category": "AuditEvent",
"operationName": "KeyGet",
"resultType": "Success",
"correlationId": "fa11ea42-67c0-47cd-8a6b-f7bcb349414f",
"identity": {
"claim": {
"oid": "80dd018c-ede7-42f4-99a8-00e278868a7c",
"appid": "b1967275-af7b-4d75-9804-c935ecb22226",
"xms_mirid": "/subscriptions/.../userAssignedIdentities/keyvault-reader",
"xms_az_nwperimid": []
}
},
"properties": {
"id": "https://ksm-key-vault.vault.azure.net/keys/key1",
"requestUri": "https://ksm-key-vault.vault.azure.net/keys/key1/?api-version=2016-10-01",
"isRbacAuthorized": true,
...
},
...
}
我们可以看到类别和操作。此外,身份是我们之前附加到 Kubernetes 的身份。通过审计日志,我们可以识别发生的操作、操作的执行者以及操作发生的资源,在我们的案例中是我们配置的 Key Vault。
Azure 密钥库用于机密加密
到目前为止,我们已使用 Azure Key Vault 存储敏感机密。我们想要识别的是,是否可以使用 Azure Key Vault 对存储在 etcd 上的机密进行加密。
我们已经创建了一个 Key Vault。我们将使用该 Key Vault 创建一个用于 KMS 目的的密钥。
我们将首先创建一个密钥:
resource "azurerm_key_vault_key" "ksm_encryption_key" {
name = "ksm-encryption-key"
key_vault_id = azurerm_key_vault.ksm_key_vault.id
key_type = "RSA"
key_size = 2048
key_opts = [
"decrypt",
"encrypt",
"sign",
"unwrapKey",
"verify",
"wrapKey",
]
}
请注意,我们还可以指定一个轮换策略:
rotation_policy { automatic { time_before_expiry = "P30D" } expire_after = "P90D" notify_before_expiry = "P29D" }
当我们创建 Kubernetes 集群时,可以使用此密钥来加密我们创建的机密。
在 AKS 部分,我们将添加此选项:
resource "azurerm_kubernetes_cluster" "ksm_aks" {
name = "ksm-aks"
...
key_management_service {
key_vault_key_id = azurerm_key_vault_key.ksm_encryption_key.id
key_vault_network_access = "Public"
}
...
}
我们已经成功地使用 Azure Key Vault 作为 KMS 在 etcd 上加密了我们的机密。我们还可以通过审计日志检查这一点:
{
"time": "2023-09-03T11:46:27.5820050Z",
"category": "AuditEvent",
"operationName": "KeyDecrypt",
"resultType": "Success",
"identity": {
"claim": {
"xms_az_rid": "/subscriptions/.../managedClusters/ksm-aks",
"xms_az_nwperimid": []
}
},
"properties": {
"id": "https://ksm-key-vault.vault.azure.net/keys/ksm-encryption-key/0c24b95c67534a3eb85c71854dc8a7bd",
"algorithm": "RSA-OAEP-256",
"clientInfo": "... k8s-kms-keyvault/v0.5.0 (linux/amd64) 84fa3b7/2023-05-17-21:13",
"httpStatusCode": 200,
..
"tlsVersion": "TLS1_2"
},
}
如我们所见,Azure Key Vault 正在积极用于解密托管在 AKS 上的机密。KeyDecrypt 操作表示解密操作。加密机密的相应操作也会在 AKS 上执行。这增强了我们机密管理的安全性。
总结
在本章中,我们成功创建了一个能够从 Azure 密钥库读取机密的 AKS 集群。我们确定了实现机密加密和解密所需的 RBAC 权限。我们还通过将机密加密存储在 etcd 上,使用 Azure 密钥库作为 Kubernetes 的 KMS,从而增强了安全性。最后,我们可以通过审计日志识别 Azure 密钥库的使用情况。
在下一章中,我们将重点介绍另一家流行的云服务提供商——Google Cloud Platform(GCP)。我们将探索 GCP 上的机密存储选项及其与 GCP 的 Kubernetes 提供的集成,以及机密加密选项。
第十章:探索 GCP 上的 Cloud Secret Store
之前,我们深入研究了 Azure Key Vault。我们成功地将 Secrets 安全地存储在 Key Vault 中,并将其作为 Secrets 在 etcd 中存储的密钥管理服务。在本章中,我们将专注于 Google Cloud 平台,并将使用 Google Cloud 的 Secret Manager。
本章将涵盖以下主题:
-
GCP Secret Manager 概述
-
GKS 上的工作负载身份
-
GKE 和 GCP Secret Manager 集成
-
审计和日志记录
-
GKE 和 KMS 集成
到本章结束时,我们应该能够将我们的 Secrets 安全地存储到 GCP Secret Manager 中,通过审计监控密钥访问,并通过在 etcd 上加密 Secrets 来增加额外的安全层。
技术要求
为了将概念与实际操作示例联系起来,我们使用了一系列常见的工具和平台,这些工具和平台通常用于与 Google Cloud API 和 Kubernetes 进行交互:
-
gcloud CLI (
cloud.google.com/sdk/gcloud#download_and_install_the) 是一套工具,用于创建和管理 Google Cloud 资源。 -
Terraform (
www.terraform.io/) 是一种基础设施即代码的软件,可以用于在云端配置和管理基础设施。 -
kubectl (
kubernetes.io/docs/reference/kubectl/) 是一个命令行工具,用于通过 Kubernetes API 与 Kubernetes 集群进行通信。
GCP Secret Manager 概述
GCP Secret Manager 是 Google Cloud 提供的秘密管理解决方案。只要我们有一个需要存储 Secrets 的应用,Secret Manager 就可以被利用。该应用可以部署在 Compute Engine、Kubernetes、Cloud Functions 或任何其他 Google Cloud 上的合法部署形式中。
由于此服务由 Google Cloud 管理,默认提供了一些功能。我们可以总结如下:
-
IAM
-
高可用性
-
日志记录与审计
-
与其他 Google Cloud 组件的集成
让我们深入了解每一个内容。
IAM
Google Cloud 提供了 身份访问管理(IAM)。某些 IAM 权限在组织级别生效,使我们能够管理组织的资源。然后,我们还有适用于项目范围的 IAM 权限,这些权限用于分配特定资源的访问权限。最细粒度的权限是基于资源的 IAM 权限。当我们创建一个资源时,可以为该特定资源分配权限。身份可以是用户、Google 群组或服务帐户。可以将特定资源的权限分配给特定身份。
高可用性
密钥管理器是一个高可用的解决方案,覆盖了灾难恢复场景。默认情况下,机密可以从 Google Cloud 的不同区域全球访问。在后台,机密会在多个区域进行复制,除非另有指定。指定区域的原因是数据驻留限制。如果某个机密不能存储在特定区域,我们可以定义要存储机密的区域,并排除任何不应包含我们数据的区域。
日志记录、审计和监控
Google Cloud 默认提供一个日志记录解决方案,既用于应用日志,也用于审计日志。应用日志与审计日志之间是有区别的。要访问 GCP 项目上的审计日志,您需要具有“Private Logs Viewer”权限。
与其他 Google Cloud 组件的集成
正如预期的那样,密钥管理器与其他 Google Cloud 组件集成。
我们可以使用密钥管理服务(KMS)对密钥管理器上的机密进行加密,并且可以将密钥管理器与 Kubernetes 集成。如我们在第八章中所见,在 AWS 上探索云端秘密存储,与 Kubernetes 上密钥管理器的集成是通过 CSI 插件实现的。
工作负载身份简介
GCP 上的 Kubernetes 引擎中的工作负载身份(Workload Identity)使我们能够将权限分配给能够与 Google Cloud 资源交互的 Kubernetes 工作负载。Google Cloud 有服务账户的概念。服务账户用于机器与资源进行交互。计算引擎、Lambda 函数甚至 Google Cloud 上的 App Engine 都可以分配一个服务账户,该账户具有与 Google Cloud 资源交互的权限。通过工作负载身份,我们可以将 GCP 上的服务账户映射到 Kubernetes 上的服务账户。
在 Kubernetes 中,我们可能会使用几种类型的部署来部署我们的应用程序。我们可以使用Deployment、StatefulSet、DaemonSet等。幕后将会创建一个 Pod,Pod 是 Kubernetes 上运行应用程序的基本组件。Pod 可以被分配一个服务账户。通过在 Kubernetes 上使用工作负载身份,并将 Kubernetes 服务账户绑定到 Google Cloud 服务账户,附加了 Kubernetes 服务账户的 Pod 将能够根据我们在 GCP 服务账户上授予的权限与 Google Cloud 资源进行交互。
因此,这一概念将帮助我们与 GCP 密钥管理器的集成。通过 Kubernetes CSI 和工作负载身份的集成,我们的 Kubernetes 工作负载访问密钥管理器将是授权且可行的。
现在我们理解了 Kubernetes 和 Google Cloud 上的密钥管理器如何协作,我们将重点实施一个 Kubernetes 集群。
集成 GKE 和 GCP 密钥管理器
借助 CSI Secret Store 插件,我们可以将 Secret Manager 解决方案与 Kubernetes 集群集成。Google Cloud 上的 Kubernetes 提供方案是 Google Kubernetes Engine。我们将使用该 Kubernetes 集群方案与 Secret Manager 集成。
创建集群有几种选择。我们将提供用于创建集群的 Terraform 代码。此外,我们还将提供其他所需的命令。
配置 Terraform 项目
我们需要配置 Terraform 提供程序。它将指向 GCP 凭证文件,还将指向 GCP 项目和区域。
提供程序配置应如下所示:
provider "google" {
credentials = "/path/to/credentials/file"
project = "your-gcp-project"
region = "us-central1"
}
要初始化,我们需要运行 init 命令:
$ terraform init
当我们运行 init 命令时,如果没有指定凭证文件,默认凭证将是我们使用 gcloud auth login 命令登录的用户凭证。或者,我们可以指定服务账户文件。
网络配置
我们将继续进行网络的配置。在 Google Cloud 上,网络是全球资源,而网络的子网是区域资源。
我们将创建网络,正如预期的那样,由于 VPC 是全球资源,因此不会指定任何区域:
resource "google_compute_network" "vpc" {
name = "${var.project_id}-vpc"
auto_create_subnetworks = "false"
project = var.project_id
}
子网络将被创建以托管 Kubernetes 上的节点。它将映射到指定的区域:
resource "google_compute_subnetwork" "subnet" {
name = "${var.project_id}-subnet"
region = var.region
network = google_compute_network.vpc.name
ip_cidr_range = "10.10.0.0/24"
project = var.project_id
}
现在我们可以专注于在 Secret Manager 上创建密钥。
在 Secret Manager 上配置密钥
GCP Secret Manager 是一种服务;在 GCP 上使用 Secret Manager 时,我们不需要创建资源。相反,我们创建将在 Secret Manager 上托管的密钥。
我们将从配置一个密钥开始:
resource "google_secret_manager_secret" "my_secret" {
secret_id = "my-secret"
user_managed {
replicas {
location = var.location
}
replicas {
location = "us-east1"
}
}
}
我们故意指定了副本所在的区域。该密钥将托管在两个区域,这使得我们的密钥使用在区域故障时也能保持弹性。
我们已经创建了密钥,但尚未指定具体的值。对于一个密钥,我们需要指定该密钥的版本。现在让我们继续添加版本:
resource "google_secret_manager_secret_version" "my_secret_version" {
secret = google_secret_manager_secret.my_secret.id
secret_data = "secret-data"
}
此外,我们还希望配置一个具有权限的服务账户,以便检索密钥:
resource "google_service_account" "my_service_account" {
account_id = "read-secrets-service-account"
}
resource "google_secret_manager_secret_iam_binding" "my_secret_reader" {
role = "roles/secretmanager.secretAccessor"
secret_id = google_secret_manager_secret.my_secret.id
members = [
"serviceAccount:${google_service_account.my_service_account.email}"
]
}
密钥已配置,我们也有一个服务账户可以用来进行检索。
现在,让我们继续创建 GKE 集群。
配置 GKE 集群
要创建 GKE 集群,我们需要创建主节点,并且还会创建一个默认的节点池:
resource "google_container_cluster" "gke_cluster" {
name = "secrets-cluster"
location = var.region
remove_default_node_pool = true
initial_node_count = 1
network = google_compute_network.vpc.name
subnetwork = google_compute_subnetwork.subnet.name
...
workload_identity_config {
workload_pool = "kube-secrets-book.svc.id.goog"
}
}
在创建集群时,GKE 默认要求我们创建一个初始节点池。我们可以使用 google_container_node_pool 资源创建另一个自定义节点池。这样,我们可以在 Terraform 中调整 GKE 节点池的更多参数,并将集群和节点池的定义分开。一旦创建了新的节点池,前提是我们将 remove_default_node_pool 设置为 true,Terraform 会删除初始的节点池。这将保持低成本,因为只有一个节点池会处于工作状态。
主要的节点池将以保持成本低为目标:
resource "google_container_node_pool" "primary_nodes" {
name = google_container_cluster.gke_cluster.name
cluster = google_container_cluster.gke_cluster.name
version = data.google_container_engine_versions.gke_version.release_channel_latest_version["STABLE"]
node_count = 1
node_config {
oauth_scopes = [
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring",
]
machine_type = "n1-standard-1"
tags = ["gke-node", "${var.project_id}-gke"]
disk_size_gb = 10
metadata = {
disable-legacy-endpoints = "true"
}
}
}
我们已经成功创建了集群。我们也可以登录集群并执行一些 kubectl 命令:
$ gcloud container clusters get-credentials secrets-cluster --region us-central1 --project kube-secrets-book
$ kubectl get node
NAME STATUS ROLES AGE VERSION
gke-secrets-cluster-secrets-cluster-9e54b21e-5kxw Ready <none> 9m41s v1.27.3-gke.1700
gke-secrets-cluster-secrets-cluster-b969915f-rfjz Ready <none> 9m35s v1.27.3-gke.1700
...
我们的 GKE 集群已经投入使用,准备好为我们的工作负载提供服务。
为 Kubernetes Secrets 添加 CSI 插件
我们已经拥有一个可操作的集群,因此,我们将专注于设置 CSI 插件。在标准的 GKE 集群中,必须安装 CSI 插件。而在 GKE Autopilot(一个默认管理 Kubernetes 许多方面的 GKE 版本)中,CSI 插件已默认安装。
请注意,CSI 插件并未被 Google 官方支持。
首先,我们需要将插件安装到集群中:
$ helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
$ helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver --namespace kube-system
$ kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/secrets-store-csi-driver-provider-gcp/main/deploy/provider-gcp-plugin.yaml
然后我们应该创建一个服务账户。Kubernetes 服务账户将被注释上我们希望工作负载使用的 GCP 服务账户的 ID:
$ kubectl create serviceaccount read-secret --namespace=default
$ kubectl annotate serviceaccount read-secret \
--namespace=default \
iam.gke.io/gcp-service-account=read-secrets-service-account@test-gcp-project.iam.gserviceaccount.com
通过这种方式,Kubernetes 的服务账户将代表我们之前创建的服务账户执行操作。服务账户能够从 Secret Manager 中检索 Secrets。
SecretProviderClass 是一种自定义资源类型,提供 CSI 驱动程序的配置和参数。我们需要指定 SecretProviderClass:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: app-secrets
spec:
provider: gcp
parameters:
secrets: |
- resourceName: "projects/project-i/secrets/my-secret/versions/latest"
path: "good1.txt"
最后一步是创建一个 Pod。该 Pod 将使用工作负载身份并代表 read-secrets-service-account GCP 账户执行操作。同时,Pod 将使用我们之前创建的 SecretProviderClass,并将 Secrets 挂载为文件:
apiVersion: v1
kind: Pod
metadata:
...
spec:
serviceAccountName: mypodserviceaccount
containers:
- ...
volumeMounts:
- mountPath: "/var/secrets"
name: mysecret
volumes:
- name: mysecret
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "app-secrets"
我们实现的目标是通过 CSI 插件将秘密挂载到 Pod。现在,我们可以专注于监控 Secrets 的使用情况。审计和日志记录在识别云资源或 Kubernetes 上的操作中起着至关重要的作用。
审计和日志记录
Google Cloud 默认提供日志记录和审计功能。一旦我们配置了 Kubernetes 集群,所有操作都可以通过日志控制台查看。
假设我们在之前配置的集群中创建了一个秘密:
$ kubectl create secret generic empty-secret
该操作将记录在 GKE 的审计日志中,我们只需使用以下查询在 GCP 的日志控制台中搜索:
protoPayload.methodName="io.k8s.core.v1.secrets.create"
protoPayload.@type="type.googleapis.com/google.cloud.audit.AuditLog"
resource.type="k8s_cluster"
结果是,我们应该看到访问 Secrets 时的所有操作:

图 10.1 – GKE 上的 Kubernetes 审计日志
除了 Kubernetes 上的审计日志外,我们还可以利用 Secret Manager 上的审计日志。在 GCP 的日志页面上,我们可以专门搜索审计日志:
resource.type="audited_resource" AND
resource.labels.service="secretmanager.googleapis.com"
这个 GCP 日志查询过滤器将帮助我们查看与 Secret Manager 相关的日志。
GKE 安全姿态仪表板
另一个由 GCP 提供的显著工具是 GKE 安全姿态仪表板,它能提升 GKE 集群的安全性。GKE 安全姿态仪表板是一组在 Google Cloud 控制台中扫描 GKE 集群和工作负载的功能,提供有针对性、可操作的建议。
GKE 安全姿态关注两个部分:
-
Kubernetes 安全姿态
-
工作负载漏洞扫描
通过 Kubernetes 安全态势仪表板,当发现漏洞时,它会自动在仪表板上显示,展示受影响的集群和工作负载,帮助我们进行后续操作:

图 10.2 – GKE 安全态势
通过工作负载漏洞扫描,集群中运行的容器镜像会被扫描出漏洞。此外,还支持扫描实际的编程语言包中的漏洞,从而使工作负载更加安全。
如预期所示,GKE 安全态势的结果与其他云组件(如日志记录和监控)集成,从而使得在处理任何安全事件时能够启用警报并实现自动化。
现在审计和日志记录已经到位,我们可以继续进行更高级的加密 Secrets 的操作,通过将 GKE 与 KMS 集成。
集成 GKE 与 KMS
使用 KMS 来加密 Google Kubernetes 容器引擎上的 Secrets 是可行的。默认情况下,GKE 会加密静态数据,且加密由 GCP 管理。除了对数据的安全处理外,我们可能希望对数据的加密过程有更多控制。在这种情况下,我们可以选择使用在 GCP 项目中我们自己配置和维护的 KMS 密钥来加密 Kubernetes 上的数据。我们将从配置 KMS 密钥开始:
resource "google_kms_key_ring" "ksm_key_ring" {
name = "ksm-key-ring"
location = var.region
}
resource "google_kms_crypto_key" "ksm_secret_key" {
name = "ksm-secret-encryption"
key_ring = google_kms_key_ring.ksm_key_ring.id
lifecycle {
prevent_destroy = false
}
}
我们还需要分配权限,以便 Kubernetes 服务账户能够执行加密和解密操作。请注意,在 GKE 上,主节点由 GCP 管理,主节点使用的服务账户并非我们 Google Cloud 项目中的服务账户,因此我们需要将权限分配给一个不属于我们项目的服务账户:
data "google_project" "project" {}
resource "google_kms_crypto_key_iam_binding" "ksm_secret_key_encdec" {
crypto_key_id = google_kms_crypto_key.ksm_secret_key.id
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
members = [
"serviceAccount:service-${data.google_project.project.number}@container-engine-robot.iam.gserviceaccount.com"
]
}
我们刚刚创建了 KMS。在我们的集群配置中,让我们启用数据库加密选项:
resource "google_container_cluster" "gke_cluster" {
name = "secrets-cluster"
location = var.region
...
database_encryption {
key_name = google_kms_crypto_key.ksm_secret_key.id
state = "ENCRYPTED"
}
}
我们没有使用 GCP 的 KMS 密钥,而是提供了我们自己的客户管理密钥(CMK)。我们的 Secrets 将使用我们提供的 KMS 密钥进行加密。
总结
在本章中,我们深入探讨了 Google Cloud 和 GKE 上的 Secrets 管理。我们了解了 Secret Manager,这是 GCP 提供的 Secrets 管理服务及其包含的功能,如可用性、审计和与其他 Google Cloud 组件的集成。然后,我们使用 GKE 服务在 GCP 上配置了 Kubernetes 引擎,并将其与 Secret Manager 集成。接着,我们使用 KMS 加密了 Kubernetes 上的静态 Secrets。最后,通过使用 Google Cloud 监控,我们成功地跟踪了 Kubernetes、Secret Manager 和 KMS 上的秘密操作。在下一章中,我们将专注于非云基础的秘密管理工具:HashiCorp Vault 和 CyberArk Conjur。
第十一章:探索外部秘密存储
之前,我们与云服务提供商 AWS、Azure 和 GCP 一起工作。我们运行了应用程序,并将其与这些平台上提供的秘密提供商集成。我们使用了每个云服务商的 Secrets 管理设施,并受益于云服务商为我们提供的工具生态系统。这使得我们能够应对诸如审计、高可用性和灾难恢复等问题。在本章中,我们将探讨不属于云服务提供商产品的一些外部秘密存储。
我们将涵盖以下主题:
-
外部秘密存储提供商概述
-
外部秘密存储的不同类型,如 HashiCorp Vault 和 CyberArk Secrets Manager
-
在外部秘密存储中管理 Kubernetes 的 Secrets
技术要求
为了将概念与动手实践结合,我们使用了一系列常用工具和平台来与 Google Cloud API 和 Kubernetes 进行交互:
-
kubectl:这是用于通过 Kubernetes API 与 Kubernetes 集群通信的命令行工具 (
kubernetes.io/docs/reference/kubectl/)。 -
minikube:这是一个用于 Kubernetes 学习和开发的本地 Kubernetes 发行版。要在您的系统上安装 minikube,您可以参考官方文档中的安装说明 (
minikube.sigs.k8s.io/docs/start/)。 -
Vault CLI:用于与 HashiCorp Vault 安装进行交互 (
developer.hashicorp.com/vault/docs/install)。 -
kubectl命令 (jqlang.github.io/jq/). -
Helm:这是 Kubernetes 的一个包管理器,我们将使用它来简化 Kubernetes 资源的部署和管理 (
helm.sh)。
外部密钥提供商概述
默认情况下,Kubernetes 上的 Secrets 存储在etcd中,正如在第一章《理解 Kubernetes Secrets 管理》中所提到的那样,理解 Kubernetes Secrets 管理。将 Secrets 存储在 etcd 中会带来一些安全问题,正如在第七章《管理 Secrets 中的挑战与风险》一章中提到的那样。使用其他形式的秘密存储可以帮助我们增强安全性,并且分隔与秘密使用相关的关注点。我们将重点关注使外部密钥提供商可行的组件。
首先,让我们看一下可用的外部秘密存储提供商:
-
AWS Secrets Manager
-
Azure Key Vault
-
GCP Secret Manager
-
HashiCorp Vault
-
CyberArk Conjur
这些提供商与 Kubernetes 集成的方式可能有所不同。实现与密钥存储提供商集成的一种非常流行的方法是通过 Secrets Store CSI Driver。
Secrets Store CSI Driver
Secrets Store CSI 驱动程序使得通过容器存储接口(CSI)卷集成 Kubernetes 秘密成为可能。Kubernetes Secrets Store 接口基于 Kubernetes CSI 插件。通过 CSI,我们可以通过实现 CSI 插件将外部存储提供者集成到 Kubernetes 中,从而扩展集群的存储能力,而无需更改 Kubernetes 的核心代码。
外部秘密存储提供者与 CSI 插件
有一些 CSI 提供者插件:
-
AWS 提供者
-
Azure 提供者
-
GCP 提供者
-
Vault 提供者
在前面的章节中,我们重点介绍了与云服务提供商解决方案一起提供的 CSI Secrets Store CSI 驱动程序。我们提到了一个新的提供者——Vault 提供者,它是 HashiCorp 的秘密存储解决方案。
列表中的所有秘密提供者都提供高可用性选项,并且所有的秘密提供者都提供审计和日志记录。这些元素确保了数据治理和安全性。除了 CSI 驱动程序解决方案外,还有其他可用的秘密管理集成方法。其中之一就是 Secrets Injector 组件,我们将在接下来介绍。
Secrets Injector
Secrets Injector 组件提供了另一种方式,可以将秘密注入到应用程序以及托管在外部秘密存储提供者上的秘密。
像 HashiCorp Vault 和 CyberArk Conjur 这样的秘密管理器通过 Sidecar Injector 支持秘密存储的使用。这些 sidecar 注入器专注于在从秘密存储中接收秘密时的授权和认证方面。同时,它们也解决了与 Kubernetes 工作负载的集成问题。
然而,请注意,注入秘密并不是总需要一个与 Kubernetes 交互的复杂二进制文件。例如,我们可以考虑 GCP Secret Manager。没有官方的二进制文件可以从 Secret Manager 接收秘密并将其附加到 Pod 上。但这并不妨碍我们实现一个安全的解决方案来实现相同的结果。我们可以利用工作负载身份,如在第十章《探索 GCP 上的云秘密存储》中提到的那样,通过工作负载身份,我们的 Kubernetes 工作负载可以与秘密管理器进行交互。然后我们可以使用 GCP Secret Manager 的客户端库,并通过初始化容器,将秘密安全地挂载到 Pod 上。
这个方案可以应用于任何其他形式的存储。关键要点是使用了工作负载身份(Workload Identity);通过它,安全性得到了保障。我们不需要在 etcd 上存储任何秘密,从而能够与秘密管理器进行交互。相反,Kubernetes 工作负载会被分配特定的权限,从而使得与秘密管理器的交互成为可能。
在其他形式的秘密管理系统中,如 HashiCorp Vault,身份验证和与秘密存储的交互与我们之前看到的有所不同,但其安全性特性保持一致。对秘密的访问是通过安全和授权的方式实现的,并遵循最小权限原则。我们将在接下来的章节中详细讨论这一点。
总结来说,我们概述了可以用来将 Secrets 注入 Kubernetes 的外部 Secrets 提供程序。接下来,我们将重点介绍 HashiCorp Vault 及其功能。
HashiCorp Vault
在分布式计算迅速发展的背景下,保护敏感信息至关重要。Kubernetes,作为容器编排的领头羊,需要强大的解决方案来管理 Secrets——那些对应用程序配置和操作至关重要的敏感数据。HashiCorp Vault 凭借其全面的工具套件,成为解决这些挑战的核心,为 Kubernetes 集群中的 Secrets 管理提供了安全的方案。
HashiCorp Vault 提供了一个集中化的加密解决方案,确保 Secrets 保持机密,并且从不在应用程序代码或配置文件中暴露。它与 Kubernetes 的集成不仅简化了 Secrets 管理,还增强了容器化应用程序的安全性,引入了动态 Secrets、Pods 中的安全秘密注入和 Kubernetes 原生身份验证等功能。这些功能促进了 HashiCorp Vault 与 Kubernetes 之间的安全通信和 Secrets 检索,提升了管理 Secrets 的灵活性、安全性和效率。
HashiCorp Vault 能够按需动态生成短期凭证,这一点将其与本地 Kubernetes Secrets 区分开来。后者通常以未加密的形式存储在 etcd 中,并且随着应用程序的扩展,管理起来变得更加困难。HashiCorp Vault 的方法最小化了攻击面,增强了安全性,并解决了 Secrets 管理不当的潜在问题。
此外,HashiCorp Vault 的广泛审计日志功能确保了透明度和问责制,帮助合规并促进了对安全事件的迅速响应。通过将 HashiCorp Vault 集成到 Kubernetes 部署中,组织能够建立一个强大、安全且符合合规要求的容器化应用环境,克服本地 Kubernetes Secrets 的局限性,将 Secrets 管理提升到最高的安全标准。
它有开源版和企业版,我们接下来将重点介绍开源版。
使用 HashiCorp Vault 作为秘密存储
HashiCorp Vault 可以作为秘密存储,它可以包含我们应用程序的 Secrets。这使得将 Kubernetes Secrets 托管在 Vault 中成为可能。可以通过两种方式实现:要么将 Vault 托管在 Kubernetes 之外,要么将 Vault 与 Kubernetes 一起托管。
让我们看看如何将外部 HashiCorp Vault 与 Kubernetes 集成。为了满足我们的需求,我们将使用 minikube 安装。
外部 Vault 存储
我们将继续创建一个 Vault 实例,它将在 Docker 容器中运行。我们可以使用 Docker Compose 运行 Vault:
services:
vault_node_1:
image: vault:1.13.3
container_name: vault_node_1
ports:
- "8200:8200"
environment:
VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:8200"
VAULT_DEV_ROOT_TOKEN_ID: mytoken
VAULT_LOG_LEVEL: debug
volumes:
- haproxy.cfg:/haproxy.cfg
我们可以使用以下命令运行 Vault:
$ docker compose up
另一种解决方案是通过使用普通的 Docker 命令运行 Vault:
$ docker run -p 8200:8200 --rm -v haproxy.cfg:/haproxy.cfg --name vault_node_1 -e VAULT_DEV_ROOT_TOKEN_ID=mytoken -e VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200 -e VAULT_LOG_LEVEL=debug vault:1.13.3
这两个命令的结果没有区别。Compose 的便利性在于它只有一个文件,而独立的 Docker 命令也可以正常工作。这完全是个人偏好问题。
需要理解的是,我们在开发模式下运行 Vault 是为了调试目的。这并不是一种安全的运行 Vault 的方式,我们将在本章的 开发模式与生产模式 部分看到这一点。开发模式将用于使我们的示例用例更简单。
让我们检查一下 Vault。我们指定了 root token,这是一个具有 root 权限的令牌。我们还将日志级别设置为 debug,以帮助我们进行安装故障排除。VAULT_DEV_LISTEN_ADDRESS 是在开发模式下绑定的地址。现在,这足以继续与 Kubernetes 集成。
在 Kubernetes 上安装 Vault
为了能够将 Vault 与 Kubernetes 集成,我们需要安装 Vault 二进制文件。最简单的方式是通过 Helm 图表。
首先,我们将获取我们的 Vault 安装的参考:
$ EXTERNAL_VAULT_ADDR=$(minikube ssh "dig +short host.docker.internal" | tr -d '\r')
然后我们将在 Kubernetes 上安装 Vault:
$ helm repo add hashicorp https://helm.releases.hashicorp.com
$ helm repo update
$ helm install vault hashicorp/vault --set "global.externalVaultAddr=http://$EXTERNAL_VAULT_ADDR :8200" --set="csi.enabled=true"
我们在 Kubernetes 上安装了 Vault 包,并设置了 Kubernetes 将指向的外部 Vault 地址。此外,我们启用了 CSI,因为我们将在另一个示例中展示 CSI 功能。要检查我们的安装是否成功,我们可以检查 vault-agent-injector 是否已部署在我们的集群中。通过使用 kubectl,我们可以判断是否存在 vault-agent-injector 部署:
$ kubectl get deployment vault-agent-injector
NAME READY UP-TO-DATE AVAILABLE AGE
vault-agent-injector 1/1 1 1 3h40m
让我们了解一下 Vault Agent Injector 的工作原理。
Vault Agent Injector
如果我们检查 Vault Agent Injector,将会看到它使用了一个特定的服务账户 vault-agent-injector。这个组件负责更改 Pod 规格,以便可以包含代理容器。这些 Vault Agent 容器将 Vault 中的 Secrets 渲染到共享内存卷中。
当一个使用 Vault Secrets 的 Pod 初始化时,以下操作将发生:
-
代理将识别使用 Vault Secrets 的 Pod。
-
Pod 规格将被更改,并将包括 Vault Agent 容器。
-
Vault 初始化容器将提取 Secrets,并将它们附加到 Pod 作为共享内存卷。
-
如果 Vault 中的 Secrets 发生变化,Vault Sidecar Injector 会改变这些 Secrets。
vault-agent-injector Pod 被分配了 vault-agent-injector-clusterrole 角色。
我们可以看到 vault-agent-injector-clusterrole 集群角色:
$ kubectl get clusterrole vault-agent-injector-clusterrole -o yaml
kind: ClusterRole
metadata:
...
name: vault-agent-injector-clusterrole
rules:
- apiGroups:
- admissionregistration.k8s.io
resources:
- mutatingwebhookconfigurations
verbs:
- get
- list
- watch
- patch
vault-agent-injector 被注册到 Kubernetes,作为一个 vault-agent-injector,它将拦截创建请求并重写 Pod 定义。
Vault 注释将如下所示:
vault.hashicorp.com/agent-inject: 'true'
vault.hashicorp.com/role: 'webapp_admin_r'
vault.hashicorp.com/agent-inject-secret-credentials.txt: 'secret/data/webapp/admin'
如我们所见,我们启用了注入方法。然后,我们指定将用于获取密钥的 Vault 角色以及将要获取的密钥:

图 11.1 – Vault Injector 和托管在 Kubernetes 上的应用程序
通过重写 Pod 定义,Pod 现在将包含 Vault Agent 容器。Vault Agent 容器将与 Vault 交互以检索密钥并将其注入到 Pod 中。
Vault 服务账户和 Kubernetes 身份验证
为了让 Vault 与 Kubernetes 交互,它必须使用服务账户。通过安装 Vault Helm 包,也将创建一个服务账户。该服务账户命名为 vault。
我们需要为该服务账户创建一个 token。根据第二章《Kubernetes 密钥管理概念解析》,我们知道在 Kubernetes 的新版本中,我们必须自己为服务账户创建密钥。
让我们为服务账户创建密钥。我们将创建一个 YAML 文件,内容如下:
apiVersion: v1
kind: Secret
metadata:
name: vault-sa-token
annotations:
kubernetes.io/service-account.name: vault
type: kubernetes.io/service-account-token
接下来,我们将应用 YAML 文件:
$ kubectl apply –f vault-secret.yaml
密钥将被创建,我们应该能够将其用于 Vault。现在我们应该配置 Vault 以启用 Kubernetes 身份验证。
Kubernetes 身份验证
我们可以通过使用 Kubernetes 服务账户 token 来验证 Vault。这是 Kubernetes 身份验证方法。实际上,我们在 Vault 上配置一个 Kubernetes 服务账户 token,然后使用该 token 与 Kubernetes API 交互。为了启用此功能,我们需要在 Vault 上启用身份验证方法。
以下步骤应按顺序执行:
# retrieve Kubernetes secret for the service account
$ VAULT_HELM_SECRET_NAME=$(kubectl get secrets --output=json | jq -r '.items[].metadata | select(.name|startswith("vault")).name')
# retrieve service account token
$ TOKEN_REVIEW_JWT=$(kubectl get secret $VAULT_HELM_SECRET_NAME --output='go-template={{ .data.token }}' | base64 --decode)
# retrieve Kubernetes certificate
$ KUBE_CA_CERT=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.certificate-authority-data}' | base64 --decode)
# retrieve the Kubernetes host
$ KUBE_HOST=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.server}')
# point to local vault address
$ export VAULT_ADDR=http://0.0.0.0:8200
# login to vault using the root token
$ vault login mytoken
# enabled kubernetes authentication on vault
$ vault auth enable kubernetes
# write Kubernetes authentication configuration
$ vault write auth/kubernetes/config token_reviewer_jwt="$TOKEN_REVIEW_JWT" kubernetes_host="$KUBE_HOST" kubernetes_ca_cert="$KUBE_CA_CERT" issuer="https://kubernetes.default.svc.cluster.local"
让我们看看它在后台是如何工作的:
-
在 Vault 上配置了一个服务账户。它具有与 Kubernetes API 交互并执行 TokenReview 请求的权限。
-
在 Vault 上创建一个角色,具有与 Vault 中的密钥交互的权限。该角色与一个 Kubernetes 服务账户映射。
-
创建一个 Pod,并为其分配我们在上一步创建的服务账户。Vault Injector 使用该服务账户的 JWT 发出请求,以便从 Vault 获取密钥。
-
Vault 向 Kubernetes API 发出 TokenReview 请求,以验证 JWT token。
-
Kubernetes 验证 token。
-
Vault 检查与 token 关联的服务账户是否与有权限访问所请求密钥的角色相匹配。
-
Vault 返回密钥值。
-
Vault Injector 将密钥注入到 Pod 中。
以下是我们到目前为止讨论的流程图:

图 11.2 – Kubernetes 身份验证流程
我们配置了 Kubernetes 身份验证。我们将面临的问题之一是 $KUBE_HOST 指向 localhost,因此证书映射到 localhost。这可能会成为一个问题,因为 Vault 可以通过 Docker 主机地址访问 minikube,而该地址与 localhost 不同。因此,我们将使用之前挂载的 proxy 文件。端口将根据 $KUBE_HOST 的端口动态变化而改变。
在另一个终端上,我们将执行以下命令并启用端口转发:
$ KUBE_HOST=$(kubectl config view --raw --minify --flatten --output='jsonpath={.clusters[].cluster.server}')
$ port=$(echo $KUBE_HOST | awk -F/ '{print $3}' | cut -d: -f2)
$ docker exec -it vault_node_1 sh
# apk add haproxy
# haproxy -f ./haproxy.cfg
由于这种身份验证方式涉及多个步骤,审计将有助于提高我们的可观察性:
$ vault audit enable file file_path=/tmp/vault_audit.log
/tmp/vault_audit.log 的审计日志文件可以通过以下命令查看:
$ docker exec -it vault_node_1 tail –f /tmp/vault_audit.log
由于身份验证已启用并且我们已配置所需的服务账户,我们应该继续为 Vault 提供对某些服务账户的权限。
Vault 策略和绑定
使用像 Vault 这样的秘密提供者的好处在于,我们可以存储秘密并提供细粒度的策略。
例如,我们将在 Vault 中存储以下秘密:
$ vault kv put secret/webapp/admin username='john.doe' password='strong-password'
我们希望为该秘密设置一个严格的只读策略。
因此,我们将实现一个具有只读权限的策略,用于访问 secret/webapp/admin 秘密:
$ vault policy write webapp_admin_r - <<EOF path "secret/data/webapp/admin" { capabilities = ["read"] } EOF
策略已经到位,因此,如果我们希望某些工作负载能够使用此策略,我们需要明确指定。在我们的例子中,我们将有一个 Kubernetes 服务账户映射到一个需要 Vault 中秘密的 Pod。服务账户的名称为 simple-app。我们的下一步是将之前创建的策略绑定到该服务账户:
$ vault write auth/kubernetes/role/webapp_admin_r \
bound_service_account_names=simple-app \
bound_service_account_namespaces=default \
policies=webapp_admin_r \
ttl=24h
现在我们应该在 Kubernetes 上创建该服务账户:
$ kubectl create sa simple-app
这个服务账户将被 Vault Injector 用来将秘密注入到应用程序中。仅仅配置服务账户不足以与 Vault 交互,还需要一个集群角色绑定:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: role-tokenreview-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: simple-app
namespace: default
auth-delegator 是一个角色,允许委托身份验证和授权检查。通过这种方式,我们在 Vault 上配置的服务账户可以发出一个 TokenReview 请求,以获取绑定到 simple-app 服务账户的 JWT token。
在应用程序中使用 Vault 秘密
simple-app 服务账户将拥有 auth-delegator 集群角色。该角色启用委托身份验证和检查。Vault 将能够执行委托身份验证。
我们将创建一个应用程序来获取秘密:
apiVersion: v1
kind: Pod
metadata:
name: webapp
labels:
app: webapp
annotations:
vault.hashicorp.com/agent-inject: 'true'
vault.hashicorp.com/role: 'webapp_admin_r'
vault.hashicorp.com/agent-inject-secret-credentials.txt: 'secret/data/webapp/admin'
spec:
serviceAccountName: simple-app
containers:
- name: app
image: nginx
使用的高亮注解对于 vault-agent-injector 来说至关重要,它会重写 Pod 配置,以便 Vault 初始化容器生效。
一旦我们的应用程序启动并运行,我们现在可以检查凭证:
$ kubectl exec -it webapp -c app -- cat /vault/secrets/credentials.txt
data: map[password:strong-password username:john.doe]
metadata: map[created_time:2023-10-08T19:23:50.814986175Z custom_metadata:<nil> deletion_time: destroyed:false version:1]
总体而言,我们成功地以安全的方式与 Vault 进行交互,而无需将任何 Vault 特定的凭据挂载到 Kubernetes Secrets 中,进而也不会出现在 etcd 上。相反,我们依赖于使用 Vault 支持的 Kubernetes 身份验证方法。现在,我们可以通过使用 Vault sidecar 容器最终将凭据注入到应用程序中。不过,Vault 也支持另一种将 Secrets 注入 Kubernetes 的方法:熟悉的 CSI Driver 方法。
Vault 和 CSI Driver
多亏了前几章的内容,我们已经熟悉了 Secret Store CSI Driver。Vault 也让我们能够使用这种方式。使用 CSI Driver 是一种将 Vault 中的 Secrets 挂载到 Pod 上的不同方法;然而,它受益于相同的组件。由于我们已配置 Kubernetes 身份验证方法,我们已经解决了 Vault 身份验证的需求。
然而,我们确实需要安装secrets-store-csi-driver包。我们将执行以下 Helm 命令:
$ helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
$ helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver --namespace kube-system
下一步是创建指向 Vault 中 Secrets 的 SecretProviderClass:
apiVersion: secrets-store.csi.x-k8s.io/v1alpha1
kind: SecretProviderClass
metadata:
name: vault-up-creds
namespace: default
spec:
provider: vault
parameters:
roleName: 'devweb-app'
objects: |
- objectName: "username"
secretPath: "secret/data/devwebapp/config"
secretKey: "username"
- objectName: "password"
secretPath: "secret/data/devwebapp/config"
secretKey: "password"
下一步是配置应用程序,它将使用 CSI 提供者:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
serviceAccountName: simple-app
containers:
- name: nginx-container
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: 'vault-up-creds'
mountPath: '/mnt/secrets-store'
readOnly: true
volumes:
- name: vault-up-creds
csi:
driver: 'secrets-store.csi.k8s.io'
readOnly: true
volumeAttributes:
secretProviderClass: 'vault-up-creds'
我们成功地将 Vault 安全地与 Kubernetes 集成。我们通过 CSI 和 Vault Injector 方法实现了这一点。还有一种将 Kubernetes 工作负载与 Vault 集成的方法,那就是在 Kubernetes 上运行 Vault 安装。
Vault 托管在 Kubernetes 上
在某些情况下,Vault 集群可能会托管在 Kubernetes 上。这是一种具有多种优点的方式。从维护角度来看,你可以利用 Kubernetes 的优势,而从延迟角度来看,只要先前的 Vault 安装是在另一个网络上部署的,它会更快。
我们将通过 minikube 进行安装。
我们将为 Vault 安装配置值:
cat > helm-vault-raft-values.yml <<EOF
server:
affinity: ""
ha:
enabled: true
replicas: 3
raft:
enabled: true
EOF
由于 Vault 运行在 minikube 上,我们无法选择在多个节点上运行。因此,我们禁用了网络亲和性。同时,我们将启用高可用性并使用三个副本来运行 Vault:
$ helm repo add hashicorp https://helm.releases.hashicorp.com
$ helm repo update
$ helm install vault hashicorp/vault --values helm-vault-raft-values.yml
通过在高可用模式下运行 Vault,将使用 Raft 一致性算法。
请注意,Vault 是通过 StatefulSet 安装的,默认节点数为 3 个。
如果查看日志,我们会看到 Vault 尚未初始化:
$ kubectl logs -f vault-0
2023-10-11T21:01:25.268Z [INFO] core: security barrier not initialized
我们现在需要初始化 Vault:
kubectl exec vault-0 -- vault operator init -key-shares=1 -key-threshold=1 -format=json > cluster-keys.json
这将创建 cluster-keys.json 文件。
让我们查看集群密钥的内容:
{
"unseal_keys_b64": [
"the-unseal-key"
],
"unseal_keys_hex": [
"the-unseal-key-hex"
],
"unseal_shares": 1,
"unseal_threshold": 1,
"recovery_keys_b64": [],
"recovery_keys_hex": [],
"recovery_keys_shares": 0,
"recovery_keys_threshold": 0,
"root_token": "root-token"
}
我们的下一步是使用解封密钥来解封 Vault:
$ VAULT_UNSEAL_KEY=$(jq -r ".unseal_keys_b64[]" cluster-keys.json)
$ kubectl exec vault-0 -- vault operator unseal $VAULT_UNSEAL_KEY
接着我们需要逐个加入节点:
$ kubectl exec -ti vault-1 -- vault operator raft join http://vault-0.vault-internal:8200
$ kubectl exec -ti vault-2 -- vault operator raft join http://vault-0.vault-internal:8200
通过加入集群中的节点,领导者选举过程应该会开始进行。
现在我们必须解封其他节点:
$ kubectl exec vault-1 -- vault operator unseal $VAULT_UNSEAL_KEY
$ kubectl exec vault-2 -- vault operator unseal $VAULT_UNSEAL_KEY
解封操作完成后,我们可以开始向 Vault 中添加数据。
在 Kubernetes 上进行端口转发应该是有效的:
$ kubectl port-forward vault-0 8200:8200
接下来,执行以下命令:
#point Vault CLI to localhost
$ export VAULT_ADDR=http://0.0.0.0:8200
# extract vault root token
$ VAULT_ROOT_TOKEN=$(jq -r ".root_token" cluster-keys.json)
#login with root token
$ vault login $VAULT_ROOT_TOKEN
# use the kv-v2 secrets engine on path secret
$ vault secrets enable -path=secret kv-v2
# put secret
$ vault kv put secret/webapp/config username="static-user" password="static-password"
让我们停止转发到 vault-0 并转发到另一个节点:
$ kubectl port-forward vault-1 8200:8200
我们还会评估是否能够获取 Secrets:
$ vault kv get secret/webapp/config
正如预期的那样,我们得到了我们的 Secrets:
$ vault kv get secret/webapp/config
...
====== Data ======
Key Value
--- -----
password static-password
username static-user
Secrets 已经通过节点存储并复制。
请注意,在这个示例中,我们启用了kv-v2引擎。该引擎允许我们拥有多个版本的机密。我们将在接下来的部分中探讨此功能。
我们阅读了关于在 Kubernetes 上运行 Vault 的概述,其中涉及了在生产模式下运行 Vault 的各个方面。因此,值得注意的是,在开发模式和生产模式下运行 Vault 的区别。
开发模式与生产模式
到目前为止,我们一直在开发模式下运行 Vault。开发模式有一些特征:
-
缺乏高可用性
-
默认解封
-
单一解封密钥
-
默认初始化
-
数据存储在内存中
当涉及到生产时,情况应该有所不同。
生产模式
在生产模式下,应该具备高可用性。这是通过 Raft 协议实现的。当 Vault 在生产环境中运行时,我们不是使用独立的 Vault 实例,而是基于 Raft 协议的 Vault 集群。这样可以提供高可用性;如果某个 Vault 节点出现故障,其他可用节点可以继续处理请求。同时,我们还可以设置性能备份节点,这些节点用于处理读取请求,从而实现读取操作的横向扩展。
在生产模式下,另一个非常不同的方面是初始化。当创建 Vault 集群时,必须进行初始化。
密封和解封
为了能够使用 Vault 安装,Vault 需要进行初始化,以便存储后端准备好接收数据。
在初始化过程中,发生了以下情况:
-
生成根密钥
-
根密钥存储在存储后端
-
根密钥被加密
-
根密钥以加密形式存储
由于根密钥是加密的,它不能直接使用;需要解密。为了解密根密钥,需要使用解封密钥。
解封密钥不会以单一密钥的形式分发。它使用 Shamir 的秘密共享算法(en.wikipedia.org/wiki/Shamir%27s_secret_sharing)。解封密钥被分为多个部分,称为份额,这些份额分散在集群中。为了重新组装密钥,需要多个份额的合成,这就是所谓的阈值。假设攻击者窃取了一些份额。如果被窃取的份额少于阈值,则无法重构解封密钥。通过使用 Shamir 的秘密共享算法,Vault 变得更加抗攻击,尤其是那些试图获取解封密钥的攻击。
当我们解封 Vault 时,我们使用解封密钥来检索未加密的根密钥。然后,根密钥将以未加密的形式存储在 Vault 内存中。Vault 通过使用根密钥,能够解密存储数据所需的加密密钥。
当我们在生产模式下运行 Vault 时,初始化是手动进行的。初始化是通过执行 vault operator init 命令来完成的。我们可以指定分割份数——解封密钥。结果,我们将打印出一些解封密钥和一个根令牌。解封密钥将用于重新组合根令牌。根令牌是附带根策略的令牌。它是一个可以使用根密钥执行操作的令牌。
在发生入侵时,可以封存 Vault。通过封存,根密钥将从 Vault 的内存中清除,Vault 将无法解密存储中存在的数据。此外,操作和 Vault 服务将停止。
高可用性
如我们通过在 Kubernetes 上安装 Vault 所看到的,默认情况下,Vault 会在 StatefulSets 中部署三个实例。这是因为使用了 Raft。Vault 是一个分布式系统,Raft 是使用的共识算法。最小节点数为三个。拥有三个节点时,可以容忍一个节点的故障,前提是其他两个节点继续运行。要计算节点容忍度,可以使用公式 (n-1)/2,其中 n 是总节点数。例如,在由五个节点组成的 Vault 安装中,如果两个节点发生故障,Vault 仍将继续运行。一旦 Vault 启动并且节点达成共识,数据将写入领导节点。领导节点必须将写入的数据复制到跟随节点。

图 11.3 – 数据的复制
请注意,如果尝试在跟随节点上执行写操作,Vault 将响应调用者并提供领导节点的地址。这样,数据将被写入领导节点。

图 11.4 – 客户端重定向
Vault 上的 Raft 解决了状态同步问题,从而启用了集成存储选项。然而,Vault 也可以使用各种形式的外部存储。
存储
Vault 提供了多种存储选项。包括外部存储的选项,例如 Cassandra、DynamoDB 等数据库。这些选项提供了底层外部存储所带来的所有好处。例如,在 DynamoDB 的情况下,Vault 可以利用 DynamoDB 提供的备份、审计、高可用性以及其他所有功能。
另一个推荐的存储是集成存储。集成存储由 HashiCorp 维护,和主机共同部署;这种方式由于避免了网络跳跃,使得操作更加高效。此外,使用集成存储可以减少操作复杂性,也不需要外部存储的额外专业知识。
KV-2 与 KV-1 存储引擎
kv 机密引擎用于将机密存储在 Vault 的物理存储中。kv 的最新版本 KV 版本 2 提供了机密存储的版本控制功能。默认情况下,保留 10 个版本的机密。这个数字是可配置的。可以检索机密的旧版本。版本控制会增加存储成本。删除某个版本会将其标记为已删除,但该版本并没有被物理删除;它可以恢复。要强制删除某个版本的物理存储,需要使用 destroy 命令。
下面是 destroy 命令的示例:
$ vault kv destroy -mount=secret -versions=5 secret/webapp/admin
kv 引擎的第一个版本,KV 版本 1,不具备版本控制功能,因此其存储需求比 KV 版本 2 少。
策略
最小权限原则对确保我们的机密安全至关重要。这是通过 Vault 策略实现的。
通过使用策略,我们可以指定访问权限。以我们之前为 Kubernetes 应用程序创建的策略为例,我们创建了 devwebapp 策略,它将提供对 secret/devwebapp/config 路径的读取权限。该策略被附加到将使用该机密的 Kubernetes 应用程序的服务账户上。因此,为该服务账户生成的令牌将仅能读取指定的机密。
总结一下,我们回顾了 HashiCorp Vault 以及如何将其与 Kubernetes 集成。接下来,我们将重点介绍另一个机密管理提供商:CyberArk Conjur。
CyberArk Conjur
CyberArk 的 Conjur 是一个机密管理解决方案。它有商业版和开源版。
Conjur 作为一个解决方案,具备以下优点:
-
基于角色的访问 控制(RBAC)
-
日志记录和审计
-
与 Kubernetes 集成
-
高可用性
让我们更深入地了解 Conjur,并看看它是如何实现这些特性的。
Conjur 的工作原理
Conjur 需要以下组件:
-
反向 Nginx 代理
-
Conjur 应用程序
-
数据库
反向代理用于处理 TLS 终止。它位于 Conjur 应用程序前面。Conjur 应用程序是基于 Ruby 的应用程序,负责安全存储机密。该应用程序由 PostgreSQL 数据库支持。
高可用性
Conjur 使用 Raft 协议实现高可用性。它采用领导者-跟随者架构。主节点将处理传入流量。如果主节点出现故障,跟随节点将提升为主节点。
服务器密钥
Conjur 使用数据密钥、Conjur UI 密钥和 SSL 密钥。这些密钥在 Conjur 服务器初始化时生成明文。为了增强初始化的安全性,这些密钥应进行加密。这是通过使用主密钥来实现的。
通过使用主密钥,密钥被加密。
在使用 Conjur 服务之前,我们需要使用主密钥解锁密钥。一旦使用主密钥解锁加密的密钥,它们将存储在 Linux 密钥链和基于内存的文件系统中。Conjur 提供了使用 AWS KMS 或硬件安全模块作为主密钥的选项。
存储
Conjur 用 PostgreSQL 数据库来存储秘密。Conjur 享受 PostgreSQL 数据库提供的所有功能,比如复制、审计和精细的权限管理。
例如,存储在 Conjur 中的一个秘密可以通过查询 Secrets 表在 PostgreSQL 数据库中找到:
# SELECT*FROM secrets;
resource_id |version |value|expires_at
----------------------+--------+-----+------------
acccount:variable:test|1 |\x4..|
(1 row)
这个秘密是不可用的,因为它已经被加密。在发生泄露时,秘密的名称可能会泄露,但实际的秘密值不会泄露,因为需要解密密钥。
版本控制
Conjur 还具有秘密版本控制功能。它会保留一个秘密的最后 20 个版本。
策略
通过使用 Conjur 策略,我们可以定义安全规则,描述哪些角色有权限对 Conjur 资源执行特定操作。
下面是 Conjur 策略的示例:
- !policy
id: ExamplePolicy
body:
- !host webApp
- !variable secretVar
- !permit
role: !host webApp
privileges: [read, execute]
resource: !variable secretVar
如我们所见,ExamplePolicy 策略为非人类身份 webApp 提供了读取 secretVar 变量的权限。
审计日志
正如我们在前几章中所看到的,审计日志在秘密管理中至关重要。
从文档中,我们可以看到审计日志的 JSON 格式示例:
{
"subject@43868": {
"resource": "demo:group:security_ops"
},
"policy@43868": {
"version": "1",
"id": "demo:policy:root"
},
"auth@43868": {
"user": "demo:user:admin"
},
"action@43868": {
"operation": "add"
},
"PROGRAM": "conjur",
"PID": "e9c07c05-4dc2-4809-b7e1-43f5d3a20599",
"MSGID": "policy",
"MESSAGE": "demo:user:admin added resource demo:group:security_ops",
"LEVEL": "notice",
"ISODATE": "2020-04-14T20:40:24.806+00:00",
"FACILITY": "auth"
}
上述日志包含了我们识别 Conjur 服务器上发生事件所需的所有信息。action 显示了发生的操作,auth 显示了执行该操作的实体,subject 显示了受影响的资源。
Kubernetes 集成
Conjur 提供了多种启用 Kubernetes 集成的选项,其中之一是 Kubernetes 认证客户端。
Kubernetes 认证客户端
Conjur 拥有 Kubernetes 认证客户端。使用 Conjur Secrets 的 Kubernetes 应用程序有一个侧车容器,里面有 Conjur 客户端。使用服务账户的客户端将与 Conjur 进行认证。然后,Conjur 将发放一个临时令牌。侧车容器将使用该临时令牌来检索秘密。
在本节中,我们更深入地了解了秘密管理器 Conjur,识别了它的功能以及它如何与 Kubernetes 集成。这些功能与我们在前几章中重点讨论的功能一起,构成了使用外部秘密存储来管理秘密的标准。
安全管理秘密的特性
正如我们所见,存储外部秘密时有一些特性。在本章中,我们深入探讨了 HashiCorp Vault 及其如何与 Kubernetes 集成。我们还了解了另一个秘密管理提供商——Conjur。
一个秘密管理器需要具备某些特性:
-
高可用性
-
数据加密
-
安全访问
-
版本控制
-
基于角色的访问控制(RBAC)
-
与 Kubernetes 的集成
-
审计
这些特性对于确保应用程序秘密的安全和稳健存储至关重要。
高可用性
高可用性是确保我们数据安全所必需的。它可以在服务中断的情况下保护我们免于丢失任何数据,确保业务的持续性。像 Vault 和 Conjur 这样的外部密钥存储通过利用 Raft(一种流行的共识方法)来实现高可用性。通过采用共识方法,集群方式的高可用性成为可能;我们可以向不同数据中心添加多个节点,形成集群并共享工作负载。由于这一选择,万一出现故障,数据将以能够容忍故障的方式进行分布。
数据加密
外部密钥存储中的数据应该进行加密。用于加密外部密钥存储中数据的加密密钥也应该加密。正如我们之前所见,提供者通过实现诸如 Shamir 秘密共享机制或使用主密钥加密数据加密密钥来实现这一点。然后,数据加密密钥被用来加密实际数据。
安全访问
访问通过适当的身份验证方法和最小权限原则来确保安全。我们可以通过策略实现这一点。像 Vault 和 Conjur 这样的外部密钥存储为我们提供了策略选项。通过策略,我们可以指定某个身份对密钥资源可以执行的操作。这使我们能够创建精细的权限,从而减少与过度授权帐户交互带来的风险。
版本控制
本书中我们使用的所有密钥存储提供者都提供了版本控制。版本控制对于防止意外删除和实现平滑的密钥轮换至关重要。由于秘密本质上是敏感的,这使得密钥轮换成为必要。拥有版本控制可以使开发团队以稳健且有韧性的方式进行密钥轮换操作。
与 Kubernetes 的集成
如我们所见,与 Kubernetes 的集成是通过使用最小权限来实现的,并且我们没有将任何敏感的密钥存储在 etcd 中。相反,HashiCorp Vault 或 Conjur 是通过 Kubernetes 服务帐户进行管理的。
秘密被注入到我们的应用程序中,而无需将其存储在 etcd 中。此外,我们不需要存储任何能够与秘密存储通信的秘密。
审计
审计对秘密管理至关重要。通过审计,我们能够记录对外部密钥存储资源的活动。在所有外部密钥存储选项中,审计功能都是存在的。在发生泄漏或任何恶意行为的情况下,审计可以帮助我们识别暴露的情况并采取补救措施。审计还可以帮助调试,并确保系统正常运行。
总结
在本章中,我们深入探讨了 HashiCorp Vault,它如何与 Kubernetes 集成,以及可以通过 Kubernetes 工作负载访问 Vault 秘密的不同方法。我们通过 vault-agent-injector 方法或 CSI 驱动程序来使用 Vault 中的秘密。此外,我们还在 Kubernetes 上进行了 Vault 安装,并确定了 Vault 生产部署的特性。我们还了解了另一个秘密提供者 CyberArk Conjur 及其功能。在下一章中,我们将重点关注秘密的整体使用案例以及可以采用的不同方法,以便与外部秘密存储集成。我们还将深入探讨与外部秘密存储集成的方法。
第十二章:与秘密存储集成
Kubernetes 提供了一个基本的系统来管理 Secrets,但通常不被认为足够安全,尤其是在生产环境中处理密码、令牌或密钥等敏感数据时。为了解决这个问题,将先进的秘密管理工具集成到 Kubernetes 中变得至关重要。这些工具通过加密增强了安全性,并提供了敏感信息的集中管理。它们超越了 Kubernetes Secrets 的原生功能,使得安全性更加强大和合规。本章将讲解如何将秘密管理工具与 Kubernetes 集成。内容包括如何在 Kubernetes 中配置外部秘密存储,以及可用的不同类型的外部秘密存储。你将了解使用外部秘密存储的安全性影响,并学习如何通过不同的方法,如 init 容器、sidecar、CSI 驱动程序、操作员和封印的 Secrets,来存储敏感数据。本章还将涵盖使用外部秘密存储的最佳实践,以及它们如何影响 Kubernetes 集群的整体安全性。本章我们将涉及以下主要主题:
-
在 Kubernetes 中配置外部秘密存储
-
与外部秘密存储的集成
-
安全性影响与最佳实践
-
实际与理论的平衡
技术要求
为了将概念与实际操作示例结合,我们将使用一系列常用于与外部秘密管理和 Kubernetes 交互的工具和平台:
-
minikube:它在你的计算机上通过虚拟机(VM)运行一个单节点的 Kubernetes 集群。可以通过
minikube.sigs.k8s.io/docs/start/的指南进行设置。 -
Helm:这是一个 Kubernetes 的包管理工具,可以简化部署过程。有关安装的指南,可以查看
helm.sh/docs/intro/install/。 -
kubectl:这是 Kubernetes 的命令行工具。安装指南可以在
kubernetes.io/docs/tasks/tools/install-kubectl/找到。 -
外部秘密管理工具:虽然可以使用多种工具进行演示,但建议使用 Hashicorp Vault。它的官方安装指南可以在
www.vaultproject.io/docs/install找到。
将秘密存储与 Kubernetes 集成
正如我们在前几章中探讨的那样,Kubernetes 本身具备秘密管理功能。然而,在大规模操作或特定的安全需求下,原生的 Kubernetes Secrets 可能会有所不足。之前讨论过的各种秘密管理工具的多样性,正是暗示了这一需求。但为什么要将它们与 Kubernetes 集成呢?
将第三方工具与 Kubernetes 集成带来以下好处:
-
操作一致性:对于已经在 Kubernetes 外使用工具的组织,集成提供了一个统一的秘密管理体验。
-
增强的安全特性:许多外部工具提供了高级功能,如秘密轮换、细粒度的访问控制和多层加密方法,这些功能在 Kubernetes 原生环境中并不容易获得,或者需要额外配置。
-
可扩展性和性能:在大规模环境中,仅使用 Kubernetes 原生的 Secrets 管理大量秘密可能变得复杂。为高容量操作设计的外部工具可以有效应对这一挑战。
-
高级审计追踪:在日益严格的法规和增加的网络威胁环境中,拥有一个全面的审计能力至关重要,而非奢侈。许多工具配备了完整的日志记录和警报功能。
-
详细的审计能力:这些能力确保合规性,提升安全性,增加责任追踪,检测异常活动,支持基于信息的决策,提供法律证据,增强操作效率,建立客户信任,减少内部威胁,并为未来的改进提供历史分析。
-
跨平台兼容性:随着混合云和多云策略的兴起,秘密管理器可以在不同的云平台之间提供一致的 Secrets 管理,使得在这些异构环境中管理 Secrets 更加简便。
虽然我们在前面的章节中已经认识到各种云秘密存储和第三方秘密存储的能力,本章旨在弥补这一差距,专注于集成。主要的焦点是展示这些秘密存储如何与 Kubernetes 无缝集成,充分利用两者的优势。
通过后续章节,我们将深入探讨这些集成的机制,提供理论与实践的双重理解。从 Kubernetes 扩展到 Pod 生命周期机制,每种方法都将展示不同的集成策略和方法。到本章结束时,我们的目标是为您提供一套强大的策略和见解,帮助您做出与独特操作需求无缝对接的选择。
在 Kubernetes 中配置外部秘密存储
Kubernetes 的去中心化特性和动态工作负载要求有一个强大的秘密管理解决方案。本节提供了关于一般配置过程的见解,并阐明了在 Kubernetes 中使用秘密的两种主要范式。
以下是一般的配置步骤:
-
选择秘钥存储:首先选择一个适合组织需求的 Secrets 管理工具,考虑安全需求、可扩展性、合规标准、团队熟悉度等因素。可选的方案非常多,包括云原生解决方案,如 AWS/GCP Secrets Manager 和 Azure Key Vault,也有诸如 HashiCorp Vault 和 CyberArk 等工具。
-
初始化并连接到 Kubernetes:一旦选择了秘钥存储,就开始其初始化。根据架构偏好,将其部署在 Kubernetes 集群内部或旁边,确保秘钥存储与 Kubernetes 之间的顺畅连接。
-
处理认证与授权:在 Kubernetes 和秘钥存储之间建立强大且安全的通信渠道。机制可以包括 IAM 角色、令牌、服务帐户或客户端证书。同时,建立精细的授权控制,确保只有授权的服务或应用访问指定的 Secrets。
-
确定秘钥获取与使用方式:深入探讨 Secrets 将如何被使用。决定是否将外部存储中的 Secrets 转换为原生 Kubernetes Secrets,或者在需要时直接从外部存储中提取。
-
测试配置:在将集成应用到生产环境之前,进行全面测试。验证密钥的获取、使用以及其他已配置的功能,确保它们按预期工作。
-
监控与审计:作为最后一步,实施监控机制来监督对 Secrets 的访问。并通过日志记录和审计工具增强这一过程,快速发现未经授权的访问尝试或潜在的安全漏洞。
完成这些通用配置步骤为在 Kubernetes 环境中实现安全高效的 Secrets 管理奠定了坚实的基础。现在,秘钥存储已经集成、认证并授权,您可以继续进入下一阶段,确保应用程序能够无缝且安全地使用 Secrets。
Kubernetes 中的秘钥消费
在集成外部秘钥存储时,有两种主要模式主导了 Kubernetes 中的密钥使用:
-
转换为原生 Kubernetes Secrets:将外部存储中的 Secrets 转换为原生 Kubernetes Secrets 使您能够利用 Kubernetes 原生的方法进行 Secrets 管理和访问。这带来了缓存的好处,减少了频繁外部请求的需求。此外,它还消除了一个关键的故障点。然而,也存在诸如冗余和确保两个秘钥存储位置之间同步等挑战。
-
直接从外部存储中获取:直接检索机密确保应用程序能够获取到最新的版本,减少了同步的需求,同时也能保持更清晰的审计记录。然而,这种方法可能会由于外部获取操作而引入延迟,并且会对外部存储产生直接依赖。
总结来说,在 Kubernetes 中配置外部机密存储的过程是构建可扩展、安全的云原生基础设施的基础。清楚理解配置步骤和各种机密消费方式为制定有效的机密管理策略奠定了基础。未来的章节将更深入地探讨这些主题及其相关机制。
与外部机密存储集成
将外部机密存储与 Kubernetes 集成是保护应用程序和敏感数据的关键组成部分。本节探讨了可以无缝将外部机密存储与 Kubernetes 集群集成的各种机制和模式,从而增强安全性和管理效率。
Kubernetes 扩展和 API 机制
Kubernetes 提供了各种扩展和 API 机制,可以利用它们与外部机密存储进行连接和交互。在本部分,我们将深入探讨可用选项,并指导你如何有效地利用它们进行机密管理。
Kubernetes 中的准入控制器和变异 webhook 用于机密管理
Kubernetes 提供了一整套工具,用于控制和修改其环境中的行为。其中,准入控制器和变异 webhook在增强 Kubernetes 集群的操作和安全性方面发挥了至关重要的作用。特别是在机密管理方面,这些工具可能会带来革命性的变化。
准入控制器是 Kubernetes 控制平面的一部分,负责管理和执行集群的使用方式。它们会在对象持久化之前、但在请求被认证和授权之后,拦截对 Kubernetes API 服务器的请求。通过这样做,准入控制器能够采取特定行动,例如拒绝请求或在存储之前修改对象。有几个内置的准入控制器,但对于像机密管理这样的特定需求,你可能需要自定义控制器。
当我们需要由准入控制器提供的灵活性,但又需要自定义逻辑时,变异 webhook 就会发挥作用。它们允许你在创建或修改特定资源时运行自定义代码(或自定义函数)。这对机密管理非常有价值,因为你可以以编程方式修改 Kubernetes 资源;例如,你可以在 Pod 创建时注入机密引用。
假设有一个场景,你不希望开发者在清单中显式地定义 Secrets。通过使用变更 webhook,你可以建立一个系统,开发者只需要指定标签或注释。Webhook 会拦截 Pod 创建请求,识别标签或注释,并注入所需的 secret 引用,从而抽象化了与 Secrets 的直接交互。
这是设置用于 Secret 注入的变更 webhook 的示例:
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: secret-injector-webhook
webhooks:
- name: secret-injector.example.com
clientConfig:
service:
name: secret-injector-service
namespace: default
path: "/mutate"
caBundle: [CA_BUNDLE]
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
让我们分解一下这个配置:
-
这个 webhook 被命名为
secret-injector.example.com。 -
clientConfig指定了处理 webhook 的服务。在这种情况下,服务名为secret-injector-service。 -
rules部分定义了何时调用此 webhook。在这里,它设置为在创建 Pod 时运行。 -
已创建。
当一个新的 Pod 被创建时,请求会被我们的 webhook 拦截。服务 secret-injector-service 然后处理这个请求,检查特定的标签或注释,并决定是否注入 secret 引用。
Admission 控制器和变更 webhook 的结合提供了一种强大的机制,用于简化和强制执行 Secrets 管理的最佳实践。通过将 Secrets 管理的责任交给这些工具,开发者可以专注于他们的应用逻辑,同时确保 Secrets 以安全和合规的方式处理。
总结来说,在希望增强 Kubernetes 集群中的 Secrets 管理功能时,考虑利用 admission 控制器和变更 webhook。它们不仅有助于维护集群的完整性,还能自动化并强制执行处理敏感数据的最佳实践。
Secrets 管理中的自定义资源定义
Kubernetes 通过使用 自定义资源定义(CRDs)允许其 API 的扩展性。CRDs 使集群操作员能够在 Kubernetes 中引入新的资源类型,而无需修改 Kubernetes 核心代码库。在处理 Secrets,尤其是那些存储在 Kubernetes 集群外部的系统(如 AWS Secrets Manager 或 HashiCorp Vault)中的 Secrets 时,CRDs 可以提供一种更符合 Kubernetes 原生方式的管理和访问方法。
定义 ExternalSecret CRD
外部 secret 的 CRD 定义可能如下所示:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: externalsecrets.k8s.example.com
spec:
group: k8s.example.com
versions:
- name: v1
served: true
storage: true
scope: Namespaced
names:
plural: externalsecrets
singular: externalsecret
kind: ExternalSecret
shortNames:
- esec
一旦定义了 CRD,下一步就是创建这个新资源类型(ExternalSecret)的实例。然而,仅仅定义和创建 CRD 并不能赋予其功能。为了使 ExternalSecret 资源有意义,你需要一个自定义控制器来理解如何解读和操作这些资源。
使用 ExternalSecret CRD
假设你有一个名为 database-password 的 secret 存储在 AWS Secrets Manager 中,你可能会定义一个引用它的 ExternalSecret 资源,内容如下:
apiVersion: k8s.example.com/v1
kind: ExternalSecret
metadata:
name: my-database-password
spec:
backendType: awsSecretsManager
data:
- key: database-password
name: dbPassword
region: us-west-1
这是这个资源的分解:
-
metadata.name:这是 Kubernetes 中分配给该ExternalSecret的名称。它不必与 AWS Secrets Manager 中的名称完全匹配。 -
spec.backendType:表示要使用的外部 Secrets 管理器;在此情况下,是 AWS Secrets Manager。 -
spec.data:这是一个列表,指示要获取的密钥。 -
key:这是在 AWS Secrets Manager 中密钥的名称或标识符。 -
name:这是密钥在呈现给 Kubernetes Pods 时的名称。 -
spec.region:指定存储密钥的 AWS 区域。
在应用以下资源后:
kubectl apply -f my-external-secret.yaml
一个观察ExternalSecret资源的自定义控制器将执行以下操作:
-
检测到新的
ExternalSecret的创建。 -
从规格中理解它应与
us-west-1区域的 AWS Secrets Manager 进行通信。 -
与 AWS Secrets Manager 进行身份验证(假设它具有必要的权限)。
-
获取
database-password密钥。 -
将此密钥以
dbPassword的名称展示给 Kubernetes Pods。具体方法可以是创建一个本地 Kubernetes 密钥、将其设置为环境变量,或根据控制器设计将其放入tmpfs卷中。
实际上,CRD 结合自定义控制器提供了一种强大的机制,来扩展 Kubernetes 的能力。在密钥管理方面,CRD 使得 Kubernetes 能够自然地与外部密钥存储解决方案集成,使得提取和使用密钥的过程对最终用户无缝化。
Kubernetes API 扩展:自定义 API 服务器
构建一个自定义 API 服务器使我们能够定义 API 行为,包括与外部密钥存储的交互。Pod 可以通过自定义 API 请求密钥,API 服务器可以从外部存储中获取密钥、处理它并返回。然而,运行和维护一个自定义 API 服务器并不是简单的。你需要设置它,确保它的安全性,并可能处理扩展和故障转移。
请注意,这是一个简化的示例,重点讲解概念配置:
apiVersion: v1
kind: Pod
metadata:
name: custom-api-server
spec:
containers:
- name: custom-api-server
image: my-custom-api-server:latest
ports:
- containerPort: 443
volumeMounts:
- mountPath: /etc/custom-api-server
name: config
volumes:
- name: config
configMap:
name: custom-api-server-config
为了让自定义 API 服务器能够与主 Kubernetes API 服务器和外部密钥存储一起工作,它需要在其配置custom-api-server-config中进行特定设置。这些设置包括如何验证谁有权限访问,这叫做身份验证,以及如何进行通信的规则,称为 API 规范。通常,这个设置会使用基于服务的身份验证或基于角色的身份验证。基于服务的身份验证检查请求访问的服务的身份,而基于角色的身份验证则查看用户或服务的角色来决定访问权限。一个常见的例子是使用 AWS 中的 IRSA 角色,Kubernetes 服务可以安全地访问 AWS 资源。
这种方法提供了与外部密钥存储的无缝交互,特别是对于那些更熟悉 kubectl 而不是 Hashicorp Vault CLI 的团队。通过扩展 API,用户可以保持在熟悉的环境中。然而,尽管 API 扩展非常强大,单靠扩展 API 并不能完成整个过程。你需要额外的组件或流程来确保 Pods 安全高效地使用这些 Secrets。这可以通过代理、控制器或其他编排机制来实现,它们会监控这些自定义或转换后的 Secrets,并使其可供 Pods 使用。
Kubernetes 扩展和 API 机制提供了一种灵活且强大的方式来集成外部密钥存储,提供多种选项以适应不同的用例和需求。了解如何利用这些工具是有效管理 Kubernetes 中 Secrets 的关键。
Pod 生命周期和管理机制
在 Pod 生命周期中管理 Secrets 对于维护安全性和操作效率至关重要。本节重点介绍 Kubernetes 提供的机制,用于在 Pod 生命周期中注入和管理 Secrets。
Init 容器
Init 容器在应用容器之前运行,可用于设置任务,例如从外部存储获取 Secrets。如果你的应用需要在启动之前通过配置文件填充 Secrets,Init 容器可以获取这些 Secrets,填充配置文件并将其存储在共享卷中。
下面是一个示例配置:
apiVersion: v1
kind: Pod
metadata:
name: app-with-init-container
spec:
initContainers:
- name: fetch-secrets
image: secret-fetcher:latest
volumeMounts:
- name: config-volume
mountPath: /config
containers:
- name: main-app
image: my-app:latest
volumeMounts:
- name: config-volume
mountPath: /config
volumes:
- name: config-volume
emptyDir: {}
通过集成这个示例 Init 容器配置,你可以确保应用在启动之前就能访问所需的 Secrets。
Sidecar 容器
Sidecar 容器与 Pod 中的主容器一起运行,可以用于在 Pod 生命周期期间动态管理 Secrets。如果你的应用需要定期刷新其 Secrets 而不重启,Sidecar 容器可以获取最新的 Secrets,并更新共享配置或通知主应用。
下面是一个示例配置:
apiVersion: v1
kind: Pod
metadata:
name: app-with-sidecar
spec:
containers:
- name: main-app
image: my-app:latest
volumeMounts:
- name: config-volume
mountPath: /config
- name: secret-refresher
image: secret-refresher:latest
volumeMounts:
- name: config-volume
mountPath: /config
volumes:
- name: config-volume
emptyDir: {}
Kubernetes 中的 Sidecar 容器通过与主容器并行运行,增强了 Secrets 管理,使 Secrets 可以动态更新,而无需重启应用,正如提供的示例配置所示。
DaemonSets
DaemonSets 确保所有(或部分)节点运行一个 Pod 的副本,使其适合用于节点级任务,例如设置节点范围的 Secrets 或 Secrets 管理工具。如果你有一个节点级应用(例如,日志代理)需要特定的 Secrets,可以使用 DaemonSet 确保每个节点获取自己的 Secrets。
下面是一个示例配置:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-level-agent
spec:
selector:
matchLabels:
name: node-level-agent
template:
metadata:
labels:
name: node-level-agent
spec:
containers:
- name: agent
image: my-agent:latest
env:
- name: NODE_SECRET
valueFrom:
secretKeyRef:
name: node-secret
key: secret-key
通过利用 DaemonSets 来进行此类节点级操作,你可以确保在整个 Kubernetes 集群中一致且安全地分发 Secrets。
环境控制器
不同于 CRD,环境控制器并不寻求扩展 Kubernetes API。相反,它们直接在 Pod 的上下文中动态管理环境变量。其优势在于可以在 Pod 层级进行直接集成,避免了需要额外的 CRD 管理或特定于新 CRD 的控制器基础设施。对于从环境变量读取 Secrets 的应用程序,如果你希望避免直接在 Kubernetes 中存储这些 Secrets,环境控制器可以在 Pod 启动前获取并注入这些 Secrets,避免了应用程序或其他容器去获取它们的需求。
假设我们使用了一个自定义控制器,它监控 EnvSecret CRD 资源。这里是一个示例配置:
apiVersion: mycontroller/v1
kind: EnvSecret
metadata:
name: database-creds
spec:
externalRef: db-credentials-in-vault
target:
envVarName: DB_CREDS
podSelector:
matchLabels:
app: my-app
这个假设的 EnvSecret CRD 资源指示控制器从外部存储中获取 db-credentials-in-vault,并将其填充到标签为 app: my-app 的 Pods 的 DB_CREDS 环境变量中。
有效地管理 Secrets 贯穿整个 Pod 生命周期,确保应用程序在需要时能够访问到必要的敏感信息,同时保持高水平的安全性。
专用的 Kubernetes 模式 – SealedSecrets
SealedSecrets 是一个 Kubernetes 控制器和工具,用于一键加密的 Secrets。它旨在帮助开发人员加密一个 secret 并提交到控制平面(通常是在 Git 仓库中并通过 持续集成和持续交付 (CI/CD)进行管理)。Kubernetes 管理员拥有解密密钥,并且当看到加密的 Secret(SealedSecret)时,控制器会将其解密为常规的 Kubernetes Secret。它通过确保实际的 secret 值不会直接存储在 Git 仓库中,而是以加密格式保存,来增强安全性。
SealedSecrets 的亮点在于它的简洁性:Secrets 以只有集群本身能够解密的方式进行加密,从而允许 Secrets 安全地与应用程序的配置一起存储,通常是在版本控制中。
让我们通过 SealedSecrets 过程的不同阶段来了解一下:
-
kubesealCLI 工具 -
使用
kubeseal加密一个 secret,这将创建一个 SealedSecret -
kubectl,就像其他任何 Kubernetes 资源一样 -
解密:SealedSecret 控制器在集群中运行,它解密 SealedSecret 并创建一个标准的 Kubernetes secret
在 DevOps 中使用 SealedSecrets 的主要好处是其易用性。它允许开发人员将应用程序的配置和 Secrets(加密形式)一起安全地进行版本控制。然而,值得注意的是,SealedSecrets 与常规的 Kubernetes Secrets 并不完全相同。当解密时,SealedSecrets 会在集群内转化为标准的 Kubernetes Secrets。这些 Secrets 只有具有相应权限的工作负载才能访问。
让我们简要探讨 SealedSecrets 的创建和应用,以及它们如何在 Pods 中使用:
-
创建 一个 SealedSecret:
这里是一个创建简单 Kubernetes 密钥的快速示例。
apiVersion: v1 kind: Secret metadata: name: my-secret type: Opaque data: password: [base64_encoded_value] sealed-secret.yaml is as follows:apiVersion: bitnami.com/v1alpha1 kind: SealedSecret metadata: name: my-secret namespace: default spec: encryptedData: password: [encrypted_value] -
通过
kubectl执行sealed-secret.yaml文件,SealedSecret 控制器将解密该文件并在指定的命名空间中创建一个名为my-secret的常规 Kubernetes 密钥。 -
在 Pods 中的使用:
一旦 SealedSecret 被解密并且常规密钥可用,Pods 可以像引用其他密钥一样引用此密钥,例如,将其挂载为卷或用来设置环境变量:
apiVersion: v1 kind: Pod metadata: name: my-app spec: containers: - name: app image: my-app:latest env: - name: PASSWORD valueFrom: secretKeyRef: name: my-secret key: password
本质上,SealedSecrets 使密钥在集群外部的加密存储和管理成为可能,而集群内的控制器确保它们在需要时被安全解密并转化为可访问的 Kubernetes 密钥。它和谐地弥合了密钥加密的操作需求与这些密钥在 Kubernetes 生态系统中实际使用之间的鸿沟。
Kubernetes 密钥的 Secret Store CSI 驱动程序
Secret Store CSI 驱动程序为将外部密钥管理平台与 Kubernetes 集成提供了先进的解决方案。这个强大的机制旨在提高 Kubernetes 工作负载中处理密钥的安全性和效率。
了解用于密钥管理的 CSI 驱动程序
容器存储接口(CSI)是将各种存储系统连接到 Kubernetes 等调度器的关键标准。在密钥管理领域,Secret Store CSI 驱动程序充当这个连接桥梁:
-
CSI 驱动程序:从根本上讲,这是 Kubernetes 与众多外部存储系统之间的接口。它负责动态地提供密钥。在一个密钥访问及时性至关重要的世界里,驱动程序提供的这一能力是无价的。
-
secrets-store.csi.k8s.io使 Kubernetes 能够从高级外部密钥存储中提取并挂载多个密钥、证书和机密。然后,这些数据会作为卷提供给 Pods。连接时,封装的数据会被挂载到 Pod 的文件系统中。这种直接访问确保了应用程序可以轻松使用这些密钥。
CSI 驱动程序作为 Kubernetes 与外部存储解决方案之间的重要桥梁。通过促进密钥、证书和机密的无缝和安全提供,确保了及时访问和高效集成。
密钥 CSI 驱动程序的独特方面
Secret Store CSI 驱动程序具有几个独特的特点:
-
双重架构:驱动程序将 CRD 和 DaemonSets 结合在一起。CRD 负责引导自定义行为和与外部密钥存储的交互,而 DaemonSets 确保驱动程序在集群中的每个节点上都有一个副本在运行。这种架构确保了密钥在集群中均匀地可用。
-
tmpfs内存文件系统。此方法确保密钥不会被写入节点磁盘,从而提高安全性。 -
节点级接口:由于它在节点级别与 Kubernetes CSI 接口一起工作,因此驱动程序要求每个主机上具有根用户权限。
Secret Store CSI 驱动程序凭借其创新的双重架构、直接的内存中密钥挂载和节点级操作而脱颖而出,这需要 root 权限,并确保在 Kubernetes 集群中对 Secrets 管理的统一、安全方法。以下是一个示例配置,演示了 Secret Store CSI 驱动程序的端到端使用:
-
部署 Secret Store CSI 驱动程序:
要在 Kubernetes 中快速开始使用 Secret Store CSI 驱动程序,可以使用 Helm 3 进行安装:
-
首先,通过以下命令添加驱动程序的 Helm 仓库:
kube-system namespace:helm install csi-secrets-store secrets-store-csi-driver/secrets-store-csi-driver --namespace kube-system
精确的部署步骤可能因 Kubernetes 环境而异,但通常你可以选择使用 helm charts 或原始 YAML 文件。这些都可以在官方仓库中找到,网址是
github.com/kubernetes-sigs/secrets-store-csi-driver。 -
-
声明 SecretProviderClass:
这是告诉驱动程序在哪里以及如何获取密钥的核心对象:
apiVersion: secrets-store.csi.x-k8s.io/v1alpha1 kind: SecretProviderClass metadata: name: my-secret-provider spec: provider: [PROVIDER_NAME] # e.g., azure, vault parameters: # Your specific provider parameters here -
SecretProviderClass已设置,可以在你的 Pod 配置中引用它:apiVersion: v1 kind: Pod metadata: name: my-pod spec: containers: - name: my-container image: my-image volumes: - name: secrets-volume csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "my-secret-provider" volumeMounts: - name: secrets-volume mountPath: "/mnt/secrets"
提供的示例配置概述了部署驱动程序、声明 SecretProviderClass 并将其整合到 Pod 中的步骤。接下来,我们将深入探讨这种方法的优点和限制。
Secrets CSI 驱动程序的优点和限制
Secret Store CSI 驱动程序虽然功能强大,但也有其优点和挑战:
-
tmpfs确保 Secrets 永远不会保存在节点磁盘上 -
动态更新:根据外部存储的能力,Secrets 可以动态更新,确保工作负载访问最新数据
-
限制:
-
复杂的设置:双重性质(CRD 和 DaemonSet)可能使初始设置更为复杂
-
节点级别访问:在每个节点上要求 root 权限在某些环境中可能被视为安全隐患
-
提供者依赖:某些功能可能依赖于外部密钥存储的能力
-
有关详细信息、最佳实践和社区支持,请始终参考官方文档,网址是 secrets-store-csi-driver.sigs.k8s.io/。对于那些有兴趣贡献、了解架构或探索其详细功能的人,项目的 GitHub 仓库是一个有价值的资源(GitHub 上的 Secret Store CSI 驱动程序,网址是 github.com/kubernetes-sigs/secrets-store-csi-driver)。
总之,Secret Store CSI 驱动程序标志着 Kubernetes 在管理 Secrets 方面的重大进展。采用它可以带来更安全和高效的 Secrets 管理,但与所有工具一样,正确的实施至关重要。
服务网格集成用于密钥分发
在 Kubernetes 不断发展的世界中,服务网格已经成为处理服务间通信的关键叠加层。它的主要价值主张在于抽象化服务间交互的复杂性,减轻开发者将此逻辑嵌入应用代码的负担。对于密钥分发,尤其是在证书和令牌的上下文中,服务网格发挥着至关重要的作用。总而言之,服务网格是一个为微服务应用提供配置的基础设施层,使得通信变得灵活、可靠且快速。它通过轻量级网络代理实现,这些代理与应用代码一起部署,而无需应用程序感知。
服务网格中的密钥 – 证书和令牌
当我们在服务网格的上下文中谈论“密钥”时,我们主要指的是以下内容:
-
证书:这些证书用于在网格中的服务之间建立信任。互信 TLS(mTLS)通常用于确保客户端和服务器服务彼此信任。服务网格自动化这些证书的提供和轮换。
-
令牌:对于某些身份验证和授权场景,可能会使用令牌(如 JWT)。这些令牌可以由服务网格生成、验证和轮换,确保应用程序不必处理这些复杂性。
服务网格通过自动化简化并安全管理证书和令牌。在服务网格中,密钥的处理和分发既安全又动态,遵循着整个生命周期中广泛应用且成熟的流程。
-
动态密钥创建:服务网格可以与外部证书颁发机构(CAs)集成,甚至可以拥有内置的 CA。按需生成证书,当服务加入网格时,自动为其创建证书。
-
密钥分发:生成后,这些证书(或令牌)会被安全地分发给相关服务。这一分发过程通过与每个服务实例配套的边车代理进行。
-
轮换和续期:使用服务网格的一个关键优势是它能够自动化密钥的轮换。此功能通过定期更新这些敏感凭证来增强安全性。经过预定时间后,密钥会被续期,旧的密钥将失效,整个过程不需要停机或手动干预。
-
吊销:在某个服务可能被攻破的情况下,服务网格可以迅速吊销相关的密钥,减轻潜在的损害。
服务网格自动化整个过程,从按需创建和分发证书及令牌到管理它们的轮换和吊销,确保安全高效的操作,并最小化手动干预。
服务网格实例 – Istio
虽然有多种服务网格实现可用,但Istio作为一个突出的例子脱颖而出。
对于其证书管理,Istio 使用一个名为Citadel的组件。它充当证书颁发机构(CA),为网格中的服务生成、分发、轮换证书,并具有撤销证书的能力。借助其内置功能,Istio 的 Citadel 确保网格中的 mTLS 通信始终保持安全。
以下是启用 Istio 中 mTLS 的示例配置:
apiVersion: "security.istio.io/v1beta1"
kind: "PeerAuthentication"
metadata:
name: "default"
namespace: "foo"
spec:
mtls:
mode: STRICT
Istio 作为领先的服务网格实现,利用其 Citadel 组件,不仅用于强大的证书管理,以确保双向 TLS 通信的安全;还扩展了其功能,涵盖身份验证、身份提供和策略执行,成为一个全面的解决方案,用于在服务网格架构中管理安全性。
好处与考虑事项
服务网格在 Kubernetes 中集成时,通过自动化证书管理增强安全性,并为所有通信启用 mTLS。这确保了安全策略在整个集群中的一致应用。然而,附加的边车代理层引入了延迟,并给监控和维护带来了挑战。
对于已经在使用或计划使用服务网格的组织,将其用于密钥管理,特别是证书和令牌,变得非常具有吸引力。这种集成简化了密钥管理,确保了安全传输、适当的范围控制和定期轮换。然而,必须认识到,虽然服务网格在管理证书和令牌方面表现出色,但它们并不是适用于所有密钥类型的“一刀切”解决方案。
总之,当服务网格存在于 Kubernetes 设置中时,利用它来管理证书和令牌可以简化操作并增强安全性。然而,它不应被视为全面的密钥管理的完全替代方案。
总结来说,服务网格增强了 Kubernetes 生态系统的安全性,特别是在证书和令牌形式的密钥分发方面。虽然其好处多多,但和任何技术一样,彻底理解并认真实施是实现其全部潜力的关键。
代理系统在密钥管理中的作用
在 IT 安全的广阔领域,特别是在讨论密钥管理时,代理系统一词作为一个重要角色出现。作为中介,这些代理就像交通警察,确保应用程序在验证其身份后获得所需内容。
以下内容可以帮助您理解代理机制如何工作:
-
请求:需要密钥的应用程序向代理发送请求。
-
验证:代理验证请求,通常会验证发送方的身份和授权。
-
获取和传输:一旦验证通过,代理从存储中检索密钥,并安全地将其发送到应用程序。
-
审计和日志:所有事务,无论是请求还是获取,都会被适当记录以供审计。这种设计确保了应用程序避免直接与不同的密钥存储交互;它们只需要与代理进行接口对接。
参考以下示例。假设服务 foo 需要连接到特定的数据库。与其直接获取数据库凭证,发生的情况如下:
-
foo发送请求:我需要凭证用于 DB1。 -
代理检查
foo是否拥有DB1的正确权限 -
确认后,代理获取并交付凭证
在整个过程中,foo 不关心确切的密钥存储位置和检索方法。它通过代理安全地获取必要的数据库凭证,代理验证权限并检索信息,确保了安全的访问过程。
代理机制与无密钥代理
由于术语相似,很容易将两者混淆,但它们的功能是不同的。无密钥代理更进一步;它们代表应用程序建立连接,而不会将密钥暴露给应用程序。
本质上,它们的区别如下:
-
代理机制:它们在验证后将密钥传递给应用程序
-
无密钥代理:它们使用密钥促进直接连接,同时保持密钥的隐藏
思考泡泡 – 代理和服务网格
此时,值得与服务网格进行类比。服务网格通过代理来控制和管理微服务架构中服务之间的流量。如果你仔细想想,代理不就是像一个代理吗?的确,服务网格的代理确保服务之间的安全通信,可能还会管理证书、令牌以及有时其他密钥。然而,其主要关注点并不是密钥管理,而是促进服务间的安全通信。
为什么代理仍然重要?
尽管无密钥代理和服务网格正在取得进展,传统的代理系统仍然不可或缺。它们灵活,适用于各种应用程序,提供密钥分发的集中控制,允许细粒度的访问,并且常常弥合遗留系统的差距。它们的角色不仅仅是检索,还包括密钥治理和生命周期管理。
探索将外部密钥存储与 Kubernetes 集成的领域,本节阐明了对于安全高效的密钥管理至关重要的机制和模式。我们从 Kubernetes 扩展和 API 机制开始,展示了如何将这些工具无缝地融入到您的密钥管理策略中,接着深入研究了 Pod 生命周期和操作机制,确保在 Pod 的生命周期内,密钥始终得到安全管理。接下来的内容讨论了 Kubernetes 的专门模式,重点介绍了 SealedSecrets 这一增强安全性的范式,并深入探讨了服务网格集成,展示了其在安全密钥分发和服务间通信中的强大能力。最后,讨论了密钥管理中的代理系统,强调了它们在应用程序与密钥存储之间创建安全中介层的作用,并确保了一个解耦且灵活的管理系统。总的来说,这些子部分共同构成了一份全面的指南,帮助团队在 Kubernetes 中安全高效地管理密钥,同时应对外部密钥集成的复杂性。
安全隐患与最佳实践
随着 Kubernetes 的普及,将其与外部密钥存储集成具有一些特定的优势,如专用加密和审计能力。然而,这种方式也带来了一系列挑战和安全隐患。
以下是这些最佳实践的列表:
-
对外部系统的依赖:依赖外部密钥存储意味着引入了一个额外的复杂性和依赖层。外部存储的任何停机或安全漏洞都可能直接影响在 Kubernetes 集群中运行的应用程序。
-
数据传输暴露:如果外部存储到 Kubernetes 的密钥传输没有得到妥善加密保护,例如缺乏端到端加密,传输过程中可能会暴露密钥。
-
通过代理或中介提升权限:获取密钥的代理或边车可能成为潜在的攻击入口。恶意攻击者若能访问其中一个代理,可能会从外部存储中窃取密钥。
-
配置和访问策略:外部密钥存储中的配置错误或过于宽松的访问策略可能会无意间暴露敏感密钥。
-
版本控制和密钥轮换的挑战:如果管理不当,Kubernetes 与外部存储之间的密钥版本同步可能会遇到挑战,导致潜在的版本不匹配或使用过时的密钥。
将 Kubernetes 与外部密钥存储集成可能具有挑战性。确保 Kubernetes 中密钥的安全性和完整性需要一系列强有力的实践。在这里,我们概述了需要考虑的关键最佳实践:
-
保护数据传输:在从外部存储将密钥传输到 Kubernetes 时,始终使用加密通道(如 TLS)。确保通信的双方相互认证。
-
限制并监控访问:在外部密钥存储中实现细粒度的访问控制。只允许特定的实体(如某些代理或 sidecar)获取密钥,并监控它们的活动。
-
密钥轮换和同步:定期轮换外部存储中的密钥,并确保有机制高效地将这些变化同步到 Kubernetes 中。这可以避免过时的密钥和潜在的安全漏洞。
-
加强代理或中介系统的安全性:如果使用代理、sidecar 或任何其他中介系统来获取密钥,确保它们是安全的、可监控的,并且以最小权限运行。
-
备份外部存储:定期备份外部密钥存储。在遭遇安全事件或故障时,这可以确保密钥恢复并快速使服务恢复上线。
-
审计和异常检测:利用外部密钥存储的审计功能。监控任何异常的访问模式或可能表明存在安全漏洞或配置错误的异常情况。
通过认识到这些影响并遵循最佳实践,Kubernetes 管理员可以有效且安全地利用外部密钥存储的优势。
实践与理论的平衡
在将 Kubernetes 与外部密钥存储集成时,找到正确的平衡至关重要。这种平衡不仅仅涉及技术层面,还包括可扩展性、审计能力、互操作性,甚至成本影响。目标是创建一个强大、可扩展且安全的环境,而不会妥协可用性或成本效益。
安全始终是最重要的。你必须确保密钥在传输过程中或静态存储时不被暴露。外部依赖如果没有得到妥善管理,可能会引入漏洞,单一的安全事件可能引发多米诺效应,危及多个系统。始终确保加密通信,并选择具有强大安全防护措施的密钥存储。
可用性和用户体验通常被视为安全的另一面。如果系统过于繁琐,可能会导致绕过或简化操作,从而抵消安全效益。此外,在评估如何实际应用这些考虑因素时,理解各种机制的最佳使用方式至关重要。基于 Pod 生命周期的方法,如 init 容器和 sidecar,自然与直接获取方法对接,而无需转化为 Kubernetes 原生密钥。相比之下,Kubernetes 扩展和 API 机制,虽然功能多样,但本质上更适合转化为 Kubernetes 资源。
细粒度访问对现代应用至关重要。并非每个应用或服务都需要访问所有秘密。正确实施的细粒度访问可以在某个服务被攻破时最小化风险。遗留系统并不能总是被忽视或立即替换。因此,任何解决方案都必须考虑如何与可能未按照现代安全实践设计的旧系统进行集成或共存。处理外部依赖是一项微妙的任务。过度依赖外部系统可能会给基础设施带来脆弱性。评估这些系统的可靠性并预设应急方案至关重要。
了解故障和恢复模型非常重要,因为故障发生的不是“如果”,而是“何时”;因此,必须制定全面的备份和恢复策略,以便在数据损坏或丢失时恢复秘密。处理潜在的秘密泄露影响范围至关重要。了解泄露的影响:如果一个节点或整个集群被攻破会发生什么?通过尽可能地分隔和隔离秘密,最大限度地减少潜在的损害。可审计性和监控确保秘密访问的可追溯性。全面的日志和实时警报有助于快速识别和纠正可疑活动。
秘密存储的可扩展性必须与组织的增长相匹配。随着集群和部署的增长,秘密存储应该能够无缝处理增加的流量。生命周期管理涉及在整个生命周期中管理秘密——包括创建、更新、轮换和删除——并将这些过程无缝集成到 CI/CD 流水线中。
在我们的多云时代,互操作性是不可谈判的。解决方案必须支持多种环境,确保跨不同云提供商的兼容性。成本不仅仅是直接的财务影响。需要考虑运营成本、潜在的泄露相关成本以及延迟相关的成本,确保解决方案的整体成本效益。地理冗余对于全球运营至关重要,确保从全球任何位置都能实现低延迟和高可用性。过渡的便捷性确保了未来的灵活性。通过优先考虑采用开放标准设计的解决方案,避免被锁定在某个特定的解决方案中。
最后,遵守特定行业的监管和合规要求,确保秘密存储符合 ISO 27001、PCI-DSS 和 HIPAA 等标准。
摘要
探索 Kubernetes 与外部秘密存储的集成,揭示了安全高效的秘密管理的基本方法和模式。我们深入研究了关键机制,包括 Kubernetes 扩展、Pod 生命周期操作,以及像 Secret Store CSI 驱动程序这样的创新工具,展示了 Kubernetes 的适应性和对安全性的承诺。
服务网格和代理机制在平衡强大安全性与应用灵活性方面发挥着关键作用,它们作为中介,负责密钥的分发,并将应用与直接访问密钥解耦。实现这一平衡需要关注细粒度的访问控制、遗留系统以及密钥泄露的潜在影响,同时还要满足可扩展性、监控和合规性等需求。
总之,将 Kubernetes 与外部密钥存储系统集成的这一复杂旅程,旨在创建一个强韧且安全的操作环境,确保为在 Kubernetes 生态系统中航行的组织提供可扩展且可持续的未来。
在我们探索将 Kubernetes 与外部密钥存储系统集成的基础上,下一章将呈现一个关于在生产环境中管理密钥生命周期的端到端故事。此章节将涵盖实际应用、挑战与解决方案,展示一种全面的方法,以确保在实际场景中安全高效地管理密钥。
第十三章:案例研究与实际应用示例
在本章中,我们将展示 Kubernetes Secrets 在生产环境中的实际应用案例。本章将涵盖 Kubernetes 生产环境中 Secrets 管理的案例研究,并分享从实际部署中获得的经验教训。此外,您还将学习如何在 CI/CD 流水线中管理 Secrets,并将 Secrets 管理集成到 CI/CD 过程中。本章还将讲解如何使用 Kubernetes 工具管理流水线中的 Secrets,并介绍确保 CI/CD Secrets 管理安全的最佳实践。我们将扩展以下主题:
-
Kubernetes Secrets 在生产环境中的实际应用示例
-
从 CI/CD 角度的密钥管理
-
从实际部署中学到的经验
-
在 Kubernetes 生产系统中管理 Secrets 的生命周期
技术要求
为了将理论与实践结合起来,我们利用了一系列常用工具和平台,这些工具和平台通常用于与 Google Cloud API 和 Kubernetes 交互:
-
gcloud CLI:这是用于创建和管理 Google Cloud 资源的一套工具(
cloud.google.com/sdk/gcloud#download_and_install_the) -
kubectl:这是用于通过 Kubernetes API 与 Kubernetes 集群通信的命令行工具(
kubernetes.io/docs/reference/kubectl/) -
minikube:这是一个本地 Kubernetes 发行版,用于 Kubernetes 学习和开发。要在系统上安装 minikube,可以按照官方文档中的指示操作(
minikube.sigs.k8s.io/docs/start/) -
sealed-secrets在 Kubernetes 上的安装(github.com/bitnami-labs/sealed-secrets#kubeseal) -
argocd:这是一个命令行工具,用于简化与 Argo CD 的交互(
argo-cd.readthedocs.io/en/stable/cli_installation/)
Kubernetes Secrets 在生产环境中的实际应用示例
到目前为止,我们已经看到了一些不同的 Kubernetes Secrets 管理方法。接下来,我们将查看一些在生产环境中如何管理 Secrets 的示例。我们将比较一些不同的方法,识别它们的差异,并分析它们的优缺点。
生产环境中密钥管理的特性
在生产环境中管理 Kubernetes Secrets 时,无论采取何种方式,都需要满足某些特性。这些特性使我们的生产部署更加可靠和安全。以下是这些特性:
-
高可用性
-
灾难恢复
-
加密
-
审计
让我们深入探讨每一个方法。
高可用性
Kubernetes 高可用;我们在 第一章《理解 Kubernetes Secrets 管理》中看到过这一点。存储在 Kubernetes 中的一个 Secret 会存储在 etcd 中,而 etcd 节点是 Kubernetes 集群的一部分。如果一个 etcd 节点出现故障,Secrets 仍然会保留在其他 etcd 节点上。这确保了 Kubernetes 会继续运行并且所有 Secrets 都能正常工作。最终,一旦丢失的 etcd 节点恢复,它将与其他节点一起恢复操作。高可用性确保了 Kubernetes 在节点丢失的情况下仍能保持稳定运行。除了节点的普通不可用外,高可用性还应解决数据中心宕机的风险。Kubernetes 集群的所有节点不应托管在同一个数据中心;相反,集群的节点应该分布在不同的数据中心中。如果失去对某个数据中心的连接,或者该数据中心出现问题,托管在其他数据中心的节点将能够继续工作。然而,我们可能会遇到更极端的情况,某些情况下,整个区域会变得无法连接,而不仅仅是某个节点或数据中心不可用。在这种情况下,能够在另一个区域执行灾难恢复变得至关重要。
灾难恢复
在 第六章《灾难恢复与备份》中,我们广泛讨论了灾难恢复。对于 Secrets,制定灾难恢复计划至关重要。从 Secrets 管理的角度来看,灾难恢复场景将根据管理 Secrets 的决策有所不同。Secrets 可以通过 Kubernetes etcd 或外部 Secret 存储进行管理。
使用 etcd 的 Secret 存储
在 Kubernetes 集群中实施灾难恢复的方法将极大地影响通过 etcd 管理的 Secrets 的灾难恢复。
我们有以下几个选项:
-
在另一个区域按需创建的集群
-
在另一个区域的备用 Kubernetes 集群
-
多个区域的主动-主动 Kubernetes 集群
可以通过内部工具实现的选项,例如备份 etcd,或者使用如 Velero 之类的工具,可以在另一个区域按需创建集群。以云服务提供商 GCP 为例,您可以通过点击按钮复制 Kubernetes 集群。
在维护备用集群或主动-主动集群时,许多选择严重依赖于您在 Kubernetes 上执行部署的方式。CI/CD 非常重要。例如,对于您的备用集群能够正常运行,您的 CI/CD 作业可能需要将 Secrets 推送到两个集群。您还可以采用 GitOps 模型。在 GitOps 模型中,您可以利用诸如 Argo CD 的工具。在这些情况下,备用 Kubernetes 集群可以通过从 Git 存储库拉取更改来更新。这样,Secrets 就可以应用到可用集群上,而不需要直接将秘密更改推送到集群中。
当涉及到 Kubernetes 和灾难恢复时,有各种选项,正如我们在第六章,灾难恢复和备份 中所看到的那样。
外部秘密存储
通过外部秘密存储,灾难恢复由秘密存储本身及其功能管理。我们检查的所有基于云的 Secrets 存储选项都支持跨区域可用性或跨区域复制选项。Azure Vault 和 Google Cloud Secret Manager 提供跨区域可用性,AWS Secrets Manager 提供跨区域复制。在 HashiCorp Vault Enterprise 中,还有跨区域复制选项。
加密
加密至关重要。我们深入研究了第三章,以 Kubernetes 本地方式加密 Secrets。在每个 Kubernetes 安装中,遵循磁盘存储以及 etcd Secrets 加密的加密是至关重要的。
审计
我们在第五章,安全、审计和合规性 中看到了为什么审计很重要以及为什么首先需要它。在我们所使用的每个云提供商中,审计选项始终存在。HashiCorp Vault 和 CyberArk Conjur 也提供了审计功能。
我们重点关注和概述了在生产环境中需要放置的 Secrets 管理的特性。接下来,我们将关注 Secrets 管理与 CI/CD 的结合以及需要避免的风险。
从 CI/CD 视角看 Secrets 管理
在 CI/CD 流水线中管理 Secrets 是一个有趣的概念。在之前的章节中,我们主要关注了基于 Git 的 Secrets 管理概念和基于 secret-storage 的 Secrets 管理。我们没有提到手动持久化 Secrets 到 Kubernetes。其中有许多原因:
-
您会失去对 Kubernetes 部署需求的跟踪
-
依赖关系不可见
-
没有清晰的应用描述
-
与基础设施即代码不兼容
接下来,我们将重点关注在我们的 CI/CD 流水线上与 Secrets 交互。
将 Secrets 管理集成到您的 CI/CD 流程中
根据管理 Secrets 所采用的方法,您的 CI/CD 策略将有显著差异。
基于 Git 的 Secrets 管理
通过基于 Git 的方法管理机密,CI/CD 应该能够与涉及的组件进行交互。
根据加密机制的不同,您需要在 CI/CD 账户中配置凭证,使其能够与加密凭证的 KMS 系统进行交互,或配置一个可以解密机密的 Kubernetes 服务账户。
让我们来看看封印机密的案例,这个概念我们在 第十二章,《与机密存储集成》中了解过,其中需要创建一个细粒度的 Kubernetes 角色和 Kubernetes 服务账户。原因在于封印操作发生在集群内部。封印后的机密可以存储在 Git 中。要检索实际的值,需要通过集群进行解密。封印操作也可以离线进行;在这种情况下,需要更多步骤来确保加密密钥的安全处理。
外部机密存储和 Workload Identity
到目前为止,我们已经使用了 Microsoft Azure Key Vault、Google Cloud Secret Manager 和 AWS Secrets Manager。它们都支持 Workload Identity 与 GitHub Actions 的集成。
在 CI/CD 流水线中与云组件交互的传统方式是将云提供商的凭证附加到 CI/CD 作业中。这种做法增加了安全风险。凭证,如服务账户或密钥机密,都是静态凭证;如果被泄露且没有适当的日志记录或审计系统,这些凭证可能会被长期使用,导致潜在的安全漏洞。
Workload Identity 是一种更安全的解决方案。通过 Workload Identity,我们可以为 CI/CD 作业分配精细化的权限,以便访问云组件。并不是所有的 CI/CD 提供商都支持 Workload Identity;然而,Workload Identity 方法的采用正在逐步增加,并预计将成为标准。
通过使用 Workload Identity,CI/CD 作业可以获得临时凭证与云提供商的机密存储进行交互。既然我们已经对 Workload Identity 有了初步了解,接下来我们将通过 GCP 实际演示。
GitHub Actions 和 GCP Workload Identity 集成
CI/CD 作业与云组件交互时的一个问题是权限问题。传统上,这个问题是通过将凭证上传到 CI/CD 作业变量来解决的,但这种解决方案带来了很多风险,我们将在本章后面讨论这些风险。
GitHub Actions 支持 OpenID Connect (OIDC) 。通过 OIDC,可以使用短期令牌在云提供商和 GitHub 之间进行身份验证。这样,我们就避免了在 GitHub 中存储长期有效的云机密。
在 第十章,《探索 GCP 上的云机密存储》中,我们将 GKE 集群与 GCP 的 Secret Manager 集成,因此我们已经对 GCP 这一平台有所了解。设想一下我们的 CI/CD 作业需要与已集成 GKE 的 Secret Manager 进行交互的场景。
如果没有 OIDC 的支持,我们将不得不将 GCP 服务帐户密钥存储在 CI/CD 作业中。得益于 OIDC,我们可以通过工作负载身份联合设置 GitHub 与 GCP 之间的身份验证。
首先,我们需要配置身份池:
$ gcloud iam workload-identity-pools create "ga-ksm-pool" --project="${GCP_PROJECT_ID}" --location="global" --display-name="GitHub actions Pool"
然后,我们将 GitHub 添加为身份提供者:
$ gcloud iam workload-identity-pools providers create-oidc "github" \
--project="${GCP_PROJECT_ID}" \
--location="global" \
--workload-identity-pool="ga-ksm-pool" \
--display-name="Github provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"
身份池已配置,GitHub 是其中一个身份提供者。接下来的步骤是将 GCP 上的服务帐户与 GitHub 仓库绑定:
$ gcloud iam service-accounts create github-service-account --project="${GCP_PROJECT_ID}"
$ project_number=$(gcloud projects list --filter="$(gcloud config get-value project)" --format="value(PROJECT_NUMBER)")
$ gcloud secrets add-iam-policy-binding ksm-secret --member="serviceAccount:github-service-account@${GCP_PROJECT_ID}.iam.gserviceaccount.com" --role=roles/secretmanager.viewer
$ gcloud secrets add-iam-policy-binding ksm-secret --member="serviceAccount:github-service-account@${GCP_PROJECT_ID}.iam.gserviceaccount.com" --role=roles/secretmanager.secretAccessor
$ gcloud iam service-accounts add-iam-policy-binding "github-service-account@${GCP_PROJECT_ID}.iam.gserviceaccount.com" \
--project="${GCP_PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/${project_number}/locations/global/workloadIdentityPools/ga-ksm-pool/attribute.repository/${github_organisation_or_username}/${github_repositoryname}"
通过此配置,我们允许来自工作负载身份提供者的身份验证来模拟所需的服务帐户。此外,注意当我们指定工作负载身份池为成员时,我们也指定了将托管需要 GCP 访问的操作的 GitHub 仓库。
我们已准备好继续进行 GitHub 作业配置:
name: Read from Secret Manager
on:
push:
branches:
- 'main'
jobs:
run:
name: 'get secret'
permissions:
id-token: write
contents: read
runs-on: 'ubuntu-latest'
steps:
- id: 'auth'
uses: 'google-github-actions/auth@v1'
with:
workload_identity_provider: 'projects/{project-id}/locations/global/workloadIdentityPools/ga-ksm-pool /providers/github'
service_account: 'github-service-account@${GCP_PROJECT_ID}.iam.gserviceaccount.com'
- id: fetch
run: |-
curl -H 'Bearer: ${{ steps.auth.outputs.access_token }}' https://secretmanager.googleapis.com/v1/projects/project-name/secrets/secret-name/versions/latest:access
在 GitHub Actions 控制台上,我们应该能够看到作业成功创建令牌并对创建的工作负载身份实例进行身份验证,同时从 Secret Manager 获取密钥。显然,这种身份验证方式也可以应用于我们与 GCP 交互时。
请注意,前述步骤故意打印了密钥,因为它们作为 GitHub Actions 与 GCP 工作负载身份集成的示例。你不应该在 CI/CD 控制台上打印任何检索到的密钥,如本章稍后提到的。
简而言之,通过利用工作负载身份,我们可以避免在 CI/CD 作业配置中存储长期有效的凭证。此外,我们与云提供商之间建立了无缝的集成,使得与云提供商组件(如 GCP 上的 Secret Manager)的交互更加简便。
Vault 作为外部秘密存储
正如我们在 Vault 与 Kubernetes 集成中所看到的,交互 Vault 需要一个令牌。当我们使用长期有效的令牌时,可能会面临泄露的风险。为此,我们可以使用 GitHub OIDC 令牌来替代直接使用 Vault 令牌,采用 JWT 方法。
每个 GitHub Action 都会收到一个自动生成的 OIDC 令牌。我们可以使用 GitHub 的 OIDC 提供者来配置 GitHub Actions 工作流与 Vault 之间的信任。
一个类似的概念是 Kubernetes 身份验证。我们使用来自 Kubernetes 服务帐户的 JWT 来检索 Vault 中的密钥。由于 Vault 已经与我们的 Kubernetes 集群建立了信任,它可以验证密钥并返回凭证。
与 GitHub OIDC 提供者和 HashiCorp 集成时也发生了类似的过程。
在 Kubernetes 上执行 CI/CD 管道
另一种安全地将 CI/CD 管道与 Kubernetes 秘密存储集成的方法是将管道运行在 Kubernetes 内部。CI/CD 作业运行在 Kubernetes 内部,因此凭证和其他 Kubernetes 组件不会暴露到我们公司的外部。
许多主要的云服务提供商,如 GitHub Actions 和 GitLab,提供了在 GitHub 和 GitLab 上管理 CI/CD 管道编排的选项,但将 CI/CD 作业执行在 Kubernetes 内。采用这种方法有许多好处。
通过在本地运行 CI/CD 作业,你使得 CI/CD 作业能够与仅存在于本地的资源进行交互。例如,假设一个 HashiCorp Vault 安装在一个私有网络中,这个网络是不可公开访问的。为了将 Vault 实例与外部 CI/CD 提供商集成,我们必须使 Vault 实例可以公开访问,这就增加了我们的安全顾虑。
通过在本地运行管道,情况就不同了。在 Kubernetes 内运行 CI/CD 管道可以增强我们 CI/CD 管道的安全性。Tekton 是一个非常流行的开源框架,它使我们能够在 Kubernetes 环境中创建 CI/CD 系统。
接下来,我们来了解一种持续交付的方法,那就是通过 GitOps 模型。让我们通过使用 Argo CD 运行一个示例来看看 GitOps 模型是如何工作的。
GitOps
GitOps 是一套用于通过 Git 集中式方法管理基础设施和应用配置的实践。Argo CD 遵循 GitOps 模型。它监控我们指定的 Git 仓库,并确保应用程序处于所需的状态。Argo CD 是一个 Kubernetes 控制器,监控运行中的基础设施,并将其与 Git 仓库中指定的基础设施进行比较。
我们可以通过做一个简单的 Argo CD 安装来理解这个模型:
$ kubectl create namespace argocd
$ kubectl config set-context --current --namespace=argocd
$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.5.8/manifests/install.yaml
我们现在可以进行端口转发,以便与 Argo CD 交互,而无需通过入口暴露服务:
$ kubectl port-forward svc/argocd-server -n argocd 8080:443
在另一个会话中,我们可以检索默认的管理员自动生成的密码,以便登录到 argocd:
$ argocd admin initial-password -n argocd
$ argocd login 127.0.0.1:8080 --username admin --password ***-** --insecure
我们现在可以创建一个应用程序;我们将使用 Argo CD 仓库中的示例(github.com/argoproj/argocd-example-apps):
$ argocd app create guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --dest-server https://kubernetes.default.svc --dest-namespace default
一个应用程序现在基于另一个 Git 仓库中的部署文件运行。
让我们来看看 GitOps 及其与我们到目前为止看到的 Secrets 管理方法的兼容性:
-
Sealed Secrets 可以得到支持,而无需额外的努力,因为 Sealed Secrets 控制器能够应用任何通过 Git 分发的新 Secrets。
-
由外部秘密存储驱动的解决方案不受影响,因为秘密信息存储在另一个组件中。
-
Helm Secrets 支持取决于用于 GitOps 的工具。Argo CD 可以支持 Helm Secrets;然而,它需要进行修改以便支持通过 Helm charts 分发的 Secrets 的加密和解密。
现在我们已经对 Secrets 管理和 CI/CD 有了概览,让我们继续讨论将 CI/CD 与 Secrets 集成所带来的风险。
在 CI/CD 管道中使用 Secrets 时需要避免的风险
当涉及到秘密时,CI/CD 流水线可能面临各种风险。配置错误的 CI/CD 流水线很容易导致泄露敏感信息、运行特权过高的流水线和供应链攻击等问题。
在流水线中泄露秘密
在流水线中泄露秘密比你想象的要容易得多。默认情况下,CI/CD 流水线会与其他配置变量区分对待秘密信息。例如,GitHub Actions 工作流。如果我们尝试在作业中打印一个秘密,秘密会被掩码,因此信息不会被泄露。这还不够。通过更改流水线的配置,我们可以将秘密值持久化到文件中。这样就可以打印文件并检索秘密信息。更糟糕的是,CI/CD 流水线会保留作业历史记录和日志。在某些情况下,历史记录无法被删除,或者会在一段时间后删除。
另一种泄露秘密的方式是将其作为 CI/CD 流水线生成的工件的一部分。在这种情况下,秘密可以通过 CI/CD 用户界面下载。
这些配置错误的流水线示例可能导致安全事件。秘密信息已经泄露,因此需要进行轮换。
生产秘密
另一个风险是将生产工作负载中使用的相同秘密与 CI/CD 流水线一起使用。生产环境中的秘密用于执行与 CI/CD 环境中的操作不同的操作。在 CI/CD 环境中使用生产秘密可能导致代码以比所需更高的权限运行,如果 CI/CD 配置错误,其使用可能会影响实际的生产系统。在秘密泄露的情况下,使用专门的秘密进行 CI/CD 作业的风险影响会更大。
恶意的贡献
CI/CD 可能成为攻击者窃取秘密的目标。触发流水线的仓库上的拉取请求为拉取请求作者提供了多种选项来获取秘密值。在这些情况下,保护与敏感信息交互的 CI/CD 作业至关重要。应该保护分支,并应将某些流水线进行隔离,以便实现细粒度的权限控制,并防止那些可能通过 CI/CD 作业试图检索秘密信息的人员访问。
在开源项目中,贡献可能旨在窃取秘密或成为供应链攻击尝试的一部分。我们以 GitHub Actions 为例,其中来自 Fork 的工作流无法访问秘密。此外,为了防止拉取请求中的滥用行为,GitHub Actions 提供了从公共 Fork 批准工作流运行的选项(docs.github.com/en/actions/managing-workflow-runs/approving-workflow-runs-from-public-forks)。
不受信的软件
一个管道的安全性取决于用于实现它的软件。在互联网上,有各种各样的 CI/CD 软件工具,从库到 Docker 容器不等。这些软件组件可能是过时的,从而暴露管道于安全漏洞,或者其中一些可能已被攻击者攻破,目标是供应链攻击。
以一个 Jenkins 插件或 GitHub 工作流操作为例,它从管道中读取 Secrets 并将其发送到外部位置。任何不可信或甚至已被攻破的工具也可能发生类似情况。
只能使用来自可信源的软件,并且应验证其真实性,例如使用基于哈希的验证。同时,管道中使用的软件应为最新版本,并包含必要的安全补丁。
拥有额外权限的管道
CI/CD 对于每个需要构建、测试和发布软件的公司至关重要。由于其关键作用,它通常会与 Kubernetes 安装共享的 Secrets 进行交互。超出 CI/CD 作业范围的 Secrets 权限,在泄漏或配置错误的情况下,可能会导致严重事件的发生。
以一个用于测试的 CI/CD 作业与 Kubernetes 集群使用的外部密钥存储进行交互为例。假设该作业从密钥存储中删除 Secrets,目标是一个暂存环境。然而,分配给管道的权限足够广泛,允许它删除生产环境中的 Secrets。管道配置错误可能导致数据丢失,甚至生产环境宕机。
现在我们已经概述了与 Secrets 和 CI/CD 集成相关的风险,接下来让我们确定最佳实践。
安全 CI/CD Secrets 管理的最佳实践
我们概述了 CI/CD 作业与 Kubernetes Secrets 的交互。为了确保安全,我们需要遵循一些最佳实践:
-
不要提交明文的 Secrets
-
如果使用令牌,应定期更换令牌并使其具有短生命周期
-
如果可能,使用基于 OICD 的集成,因为它更加安全
-
将管道迁移到本地 Kubernetes 环境有助于增强安全性
-
应遵循最小权限原则
-
使用专用的 Secrets 进行测试
-
不要在测试和生产环境中使用相同的 Secrets
到目前为止,我们主要关注了如何在生产环境中处理 Kubernetes Secrets,以及 Kubernetes Secrets 与 CI/CD 的集成。接下来,我们将聚焦于一个在组织内实施 Secrets 管理系统的案例研究。
从实际部署中吸取的教训
现在让我们看看如何与 Kubernetes 上的 Secrets 进行交互,以及在与 Kubernetes 上的 Secrets 交互时,我们应该做什么和不应该做什么。
案例研究 – 开发 Secrets 管理
随着越来越多的组织采用容器编排,以下案例研究展示了在组织内部建立强大机密管理系统的过程。
Square 的 Keywhiz 秘密管理系统
Keywhiz 是 Square 开发的一个系统,用于存储重要的数字密钥和密码,例如用于保护网站的密钥,确保它们安全并集中存放。这个系统非常有用,因为组织之前缺乏安全的方法来存储这些机密信息。Keywhiz 确保只有 Square 的正确部分在需要时才能访问这些机密,并通过安全的连接进行访问。
深入了解 Keywhiz 的机密管理系统
让我们深入了解 Keywhiz,看看 Square 是如何从头到尾构建这个系统的。
商业正当性
Square 在构建 Keywhiz 时,设想重要的机密信息应该难以访问。它们不应该出现在任何人都能看到的地方,例如开发人员的计算机或互联网上。只有 Square 中需要这些机密的特定部分才能访问它们。对于使用安全连接保护数据的服务尤为重要。Square 的服务可以直接访问这些机密,而无需使用许多步骤或其他服务。即便是特殊情况,例如需要使用额外安全硬件时,Keywhiz 也有处理方法。
Keywhiz 还注重避免将过多的机密信息散布在各处,因为这可能带来风险。通过将所有机密信息集中在一个系统中,跟踪它们并确保其安全变得更加容易。此外,这个系统还允许 Square 检查其数字密钥和密码的健康状况,例如检查它们是否足够强大或是否需要尽快更换。
对 Square 来说,了解机密信息何时以及如何被使用是非常重要的。因此,Keywhiz 会详细记录每次机密被访问的情况。如果仅仅将机密信息存储在服务器上作为文件,是无法做到这一点的。虽然有一些工具可以帮助监控这些信息,但它们需要额外的设置工作。
Keywhiz 是为 Square 的许多不同服务而设计的。它已经被设置为处理各种需求,从保护网站到管理数据库。
该系统的可靠性至关重要。它必须始终有效,因为 Square 的服务依赖于这些机密信息来运行。
该系统还设计得易于使用。如果它不够易用,人们可能会尝试走捷径,这可能会降低安全性。
最后,Keywhiz 将密钥更换与软件更新的过程分开。这意味着 Square 可以在不更改整个系统的情况下更新其安全性,从而使系统更安全且更灵活。
分类并集中存储机密信息
Square 的 Keywhiz 系统非常重视其数字机密的安全性。它首先将这些机密组织成明确的类别。这不仅仅是为了保持整洁——更重要的是,知道 Square 系统中的哪个部分需要哪些机密来操作。从此,所有机密都保存在一个中心位置。这意味着它们不会分散在不同的位置,以至于可能被遗忘,或者更糟糕的是,落入不当之手。
但是,Keywhiz 的亮点在于它如何锁定这些机密。在机密被保存在 Square 的数据库中之前,它会被加密层包裹——就像把一封信放入一个只有特定人员才能打开的保险箱。这涉及一种特定类型的加密,称为 AES-GCM (en.wikipedia.org/wiki/Galois/Counter_Mode)。它被 国家标准与技术研究所 (NIST)在其《特别出版物 800-38D》中推荐为块密码(en.wikipedia.org/wiki/Block_cipher)操作模式的首选方法,重点关注 Galois/Counter Mode (GCM)和 Galois 消息认证码 (GMAC)。每个机密都有自己独特的加密密钥,该密钥通过一种叫做 HKDF (en.wikipedia.org/wiki/HKDF)的方法创建,HKDF 是一种基于 HMAC 消息认证码的简单 密钥派生函数(KDF),它确保即使发现了一个密钥,其他密钥依然安全。Square 使用硬件安全模块来存储派生密钥。
现在,当涉及到传递这些机密时,Keywhiz 确保只有 Square 系统中的正确部分,即它们称之为 客户端,可以访问这些机密。访问控制的结构围绕三个关键元素:客户端、组和机密。“客户端”是指任何能够访问机密的证书。这些客户端可以属于多个组,组是客户端的集合。为了使客户端能够访问特定的机密,该机密必须与客户端所属的至少一个组相关联。通常,Keywhiz 通过创建三种主要类型的组来组织这一点:每个特定服务器上的每个服务一个组,每个独立服务一个组,以及包含所有客户端的通用组。
之前,人们尝试过其他方式来保护机密的安全,比如将其混入程序代码中,或手动添加到服务器中。但这些方式很有风险——机密可能不小心与外界共享,或者丢失。即使试图使用管理服务器设置的系统来保护机密也并不理想,因为这些系统的目的是在公司内部共享信息,这显然不是你想要的机密保护方式。
PKI 作为认证的真理来源
公钥基础设施 (PKI) 是 Square 身份验证过程的核心。它就像一个验证系统,确保只有 Square 网络中的正确部分可以访问所需的秘密信息。为了建立这种信任,Square 使用 mTLS 和 X509 证书,后者是服务的数字身份验证凭证。Square 使用 certstrap 简化了这一任务,这是一个简单的证书管理工具。这个工具帮助 Square 创建自己的证书颁发机构,可以将其视为数字身份证明机构。通过 certstrap,Square 可以向其服务发放这些数字身份证,确保每个服务在其网络中得到识别和信任。
certstrap 使 Square 避免了传统工具(如 OpenSSL)的复杂性。它使 Square 能够创建一个信任链,其中每个服务的身份都得到了验证和保护。这种验证对 Square 至关重要,因为它保持了服务之间通信的安全,确保只有公司内部的授权实体可以访问秘密信息。
授权数据模型
在 Square 的 Keywhiz 系统中,授权—决定谁可以访问什么—是一个结构化的过程。以下是 Square 设置的方法:
-
客户端是 Square 系统的组成部分,例如需要访问秘密信息以正常工作的服务或应用程序。它们通过所谓的客户端证书来证明自己的身份,就像系统的身份证。
-
秘密信息是客户端执行任务所需的敏感数据,例如配置文件或密码。每个秘密信息都有一个唯一的名称,避免混淆,并且一旦设置,就无法更改。然而,如果需要更新,Square 可以保留一个秘密的多个版本。
-
组作为客户端和秘密信息的交汇点。可以将组视为标签或标记。Square 用这些组标签标记客户端和秘密信息。当客户端和秘密信息拥有相同的组标签时,客户端就可以看到并使用该秘密信息。这是 Square 组织系统中哪些部分可以访问哪些秘密信息的方式。
-
用户是管理 Square Keywhiz 的人员。是他们设置系统并决定哪些客户端和秘密信息属于哪些组标签。他们通过安全的方法登录到 Keywhiz,并在登录后需要一个来自 Keywhiz 的特殊代码,以保持工作安全地继续进行。
Square 的 Keywhiz 通过结构化的授权模型管理访问权限,该模型涵盖客户端、秘密信息、组和用户管理,确保权限的安全与有序。
秘密分发
Square 还维护一个名为 Keysync 的 Keywhiz 客户端实现。Keysync 是一个程序,它通过安全的方式连接到 Square 的 Keywhiz 服务器,并请求它需要的秘密信息,以保持 Square 服务的正常运行。它使用称为 mTLS 的技术进行安全通信,确保一切都安全和私密。
一旦 Keysync 获取了这些 Secrets,它会将其保存在服务器内存的安全区域,即tmpfs。这是一个临时空间,一旦服务器关闭,里面的内容不会保存。因此,如果发生停电或服务器需要重启,这些 Secrets 不会被遗留在可能被他人看到的地方。
Keysync 的巧妙之处在于它设计时就考虑到了应对突发问题。如果 Keywhiz 服务器出现问题,Keysync 仍然会保留它之前下载的 Secrets,因此 Square 的服务可以继续正常运行,直到进行完全的服务器重启,Keysync 才需要重新获取所有 Secrets。
为了处理这些 Secrets,Square 的管理员有一个管理员 CLI,允许他们直接在系统中输入命令,按需添加、删除或更改 Secrets。这是一种简便的方式,使他们能够保持系统的更新,并检查 Secrets 管理系统的健康状况,而不必通过复杂的界面进行操作。
Keysync,Square 的 Keywhiz 接口命令行工具,确保在不同场景下安全、机密地传递 Secrets。
从第三方视角来看,Keywhiz 的挑战和经验教训
考虑到 Keywhiz 已于 2023 年 9 月 18 日被弃用并停止维护,回顾它在服务期间所面临的挑战和所带来的经验教训,具有很大的启示意义。推荐转向 HashiCorp Vault,强调了需要一个强大且积极维护的 Secrets 管理解决方案。
Kubernetes 环境下的集中式管理很困难
Keywhiz 在 Kubernetes Secrets 管理中面临的核心挑战之一是,在像 Kubernetes 这样高度分布且动态的环境中,集中式管理本身的复杂性。Kubernetes 环境通常需要一种更加灵活和去中心化的 Secrets 管理方式。尽管 Keywhiz 提供了集中控制和强加密来保护 Secrets,但它可能没有针对 Kubernetes 工作负载的去中心化和短暂性质进行最佳配置。
例如,在 Kubernetes 中,必须有一个能够处理 Secrets 动态创建和删除的管理解决方案,这与 Kubernetes Pods 和 Services 的短暂特性相符。Kubernetes 中的 Secrets 管理还需要与 Kubernetes 的基于角色的访问控制(RBAC)紧密集成,并能够高效地跨多个集群和命名空间管理 Secrets。
虽然 Keywhiz 在集中式 Secrets 管理方面表现出色,提供强加密并安全地将 Secrets 分发到各种服务和平台,但将这些能力适配到 Kubernetes 的特性上可能会有挑战。Kubernetes 环境通常需要像 HashiCorp Vault 这样的工具,它提供了广泛的 Kubernetes 集成,包括动态 Secrets、与 Kubernetes 服务帐户的身份验证集成,以及为 Secrets 定义细粒度访问控制策略的能力。
一个秘密管理系统不仅是一个系统,而是一个完整的生态系统
Keywhiz 在组织生态系统中的作用不仅仅是作为一个独立的秘密管理工具。其有效性在很大程度上依赖于与公司现有工作流程、政策和组织文化的集成。这个场景说明,秘密管理系统的效果不仅仅取决于其技术能力,还取决于它如何与组织的整体运营环境对接并提供支持。
例如,考虑一个采用 Keywhiz 来管理敏感数据的医疗保健组织,如患者记录和内部系统的登录凭证。在这种环境下,尽管 Keywhiz 作为这些秘密的存储库,但其有效性依赖于与组织现有医疗信息系统的无缝集成。这种集成可能涉及与员工目录服务同步,以便根据角色和部门管理访问权限。它还需要与医疗合规标准对接,在这种情况下,Keywhiz 的审计跟踪和加密功能对于满足监管要求至关重要。通过这种方式,Keywhiz 成为组织整体数据安全框架的一个不可或缺的部分,受到组织内外多种因素的影响。
缺乏将审计作为故事的视角
Keywhiz 的一个显著局限性是它在审计方面的处理方式,特别是它如何处理秘密分发。Keysync 作为客户端工作,秘密被下载为 tmpfs 文件,然后由应用程序使用。然而,这种模式并没有本质上为每个操作提供详细的审计数据,具体来说,它缺乏对应用程序是否正在积极使用已下载秘密的可见性,因为这一互动发生在客户端,并且无法直接通过 Keywhiz 服务器观察到。
这可能导致审计过程中出现潜在的漏洞,例如以下几点:
-
确定访问秘密的具体用户或服务
-
记录每次访问的精确时间戳
-
理解访问的性质,例如是否读取或修改了秘密等
-
检测访问秘密的 IP 地址或机器
Keysync 过程无法满足与应用程序相关的秘密的实时需求,从而导致维护持续且全面的审计跟踪出现挑战。这一漏洞可能影响到全面记录组织内部秘密访问生命周期的能力,使得追踪所有与敏感数据的互动变得困难。
在 Kubernetes 生产集群中从头到尾管理秘密的生命周期
上一章涵盖了广泛的主题,但讨论的概念与在生产环境中管理秘密的实际例子之间存在明显的脱节。在本节中,我们将缩小焦点,深入探讨在 Kubernetes 生产集群中的秘密端到端管理,提供更具实践性、面向应用的视角。
在探索这种环境中秘密管理时,我们意识到这一过程不仅仅是安全存储。我们将关注点从单纯的存储库转向对整个系统生命周期中秘密使用的全面视角。秘密是运营流程的组成部分,嵌入到推动组织数字化运营的工作流中。
挑战在于有效管理整个秘密的生命周期,从创建到废弃,并严格强调精确性和安全性。这种全面的方法对于致力于高安全性和高运营标准的组织至关重要。有效管理秘密是了解它们的生成、分发、撤销和废弃在动态的 Kubernetes 生态系统中的过程。本节将引导你了解在维护安全高效的数字基础设施中,秘密管理所扮演的细致而重要的角色。
最终决定关于全面的秘密生命周期管理
在 Kubernetes 生产环境中管理秘密时,采用全面且系统的方法至关重要。秘密生命周期管理不仅仅是安全存储的一个方面,它涵盖了从最初的供应到最终的废弃和撤销的一系列关键过程。
配置涉及以安全和可控的方式创建或生成秘密,确保它们强大且唯一,并符合组织的安全政策。存储阶段需要一个强大且安全的存储库,例如 HashiCorp Vault 或云端秘密存储,确保秘密在静态时加密并保护免受未经授权的访问。分发是一个微妙的操作,秘密必须安全地传输到所需的服务或应用程序中,确保在传输过程中保持完整性和机密性。
废弃过程在秘密不再需要时发挥作用,必须有一个安全的流程将它们退役,确保它们不能被重新使用或被利用。最后,撤销是生命周期中的关键步骤,特别是在发生泄露或秘密完整性存疑的情况下。迅速有效的撤销机制确保立即切断访问,减轻潜在的损害。
采用全面的 Secrets 生命周期管理方法,确保 Secrets 不仅被安全存储,还能在整个生命周期中得到妥善管理。这种端到端的视角不仅是一种最佳实践,也是确保 Secrets 完整性以及整个生产系统完整性的组织需求。当我们在后续章节中深入探讨这些方面时,Kubernetes 中全面的 Secrets 管理方法的关键性将愈加明确,凸显其作为组织安全和运营环境中的关键组成部分的作用。
高 SLA 作为商业可持续性的关键
维持高 服务水平协议 (SLA) 对于 Kubernetes 环境中的 Secrets 管理至关重要,强调系统的可靠性和可用性作为商业可持续性的基础性方面。这在生产环境中尤为重要,因为停机或安全漏洞可能会对财务和运营造成重大影响。
为了实现这一高可靠性,企业需要实施强大的监控和警报系统。定期对 Secrets 管理系统进行压力测试也至关重要,以识别和解决潜在的漏洞,确保系统能够应对各种操作压力,并保持其 SLA 承诺。
在云 Secrets 存储的背景下,服务提供商通常会提供符合大多数用例要求的承诺 SLA。然而,对于自部署的 Secrets 管理系统,需要采用不同的方法来评估和确保 SLA,尤其是在密钥检索对系统基础设施和平台使用至关重要时。
确保高 SLA 的一种策略是使用如 HashiCorp Vault 等解决方案中的主动-主动复制模型。该模型通过多个活动系统之间的持续同步,增强了容错性和可用性。然而,这并不是唯一的方法。
替代方法,如使用安全的 Redis 配置来缓存 Secrets,可以提供临时可用性(例如,持续几个小时),并提高平台的可靠性。在这种情况下,Secrets 被加密后以密文形式存储在 Redis 中,Redis 充当临时缓存。这种方法不仅确保了 Secrets 的安全性,还提供了额外的可靠性层,确保在关键时期或主要系统故障时,Secrets 能够保持可用。
总体而言,维持高 SLA 的方法应根据 Kubernetes 环境的具体需求和架构量身定制。
紧急恢复 - 备份和恢复
在 Kubernetes 生产集群中,为密钥管理制定有效的紧急恢复计划至关重要。一个强有力的恢复计划是迅速恢复操作的关键,能够在数据丢失、系统故障或安全事件发生时最小化停机时间。这种前瞻性的做法确保密钥管理系统能够迅速从意外中断中恢复,从而保持生产环境的连续性。
定期备份在保护密钥和配置的完整性及可用性方面发挥着至关重要的作用。这些备份为可靠的恢复机制奠定了基础,确保在遭遇数据丢失、硬件故障或安全漏洞后,能够可靠地恢复敏感信息和配置。
欲了解更多详细信息和见解,请参考 第六章,灾难恢复 与备份。
不仅仅是存储密钥,而是配置密钥。
配置和存储密钥是管理 Kubernetes 环境中密钥的关键步骤,作为初始入口点,并为强制执行安全使用模型提供重要机会。通过控制密钥的来源并明确其目标资源,组织可以建立一个稳健的长期密钥管理框架。
在配置密钥时,确保透明性和可追溯性是至关重要的。强制执行清晰且标准化的密钥创建流程有助于追踪密钥的来源、预期用途和目标资源。这一做法有助于保持有序的密钥清单,简化日后管理和审计工作。
在 Kubernetes 中安全存储密钥时,采用最佳实践,如静态和传输加密、使用专用服务帐户和实施基于角色的访问控制至关重要。组织应充分利用 Kubernetes 的本地功能,如命名空间和网络策略,为密钥提供额外的隔离和保护层。
确保长远成功的密钥管理的一个关键方面是强制使用特定的密钥类型。Kubernetes 支持多种密钥类型,每种类型都针对特定的使用场景。通过强制使用这些类型,组织可以利用 Kubernetes 内建的验证机制,确保密钥符合预期结构,减少配置错误的风险。例如,强制使用 kubernetes.io/dockerconfigjson 类型来存储 Docker 注册表凭证,可以确保密钥内容符合预期格式,减少运行时错误的风险。
此外,利用特定类型的秘密有助于创建一个自描述且更易管理的环境。开发人员和管理员可以根据 Secrets 的类型轻松理解其目的和使用方式,从而提高整体清晰度并减少错误的可能性。这种做法还简化了审计和合规流程,因为在 Kubernetes 集群中追踪和报告不同类型 Secrets 的使用变得更加简单。
将这些实践融入到 Secrets 管理的供应和存储阶段,为安全高效的秘密处理奠定了坚实的基础。它确保 Secrets 的创建、存储和使用符合安全最佳实践,为安全、合规性和可管理性方面提供了显著的投资回报。通过执行明确的标准并利用 Kubernetes 的原生功能,组织可以创建一个强大而弹性的 Secrets 管理环境,能够安全高效地支持其应用程序。
Secrets 轮换
Secrets 的轮换在维持安全的 Kubernetes 环境中至关重要,主要因为静态的、长期有效的凭证带来了固有的安全隐患。一旦 Secrets 被泄露,它们可能成为恶意实体的入口,导致未经授权的访问和潜在的数据泄露。为了减少这种风险,必须实施 Secrets 的定期轮换,确保即使一个秘密被泄露,其生命周期也会受到限制,从而减少可能造成的损害。
然而,轮换 Secrets 的任务并非没有挑战,尤其是在处理分布在各个服务中的大量 Secrets 时。此时,自动化便提供了一个精简高效的解决方案。通过利用自动化系统,组织可以确保按时进行轮换,符合最佳实践和合规要求。这些系统通过定期更新 Secrets 和凭证,分发更新版本到相应的服务,并确保将过时的 Secrets 安全地淘汰。
需要注意的是,虽然自动化在 Secrets 轮换中起着重要作用,但可能存在一些特殊情况,某些 Secrets 由于技术限制或特定用例而被豁免于轮换。在这种情况下,必须保持透明性和清晰的文档记录,标明这些 Secrets 的特殊情况以确保可见性。尽管如此,仍然必须具备强有力的事件响应计划,确保如果发生安全事件,这些豁免的 Secrets 可以及时轮换,从而减轻潜在风险并保障环境安全。这种全面的 Secrets 轮换方法不仅保证了 Secrets 本身的安全性,还确保了整个 Kubernetes 生态系统的韧性和完整性。
处理密钥更新和轮换
处理密钥更新和轮换是保持安全性的重要方面。正如 NIST 所推荐的,定期轮换密钥是减少安全风险的关键。
我们更新密钥的方法包括几种方式:
-
监控和重载:我们持续监控密钥的变化,将其更新到应用程序内存中,确保使用最新的值。
-
密钥链模式:这涉及维护多个版本的密钥,以防止停机。重要的是监控使用中的版本,并及时淘汰旧版本。
-
重启作为重载方法:实施一个自动系统,在密钥更改时重启 Pod,可能使用 Kubernetes 作业或其他工具来检测更改并启动重启。
每个应用程序的需求决定了选择的方法,重点关注以下几点:
-
及时使用密钥:根据服务器处理多个值的能力
-
平滑密钥更改:确保应用程序平稳管理新密钥,不丢失状态或导致停机。
-
避免停机:对于无法容忍停机的应用程序,使用多 Pod 副本和滚动更新等策略
-
监控和警报:保持一个强健的系统来监控密钥和 Pod,设有密钥轮换和 Pod 重启的警报
这一策略旨在同时确保安全性和操作效率,适应各种应用程序的密钥轮换需求。
用于自动密钥轮换的 Kubernetes 示例清单
以下是一个简化的示例,说明如何设置 Kubernetes 作业,以便在密钥轮换时触发 Pod 重启:
apiVersion: batch/v1
kind: Job
metadata:
name: secret-rotator
spec:
template:
spec:
containers:
- name: secret-rotator
image: user-implemented-rotator-image
env:
- name: VAULT_ADDR
value: "http://vault:8200"
- name: SECRET_PATH
value: "secret/my-app"
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: POD_SELECTOR
value: "app=my-app"
restartPolicy: Never
backoffLimit: 0
这里是一个展示附加密钥轮换作业的图示:

图 13.1 – 密钥轮换的 Cron 作业
在这个例子中,作业运行一个容器,监视 Vault 中的密钥轮换,并在检测到轮换时重启 Kubernetes 中的相关 Pod。这确保受影响 Pod 的初始化容器再次运行,获取最新版本的密钥。通过采用这些策略,可以确保应用程序保持安全,使用最新的密钥,同时最小化停机并保持稳健且有弹性的部署。
授权泛滥问题
在 Kubernetes 中,管理对密钥的访问需要特别关注,因为涉及到微妙的平衡。
Kubernetes 中的授权泛滥问题发生在权限设置过于宽泛时,通常是无意间造成的,导致显著的安全风险。这通常出现在 RBAC 配置未充分定制的情况下,导致用户或服务获得超过必要的权限。
当然,像 Kubernetes RBAC 和与身份管理解决方案的集成等解决方案是众所周知的,但实际问题包括以下几点:
-
如何强制执行合规性并防止用户利用或规避配置源
-
如何有效监控并及时撤销任何政策违规行为
-
如何审计基于配置的访问策略更改并理解其影响
Kubernetes RBAC 中的典型配置错误可能会无意中允许意外的用户组访问所有 Secrets:
# serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: myapp-sa
---
# role.yaml
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: myapp-role
rules:
- apiGroups: [""]
resources: ["pods", "secrets"]
verbs: ["get", "watch", "list", "create", "update", "delete"]
---
# misconfigured-role-binding.yaml
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: myapp-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: myapp-role
subjects:
- kind: ServiceAccount
name: myapp-sa
在此示例中,myapp-sa服务账户被创建时具有过于宽泛的 Pod 和 Secrets 访问权限。myapp-role-binding角色绑定通过引用myapp-role加剧了这一问题,myapp-role授予对这些资源的完全访问权限。因此,myapp-sa拥有超过必要权限的权限,存在未经授权访问敏感数据的风险。
解决这个问题需要实施细粒度的访问控制,精确地定义每个角色和用户的权限,遵循最小权限原则。这意味着仅授予用户执行特定任务所需的访问权限。定期审查和更新 RBAC 策略也是至关重要的。
客户端的标记、标签和屏蔽
在 Kubernetes 环境中,管理 Secrets 的方法应是整体性的,不仅要考虑 Secrets 如何存储,还要考虑它们在客户端如何管理和交互。在此背景下,标记、标签和屏蔽 Secrets 变得至关重要。采用这些实践的理由在于它们能够增强安全性、可管理性并遵守合规标准。通过标记和标签 Secrets,团队嵌入了关键的元数据,阐明了机密的目的、关联资源和生命周期阶段。这些元数据成为一项强大的工具,有助于实施精细的访问控制,并简化跨系统跟踪机密使用情况的过程。
在实施过程中,这些实践应无缝地集成到开发和部署工作流程中。应鼓励或甚至强制开发人员和操作人员在其部署配置中包含相关标签和标记,以确保每个机密从一开始就得到适当的注解。另一方面,屏蔽是指在日志或用户界面中遮蔽机密值,以防止意外暴露,这在调试场景中尤其常见。系统需要配置为自动识别和删除敏感信息,这一任务可以通过模式识别、校验和或在应用程序日志配置中明确标记敏感字段来实现。
从长远来看,这些实践的回报是巨大的。通过标记和标签提供的大量元数据促进了强大而持续的审计轨迹,使团队能够在较长时间内有效地追踪和管理机密。这不仅对日常操作的完整性至关重要,而且对于满足严格的安全性和合规性要求也至关重要。任何未经授权的访问或修改都可以迅速识别和纠正,确保组织的机密管理策略既安全又具有弹性。与此同时,掩码技术确保即使在日志或 UI 被暴露的情况下,机密值的机密性仍然得到保持,从而减少了意外暴露的风险。
通过采用这些客户端实践,组织为全面的机密管理策略奠定了基础,确保机密在其 Kubernetes 环境中被管理、追踪和保护。这种方法不仅增强了组织的安全态势,还确保其机密管理实践与行业最佳实践和合规标准相一致。
服务器端的审计和监控
在机密管理的服务器端,定期审计和强有力的监控在维护安全和合规的 Kubernetes 环境中发挥着至关重要的作用。审计作为一项必要的实践,确保所有与机密的交互——无论是访问、修改还是删除——都被细致地记录和审查。这一做法不仅对维护机密的完整性至关重要,还能够验证所有访问模式是否遵循既定的安全政策和合规要求。通过实施全面的审计,组织创建了安全的证据轨迹,促进了涉及敏感信息的所有操作的问责制和透明度。
要实现这种级别的监管,必须利用专为强大审计和监控量身定制的技术和工具。这包括实施能够提供实时警报和详细访问日志的解决方案,确保任何异常或未经授权的活动都能被及时检测和处理。像 Kubernetes 的审计日志以及 Prometheus 或 Grafana 等监控解决方案,可以被配置为在环境中无缝工作,为团队提供所需的可见性,以保障其机密。此外,将这些工具与现有的安全信息和事件管理(SIEM)系统集成,能够进一步增强组织对事件关联、模式识别以及迅速有效应对潜在威胁的能力。
一致性审计对于安全性和遵守法规的重要性,以及增强审计的策略和工具,确保了对机密访问和变更的适当监控。
确保安全的密钥分发
确保在 Kubernetes 环境中安全地分发密钥至关重要,因为不安全的做法可能导致严重的漏洞和安全漏洞。当密钥以明文形式存储时,无论是在主机的文件系统中还是在容器环境中,它们都容易受到未经授权的访问和潜在的滥用。如果攻击者获得了主机或容器的访问权限,他们就能轻松地检索并滥用这些密钥,这种漏洞尤为令人担忧。
理解并建立明确的安全边界对于缓解这些风险至关重要。组织必须采取主动立场,实施严格的控制措施并采用最小权限原则,以在发生安全事件时最小化爆炸半径。这种方法涉及限制密钥的访问,确保只有必要的人员可以访问,并且密钥不会不必要地暴露。
在密钥分发过程中建立信任链对于保持密钥的完整性和机密性至关重要。这涉及到在分发过程的每一个步骤中验证密钥的真实性和完整性,从它们生成或从密钥存储中检索的时刻开始,经过传输,最终到达预定的服务或应用程序进行使用。可以实施多种措施来确保这一信任链,包括在传输过程中使用加密技术、采用安全的密钥注入方法进行分发,并利用可信的平台和身份进行访问控制。
退役和撤销密钥
退役和撤销密钥是密钥生命周期管理的关键环节,确保过时、已泄露或其他不再需要的密钥被及时且安全地移除。实施退役最佳实践包括安全地退役密钥,并确保它们从密钥存储和可能存在的任何环境中彻底清除。这个过程必须彻底且系统化,以防止与残留密钥相关的潜在安全风险。
将密钥管理集成到更广泛的服务退役工作流中至关重要。当服务被退役或替换时,相关的密钥也应同时进行退役。这确保了一个协调一致、简化的过程,减少了疏漏和潜在安全漏洞的风险。通过将密钥考虑直接嵌入到服务退役过程中,组织可以加强一致性并遵守安全协议。
撤销协议在主动和被动的 Secrets 管理中都起着关键作用。主动管理中,Secrets 应按照预定义的计划或触发条件进行轮换和撤销,例如证书到期或服务生命周期结束。被动管理中,一旦发生安全事件或发现 Secrets 泄露,必须立即撤销以减轻风险并防止未经授权的访问。建立清晰高效的撤销协议能确保团队能够迅速响应,最大限度地减少安全漏洞的潜在影响。
这些实践共同加强了 Secrets 管理生命周期,确保 Secrets 不仅在生成和使用过程中保持安全,同时在退役和撤销时也保持同样的谨慎。这种全面的方法提升了 Kubernetes 环境的整体安全态势,保护敏感信息并维护生产系统的完整性。
责任、值班支持、渗透测试和风险评估
在 Kubernetes 生产环境中有效管理 Secrets,需要明确划分责任、健全的值班支持结构,并通过渗透测试和风险评估持续致力于安全性。
责任与值班支持
管理部署平台的团队必须明确划分并分配与 Secrets 分发和 Secrets 存储管理相关的责任。这不仅包括 Secrets 的初始设置和分发,还包括其持续管理、更新和轮换。值班责任是这一过程的关键组成部分,确保始终有具备专业知识和能力的团队成员可以应对可能出现的问题,从访问问题、配置错误到潜在的安全事件。这些团队成员必须熟练掌握 Secrets 管理工具的配置和调试工作,以及更广泛的 Kubernetes 环境,以有效地处理和解决事件。此外,他们还应积极参与系统的增强工作,不断提升系统的稳定性、安全性和效率。
渗透测试与风险评估
定期对生产环境中的 Secrets 存储进行渗透测试至关重要,能够识别和减轻潜在的安全风险。这种主动的安全方法有助于发现漏洞,评估访问策略的健壮性,并评估 Secrets 可能被暴露的路径。这些渗透测试的结果应直接纳入组织的更广泛风险评估工作中,帮助全面了解系统的安全态势,并为风险减缓提供有依据的决策指导。
渗透测试不应是一次性的努力,而应是一个持续的实践,不断演进,以应对新出现的威胁和漏洞。它应涵盖 Secrets 管理生命周期的各个方面,从 Secrets 的初步配置,到它们的存储、分发,直至最终的退役。
通过将这些实践整合到组织的整体风险评估框架中,团队可以确保不仅解决了当前的威胁,还建立了一个能够应对未来挑战的弹性系统。对责任、值班支持、渗透测试和风险评估的全面方法,是在 Kubernetes 生产环境中维持安全高效的 Secrets 管理框架的关键组成部分。
总结
在本章中,我们深入探讨了在生产集群中管理 Kubernetes Secrets 的复杂性。我们强调了有效的 Secrets 管理所需的品质,并审视了各种部署策略及其与 CI/CD 流程的整合。此外,我们还通过一个关于 Keywhiz 的详细案例研究,全面理解了 Secrets 管理的发展,强调了涵盖 Secrets 管理整个生命周期的整体方法。
下一章将总结我们在本书中获得的见解和知识,并对 Kubernetes Secrets 管理的演变及未来趋势提供前瞻性视角。
第十四章:结论及 Kubernetes Secrets 管理的未来
本手册为您提供了确保容器编排平台安全的基本组成部分,作为持续改进安全态势的参考,同时关注外部解决方案的需求,以增强或改进当前 Kubernetes 设计,尤其是在管理 Secrets 方面。
在本章中,我们将讨论以下主要主题:
-
当前 Kubernetes 的状态以及它在 Secrets 管理方面的改进
-
Kubernetes 项目带来的未来以及如何影响它
-
如何保持与最新趋势和实践同步
当前 Kubernetes 的状态
随着 Kubernetes 的广泛采用,本手册展示了可以利用 Kubernetes 本地构件作为内部保险库解决方案的方案,以及来自 Azure、AWS、GCP 和 HashiCorp 的外部解决方案。两者的结合可能是改善安全态势的关键,尤其是在管理平台组件和应用程序的 Secrets 时。
这些解决方案作为保护措施,确保存储在 Kubernetes 集群中的敏感信息的安全,围绕着存储、管理和分发 API 密钥、密码和证书的原则,确保在没有或有限的安全暴露下安全地处理这些数据。
本地解决方案
Kubernetes 项目使用Kubernetes 增强提案(KEP)来记录设计变更,以及这些变更的目标版本(包括 alpha、beta 和正式发布(GA)版本)。
KMS 提供商
KEP 自 1.25 版本以来一直记录与本地 Kubernetes Secrets 管理相关的安全变更,涉及以下主题:
-
实现无缝的密钥轮换
-
引入可靠性的健康检查
-
在不妥协可恢复性和安全性的前提下提高性能
-
改善 Kubernetes API 服务器、KMS 插件和 KMS 之间的端到端可观察性,并具备审计功能
这些改进与我们在本手册中观察到并通过相应措施缓解的挑战保持一致,有助于提高并维护 Kubernetes 集群的安全态势。
截至写作时,Kubernetes 的最新版本是 1.28。它带来了一系列关于本地 Secrets 管理的改进,具体内容已记录在KEP-3299、KMSv2 改进中:
-
以下是相关变更:
-
放弃之前的
KMSv1功能,转而支持KMSv2;此变更旨在推动KMSv2的 GA(正式发布),该版本计划在 1.29 版本中发布。 -
实现新的
KMSv2KDF功能门控或为每次加密生成新的 DEK。此变更为运营团队提供了一种可选行为,可以启用此功能以遵守那些不接受默认KMSv2行为(即在 Kubernetes API 服务器启动时生成单一 DEK)的法规。
-
这是 KEP 3299 的 GitHub 链接:github.com/kubernetes/enhancements/blob/master/keps/sig-auth/3299-kms-v2-improvements/README.md。
请参阅第三章中的键值数据部分,了解KMSv1和KMSv2实现之间的技术演变概述。
请注意,KMSv2 在 Kubernetes 1.28 版本中仍被视为测试版,并计划在 1.29 版本中达到 GA 状态(写作时的情况)。
CSI 密钥存储
由于依赖于外部保管库服务,CSI 密钥存储可以被视为 Kubernetes 的外部解决方案。然而,CSI 密钥存储是:
-
基于容器存储接口(CSI)架构,这是一个原生的 Kubernetes 接口
-
由 Kubernetes 项目的兴趣小组(SIG)推动
-
利用原生的 Kubernetes API 对象,无需代理、侧车容器或非原生的 Kubernetes 模式
这是 CSI 密钥存储项目的链接:secrets-store-csi-driver.sigs.k8s.io/。
不仅如此,这种模型可以简化从外部保管库向应用程序注入密钥,还可以在需要时将密钥同步为原生的 Kubernetes 密钥对象。
同步密钥能力
使用同步密钥功能时,CSI 密钥存储会将外部密钥同步为原生 Kubernetes 密钥对象,这要求设置数据静态加密,以确保这些对象中的敏感数据负载被加密。
请参阅第三章的键值数据部分,了解关于不同选项的概述,标题为《以 Kubernetes 原生方式加密密钥》。
就像 Kubernetes 的kms提供程序一样,CSI 密钥存储遵循相同的最小要求,以拥抱 DevSecOps 模型,包括以下内容:
-
一个原生的 CSI 模型,供平台团队实现与外部保管服务的简单连接
-
通过原生 Kubernetes API 对象提供的自服务模型,供应用团队使用密钥
-
度量、日志记录和密钥自动旋转,以遵守安全法规和框架
-
从路线图的角度来看,以及当前实施状态,可以在此查看:
secrets-store-csi-driver.sigs.k8s.io/design-docs
CSI 密钥存储项目是一个优雅且原生的解决方案,越来越受欢迎,甚至已经完全集成到企业 Kubernetes 发行版中,如 Red Hat OpenShift 版本 4.14。
外部解决方案
在上一节中,我们查看了 Kubernetes 项目的原生解决方案的路线图,因为它们是开源的。然而,在这里做这件事比较困难,因为并非所有解决方案都是开源的。
我们在本手册中审查的外部解决方案是最常见的,它们在各自领域处于领先地位,或是云服务提供商的原生解决方案。
与 Kubernetes 项目类似,这些生态系统解决方案正在迅速发展,以支持更多的模式并改善业务连续性规划,但更重要的是,提升平台和运行在其上的应用程序的安全态势。
由于解决方案的种类繁多,我们在撰写时暂时聚焦于一个例子。HashiCorp 最近推出的 Vault Radar 提供了扫描器,可以识别应用程序代码中泄漏或硬编码的 Secrets。Vault Radar 为安全暴露提供了修复路径,并通过风险优先级对其进行排序。
在提升和维护安全态势的过程中,随着多云架构的普及,识别敏感信息的扩散对于减少攻击面至关重要。
Kubernetes 的未来状态
在考虑某个软件或解决方案的未来状态时,有两个考虑因素:
-
与特定、未来和缺失的用例相关的需求或愿望
-
现有路线图、其与当前架构的关系以及如何纳入未来的增强功能
正如我们在本手册中所观察到的,没有一个软件能解决所有需求。它通常涉及将多个项目结合起来,组成一个解决方案,朝着我们的最终目标迈进,同时管理 Kubernetes 平台和运行在其上的应用程序的密钥。因此,让我们对未来可能期望的解决方案进行一个以解决问题为驱动的概述。
思考的食粮和增强功能
从 Kubernetes 项目提供的原生栈开始,提议的变更和路线图已在 KEP-3299 中进行了文档记录。负责 CSI Secrets Store 的 SIG 设计文档也同样如此。
一般来说,尽管我们对 Kubernetes 的 API 驱动设计表示尊重,但以下主题将非常受欢迎:
-
aesgcm和aescbc带来了一系列挑战,这些挑战相对增加了实现的复杂性,暴露了它们的加密密钥或缺乏商业版 KMS 插件来提供企业支持和服务。此变更不仅有助于加密存储在etcd中的敏感数据负载,还可以加密来自 API 对象的任何其他数据负载。例如,部署定义可能会提供关于平台和应用程序的足够有价值的信息,供恶意攻击者利用。 -
动态密钥:根据特定场景更新密钥的能力,这是符合大多数(如果不是所有)法规和框架的要求。相关的努力正在通过以下倡议进行考虑:
-
KEP-3299 在加密轮换自动化方面具有优先权,以减少潜在的攻击。
-
CSI Secrets Store 会在外部源发生变化时触发机密负载的更新。
-
虽然前两个点可以观察并操作密钥或机密的生命周期,但审核机密负载的实际使用情况和唯一性将大大有助于减少相同负载在多个平台和应用程序中的扩散,从而在相关负载被泄露时避免大规模曝光。
-
-
etcd以避免潜在的安全暴露,那么用外部保险库替换该数据存储,利用 Kubernetes KMS 插件提供者如何?这将大大减轻知识负担、时间和知识成本以及维护工作。 -
etcd数据存储。此类解决方案的前提是将多个服务实例(至少三个以满足明显的高可用性要求)绑定到定义的网络段,并且在该网络段的边界内存储部分加密密钥。采用这种加密密钥服务模型,并结合使用aesgcm和aescbc,可以比现在更安全,且部署外部服务的数量有限。 -
丰富日志功能:通过安全信息与事件管理(SIEM)系统,可以从与机密相关的角度提供更详细的安全态势视图。这通常是企业组织的请求,它们希望利用集中日志系统的强大功能,关联事件,并在安全事件响应过程中迅速审核潜在受影响的系统,尤其是考虑到多平台和多云架构中对象的扩散。
这些是你在阅读本手册并实现共享解决方案后可能想到的五个例子。
如何分享你的想法
我们都有改进可用性和支持组织生存的需求。就像之前的五个例子一样,这些可能尚未在任何软件项目和供应商的增强请求、路线图或设计文档中提到。
那么,我们如何影响开发呢?一般规则是打开问题或工单来表达你的增强请求。
对于专有解决方案,可以通过联系供应商并请求开放与您的增强请求相关的工单来进行翻译,这可能(也可能不会)进入供应商的工程组织。在这个阶段,类似请求的发展过程和跟踪黑洞活动的方式差不多。
对于开源项目,包括 Kubernetes,过程更加开放。首先,建议你在 Slack 上或在聚会上与相关 SIG 讨论你的想法。这将帮助你发现是否有类似的工作流正在进行,你可以参与,或者是否值得将其介绍给 Kubernetes 项目开发团队。
在这个阶段,下一步是创建一个新的 GitHub 问题。让我们看看 GitHub 问题 #111532,关于 Kubernetes API 服务器默认行为如何处理 EncryptionConfiguration API 对象:
你希望添加什么?
EncryptionConfiguration API 对提供商列表的当前行为是基于顺序的,因此 API 服务器必须重新启动才能识别新定义的顺序。
举个例子,我们可以考虑从 这个定义开始:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- identity: {}
- kms:
name: myKmsPlugin
endpoint: unix:///tmp/myKmsPlugin.sock
cachesize: 100
迁移到以下内容将需要重新启动 API 服务器:
---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms:
name: myKmsPlugin
endpoint: unix:///tmp/myKmsPlugin.sock
cachesize: 100
- identity: {}
通过提案 #111405,所需的构造支持 此实现。
为什么 需要这样做?
当前行为是服务视角中的中断来源,要求具备提升权限的用户,并且不允许在托管 Kubernetes 环境中利用 KMS 提供商的插件功能(无法访问控制 平面节点)。
此更改将改善以下方面:
-
第二天操作;将 EncryptionConfiguration API 移动到常规 CRUD API 对象
-
通过避免重新启动 API 服务器,增强韧性和服务持续性
-
使用 KMS 提供商插件来管理 Kubernetes 服务
关于 GitHub 问题 #111532 的说明
这个例子是来自本书 GitHub 仓库的原始摘录,未经语言修正或格式化处理。
模板很简单 —— 明确解释“做什么”和“为什么”,并附上清晰的例子,如果可能,提供如何操作以及代码来说明更改。
Kubernetes SIG 认证负责 KMSv2 实现,接手了 GitHub 问题 #111532,并通过实施 GitHub #111919 的代码更改解决了增强请求。此更改的结果是,当修改 EncryptionConfiguration API 对象时,可以重新加载配置而无需重新启动 Kubernetes API 服务器。
以下链接将为你提供直接访问资源的途径,帮助你为 Kubernetes 项目做出贡献:
-
如何联系 Kubernetes 社区:
kubernetes.io/community/ -
Kubernetes GitHub 组织:
github.com/kubernetes -
Kubernetes SIGs:
github.com/kubernetes-sigs -
GitHub 问题 #111532 作为示例:
github.com/kubernetes/kubernetes/issues/111532#issue-1321971248 -
GitHub issue #111919 用于解决增强功能 #111532:
github.com/kubernetes/kubernetes/issues/111919
尽管对于一个如此庞大的项目来说,提出一个问题可能显得有些令人生畏,但围绕 Kubernetes 的开源社区一直在为任何人分享想法和经验创造安全空间。现在轮到你来贡献了!
持续改进
本手册采用了持续改进的方法,考虑了 DevSecOps 最佳实践。在设计、架构、实施和保护任何平台或软件时,应该考虑各种原则。让我们来看看。
技能获得
大多数安全暴露都与知识或意识的缺乏有关。DevSecOps 的一个原则是跨团队协作,成员可以分享他们的经验和发现,因为他们共同承担着确保最终用户和客户的系统安全的责任。
参加培训、网络研讨会和聚会应该是任何组织的优先事项,以理解安全性的重要性并提升其安全态势。
最后,通过分享经验、知识、代码和想法,贡献开源项目,回馈组织。
及早开始,快速失败,持续迭代
大多数组织在构建基础设施时仍倾向于采用瀑布式方法,缺乏创新。
尽管这种项目方法论对于许多领域都是有效的,但如果等到最后时刻再实施解决方案,可能会导致显著的延迟,产生次优结果,甚至更糟,项目失败。
另一个考虑采用这种方法论的原因是,在过去二十年里,构建新平台的创新受限。因此,Kubernetes 的采用往往伴随着遗留模式的痕迹,导致实施效果不佳或给应用和运维团队带来额外负担。
对于像 Kubernetes 这样的云原生平台,一个好的实践是及早开始实施,确保所有利益相关者参与其中,并与项目一起学习,持续反馈成功、失败和未来的改进。这将形成一种混合项目模型,兼顾瀑布式和敏捷项目管理风格,从而满足企业对规划和加速交付的需求。
自动化作为策略,Everything as Code (EaC)
大多数安全漏洞都与缺乏自动化有关。当团队在处理安全问题或事件时,通常是在系统上执行任务,将其记录在工单中,工作就此止步。
作为最佳实践,实施系统的每一个动作都应该被编码、通过源代码修订系统进行审查,并且自动化。包括所有团队的活动,因为从安全角度来看,它们都对现有程序以及与事件或新发现的漏洞相关的未来程序负责。
从那里开始,需要不断地进行偏差检测,以确保所有系统都能及时更新最新的安全修复。
一个好的实践是将一切视为代码(EaC)用于自动化你的基础设施、应用程序、治理、合规性和安全性。这样做时,整个堆栈,从硬件到运行在容器中的应用程序,再到业务连续性计划,都可以成为你的持续集成与持续交付(CI/CD)流水线的一部分。这种方法将充当防止在不安全平台上部署关键应用程序的守门人。
威胁建模
威胁建模在平台和应用程序的实施与部署过程中,能够及早识别潜在的安全威胁和漏洞。安全团队应成为每个领域核心团队的一部分,帮助发现可能出错的地方,并规划相关的缓解措施。
通过借助适当的工具和协作文化,威胁建模有助于创建一个积极主动的安全态势,从而减少甚至防止重大安全问题在成为现实之前发生。
事件响应
在实施所有这些预防措施时,安全事件将会发生或后续发生。DevSecOps 实践包括响应计划,以简化一旦检测到安全事件后如何处理。安全团队将通过定义角色、沟通渠道以及采取的行动来培训组织,以遏制和缓解安全风险。
事件响应不仅仅是一个文档化的过程,它应该是一个定期的演练,能够随机触发,并且可以像锁盾演习一样通过游戏化的方式提升团队的能力,尤其是在面对网络攻击时。
这里有一个链接,展示了国际层面的年度“锁盾”演习:ccdcoe.org/exercises/locked-shields/。
总结
恭喜你——你做到了!你已经阅读了本手册,它作为任何采用 Kubernetes 并关注密钥管理话题的组织的参考。通常从传统平台的角度来看,这一问题似乎是可控的,但对于作为领先云原生容器平台的 Kubernetes 来说,情况完全不同。
本手册提供了一个全面的技术示例 walkthrough,讲解如何应对挑战、回答业务连续性的问题,并提供有关审计和合规的必要考虑。但是,如果这些内容被视为一次性实施的终极目标,而不是采纳 DevSecOps 思维方式,它们将不具有太大意义。
尽管大多数人将 DevSecOps 视为一个流行词,但它是一个根本性的转变,组织可以通过这一转变快速学习并采用新技术。这种方法需要支持组织基础设施的各团队之间的跨部门协作。通过培养分享即关怀的文化,自动化安全策略、检查和修复的任务将成为一种自然而然的活动,无需将安全视为创新的瓶颈。
采纳本手册中分享的文化、流程和技术将使组织能够更快速、更安全、更可靠且符合合规性地向客户交付价值。
技术是简单的,不应成为组织变革的替代品。希望本书能够帮助你选择合适的工具集来管理你的秘密,并激发你在注入 DevSecOps 文化和持续变革的旅程中的转型。


浙公网安备 33010602011771号