亚马逊AWS官方博客

使用 Sealed Secrets 在 Kubernetes 中管理密钥部署

Kubernetes 是一个用于对容器化应用程序进行自动部署、扩展和管理的开源系统。该系统非常适合于利用云的弹性来大规模构建和部署原生云应用程序的使用案例。Amazon Elastic Kubernetes 服务 (Amazon EKS) 是一项用于在 AWS 上运行生产级高可用性 Kubernetes 集群的托管服务,让您无需设置或维护 Kubernetes 控制平面。如今,成千上万的客户都选择使用 Amazon EKS 来大规模运行容器化应用程序。

无论客户是使用 Kops 等开源工具来建立 Kubernetes 集群并自行管理,还是使用 Amazon EKS 等托管服务,开发运营团队通常都会使用持续交付管道来部署客户的 Kubernetes 集群的各种资源。用户可以将与这些集群资源相关的 YAML 清单存储在 Git 存储库中并从中进行版本控制。

在本博文中,我们将详细介绍 Sealed Secrets 开源项目中的工具使用情况,该项目允许用户管理将敏感信息部署到 Kubernetes 集群的过程,还可以将这些信息安全地存储在 Git 存储库中和集成到持续交付管道。

使用 GitOps 进行持续交付

GitOps 是 WeaveWorks 创造的一个术语,是一种进行 Kubernetes 集群管理和持续交付的方式。这种方法将 Git 存储库指定为部署构件(例如 YAML 文件)的单一事实来源,这些构件提供了描述集群状态的声明式方法。如下图中的架构所示,Weave Flux 代理在 Kubernetes 集群中运行并监视 Git 存储库和镜像注册表,例如与应用程序工作负载有关的容器镜像所在的 Amazon Elastic Container Registry (Amazon ECR) 和 Docker Hub。如果通过持续集成系统(例如 Jenkins)将对部署构件的更改推送到此配置存储库或者将新镜像推送到镜像注册表,则 Weave Flux 代理会通过撤销这些更改并更新已部署到集群的相关应用程序工作负载来做出响应。

持续交付的 GitOps 工作流

 

处理 Secret 时遇到的挑战

Kubernetes 集群具有不同类型的资源,例如 Deployment、DaemonSet、ConfigMap 和 Secret 等。Secret 是一种资源,可帮助集群操作员管理对密码、OAuth 令牌和 SSH 密钥等敏感信息的部署。这些 Secret 可作为数据卷进行装载,也可以作为环境变量向 Kubernetes Pod 中的容器公开,从而解耦 Pod 部署与管理 Pod 中容器化应用程序所需的敏感数据。

此处面临的挑战是通过将相关 YAML 清单存储到集群外部的 Git 存储库中这一方式,实现将这些 Secret 集成到 GitOps 工作流中的目的。Secret 中的数据仅使用 Base64 编码进行混淆。解码 Base64 编码的数据很简单,因此将这样的文件存储在 Git 存储库中是非常不安全的。开发人员经常会不小心将这些文件检入 Git 存储库中,使凭据等敏感信息暴露到生产数据库中。

为了应对这项挑战,Sealed Secrets 开源项目提供了一种针对 Secret 对象的加密机制,使您可以安全地将其存储在私有或公有存储库中。您也可以借助 kubectl 等工具使用常规工作流将这些已加密的 Secret 部署到 Kubernetes 集群中。

工作原理

Sealed Secrets 包括以下组成部分:

  1. 一个部署到集群的控制器
  2. 一个名为 kubeseal 的 CLI 工具
  3. 一个名为 SealedSecret 的定制化资源定义 (CRD)

启动后,控制器会查找集群范围的私钥/公钥对,如果未找到,则会生成一个新的 4096 位 RSA 密钥对。私钥保存在一个 Secret 对象中,该对象位于控制器所在的命名空间中。任何想要在此集群中使用 Sealed Secrets 的人都可以公开获得此密钥的公钥部分。

加密时,原始 Secret 中的每个值都使用带有随机生成的会话密钥的 AES-256 方式进行对称加密。然后,将 SHA256 和原始 Secret 的命名空间/名称作为输入参数,使用控制器的公钥对会话密钥进行非对称加密。加密过程的输出结果是一个字符串,其构造为:加密会话密钥的长度(2 个字节)+ 加密会话密钥 + 已加密的 Secret

将 SealedSecret 自定义资源部署到 Kubernetes 集群时,控制器会拾取该资源,然后使用私钥将其解封并创建一个 Secret 资源。解密时,会再次使用 SealedSecret 的命名空间/名称作为输入参数。这样可以确保 SealedSecret 和 Secret 严格绑定到相同的命名空间和名称。

配套的 CLI 工具 kubeseal 用于使用公钥从 Secret 资源定义中创建 SealedSecret 定制化资源定义 (CRD)。kubeseal 可以通过 Kubernetes API 服务器与控制器进行通信,并在运行时检索加密 Secret 所需的公钥。您也可以从控制器下载公钥并保存在本地以便离线使用。

SealedSecrets – 工作原理

安装 kubeseal 客户端

对于 Linux x86_64 系统,可以使用以下命令将客户端工具安装到 /usr/local/bin 中:

wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.10.0/kubeseal-linux-amd64 -O kubeseal
sudo install -m 755 kubeseal /usr/local/bin/kubeseal

对于 macOS 系统,客户端工具的安装方式如下:

brew install kubeseal

安装适用于 SealedSecret 的自定义控制器和 CRD

有关最新版本和详细的安装说明,请参见 Sealed Secrets 项目的 Git 存储库。截至撰写本文时,该项目的最新版本为 v0.12.1。您可以使用 kubectl 将其一步安装在 Kubernetes 集群上,如下方命令所示。此操作会在 kube-system 命名空间中安装控制器和 SealedSecret 定制化资源定义,并创建服务账户和 RBAC 构件,例如 Role/ClusterRole 和 RoleBinding/ClusterRoleBinding。

wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.12.1/controller.yaml
kubectl apply -f controller.yaml

来自控制器的日志中出现了 Secret 的名称,该 Secret 在 kube-system 命名空间中创建,并且包含控制器用于解封已部署到集群的 SealedSecrets 的私钥对。

kubectl logs sealed-secrets-controller-6b7dcdc847-9l8pz -n kube-system

上述命令的输出如下:

2020/04/09 23:06:50 Starting sealed-secrets controller version: v0.12.1
2020/04/09 23:06:50 Searching for existing private keys
2020/04/09 23:06:53 New key written to kube-system/sealed-secrets-keyhvdtf
2020/04/09 23:06:53 Certificate is 
-----BEGIN CERTIFICATE-----
MIIErTCCApWgAwIBAgIQILmhsVqF7t1YIRzdZp+injANBgkqhkiG9w0BAQsFADAA
MB4XDTIwMDQwOTIzMDY1M1oXDTMwMDQwNzIzMDY1M1owADCCAiIwDQYJKoZIhvcN
AQEBBQADggIPADCCAgoCggIBAM/LlngsOuuBtDD/9nbfjIoZ2kxgepEzzBQWmpr9
gkWpw2cqOggK3ZdEv+JYw/dxJIZ7E8G/PNiY7YzcR1JDbFxclmvzuzaGDLhUdCzQ
X34ZJKOcIRAAuTYZiW60laRNVm37r68/tpok48I8+YxCD7qVtJ3y6ddRKt8iPMn2
mOZLohjy7pbZpcRp5tdS0lU8ZavdcYaUwIjxQR8XUS00nj4PIjVyKOGszjVac5bv
D4HTAZcLMNpe2iECQziZ4S/HmlBTR7+4z8hF9fk+aAy9ZeCvAZMGYBY3Um+x3qqz
3cNdlA6IF9nk1aWMzXkyx9K9CE2QIblT4WQVyB7ZYxPTtlpau9XsNGjBI4DzrcHH
VdDkUQEpNYwkxqpNndJlWSS5ZnglgoOwYLob4XT4hoqoqyrO5gCUKlarofmdmYm4
ELUeGPfYKlKP6rKOxQUQ0FHQL6Bvwx/Xx5wr6r1ylrC+++NFO6lMrQ0GhvYvtxN5
Ut0iy8fvXXuBP5HI8eBTEj0M7pAEZ+o/o+91N41KwuRoSBIOPD6q0e+ULBQ87Hth
gpuskPmQe5lT4dv0TuSTjdJg4LQuApm+w0ta4FXX9PoXoojSaHy0EoQQvw4bAr8x
8ioz6ogmGSyFPVF/qnBnQpH5RqiWAIW1jUgt2SIluNi8Zqc4IjImIaI/ZRObElqJ
qXAXAgMBAAGjIzAhMA4GA1UdDwEB/wQEAwIAATAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBCwUAA4ICAQBPyUeKHuk0D0XYRjiY9WQT0hEmH5ABP7Jypz9TnElz
asuIorEtaUEhtzKj1L+zEkyNzZzeKurDE9PL2xQB3HR675y0bDPS1ndpLDDIU7qt
38rvhb7iHbgWPti7EeKteBaNVvcaGfwhG8vnYoKKI8KzTy04rk2/0OFQNzpXOHra
Yw+mlho3MHtlT5AyTAuUqSXbRXLTMKOuXQlcGwdGAxiaV5i40AkfUICDhGZnMF8F
JNujDL0kpDF04qybMrJsOlHu1ClJWKcrxQh7v/ITfFdgw3N/lnA+OBXoXOs+VL+4
s6A20IddIBAaTd04ajmIZa7FEXSj+yMA2v7+gLE8POQlQANVNH2iJ6kgePlQB2+9
gdXeQIKwWyC9cCiN/yvCWz58fb2wS5lVE0SL7Jp9A42N6C+x+Hfy4jWTSB+SAQ6F
TKGWNHh9G1ghhFbq8cDShBsOr8MrBgxeWMlXG7o0yZlSbiC64FpZn2tYE3Elc8VX
kyhv+WxwggBsuk8qvzBK8sPRT+VmQ1w/GUdsJXw/GWaL2m3KZZyfTka3OwM1R7tJ
xgvkU74FVLk+SwcTVdCewfahSkmm43vQFr8u6EHqsYN2jY2h9ka8jTOzCof1OiPS
bwMtSuxW82ty6Pu3UyskQBRLmLOAuy/nmXZKjcdtB/OLELbUO44NVgxkIhWCLrCU
ew==
-----END CERTIFICATE-----

启动时,控制器会在其命名空间中搜索带有 sealedsecrets.bitnami.com/sealed-secrets-key 标签的 Secret。如果找不到,控制器会在其命名空间中创建新的 Secret,并将密钥对的公钥部分打印到输出日志中。您可以使用以下命令以 YAML 格式查看此 Secret(包含公有/私有密钥对)的内容:

kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml
apiVersion: v1
items:
- apiVersion: v1
  data:
    tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVyVENDQXBXZ0F3SUJBZ0lRSUxtaHNWcUY3dDFZSVJ6ZFpwK2luakFOQmdrcWhraUc5dzBCQVFzRkFEQUEKTUI0WERUSXdNRFF3T1RJek1EWTFNMW9YRFRNd01EUXdOekl6TURZMU0xb3dBRENDQWlJd0RRWUpLb1pJaHZjTgpBUUVCQlFBRGdnSVBBRENDQWdvQ2dnSUJBTS9MbG5nc091dUJ0REQvOW5iZmpJb1oya3hnZXBFenpCUVdtcHI5CmdrV3B3MmNxT2dnSzNaZEV2K0pZdy9keEpJWjdFOEcvUE5pWTdZemNSMUpEYkZ4Y2xtdnp1emFHRExoVWRDelEKWDM0WkpLT2NJUkFBdVRZWmlXNjBsYVJOVm0zN3I2OC90cG9rNDhJOCtZeENEN3FWdEozeTZkZFJLdDhpUE1uMgptT1pMb2hqeTdwYlpwY1JwNXRkUzBsVThaYXZkY1lhVXdJanhRUjhYVVMwMG5qNFBJalZ5S09Hc3pqVmFjNWJ2CkQ0SFRBWmNMTU5wZTJpRUNRemlaNFMvSG1sQlRSNys0ejhoRjlmaythQXk5WmVDdkFaTUdZQlkzVW0reDNxcXoKM2NOZGxBNklGOW5rMWFXTXpYa3l4OUs5Q0UyUUlibFQ0V1FWeUI3Wll4UFR0bHBhdTlYc05HakJJNER6cmNISApWZERrVVFFcE5Zd2t4cXBObmRKbFdTUzVabmdsZ29Pd1lMb2I0WFQ0aG9xb3F5ck81Z0NVS2xhcm9mbWRtWW00CkVMVWVHUGZZS2xLUDZyS094UVVRMEZIUUw2QnZ3eC9YeDV3cjZyMXlsckMrKytORk82bE1yUTBHaHZZdnR4TjUKVXQwaXk4ZnZYWHVCUDVISThlQlRFajBNN3BBRVorby9vKzkxTjQxS3d1Um9TQklPUEQ2cTBlK1VMQlE4N0h0aApncHVza1BtUWU1bFQ0ZHYwVHVTVGpkSmc0TFF1QXBtK3cwdGE0RlhYOVBvWG9valNhSHkwRW9RUXZ3NGJBcjh4Cjhpb3o2b2dtR1N5RlBWRi9xbkJuUXBINVJxaVdBSVcxalVndDJTSWx1Tmk4WnFjNElqSW1JYUkvWlJPYkVscUoKcVhBWEFnTUJBQUdqSXpBaE1BNEdBMVVkRHdFQi93UUVBd0lBQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUEwRwpDU3FHU0liM0RRRUJDd1VBQTRJQ0FRQlB5VWVLSHVrMEQwWFlSamlZOVdRVDBoRW1INUFCUDdKeXB6OVRuRWx6CmFzdUlvckV0YVVFaHR6S2oxTCt6RWt5TnpaemVLdXJERTlQTDJ4UUIzSFI2NzV5MGJEUFMxbmRwTERESVU3cXQKMzhydmhiN2lIYmdXUHRpN0VlS3RlQmFOVnZjYUdmd2hHOHZuWW9LS0k4S3pUeTA0cmsyLzBPRlFOenBYT0hyYQpZdyttbGhvM01IdGxUNUF5VEF1VXFTWGJSWExUTUtPdVhRbGNHd2RHQXhpYVY1aTQwQWtmVUlDRGhHWm5NRjhGCkpOdWpETDBrcERGMDRxeWJNckpzT2xIdTFDbEpXS2NyeFFoN3YvSVRmRmRndzNOL2xuQStPQlhvWE9zK1ZMKzQKczZBMjBJZGRJQkFhVGQwNGFqbUlaYTdGRVhTait5TUEydjcrZ0xFOFBPUWxRQU5WTkgyaUo2a2dlUGxRQjIrOQpnZFhlUUlLd1d5QzljQ2lOL3l2Q1d6NThmYjJ3UzVsVkUwU0w3SnA5QTQyTjZDK3grSGZ5NGpXVFNCK1NBUTZGClRLR1dOSGg5RzFnaGhGYnE4Y0RTaEJzT3I4TXJCZ3hlV01sWEc3bzB5WmxTYmlDNjRGcFpuMnRZRTNFbGM4VlgKa3loditXeHdnZ0JzdWs4cXZ6Qks4c1BSVCtWbVExdy9HVWRzSlh3L0dXYUwybTNLWlp5ZlRrYTNPd00xUjd0Sgp4Z3ZrVTc0RlZMaytTd2NUVmRDZXdmYWhTa21tNDN2UUZyOHU2RUhxc1lOMmpZMmg5a2E4alRPekNvZjFPaVBTCmJ3TXRTdXhXODJ0eTZQdTNVeXNrUUJSTG1MT0F1eS9ubVhaS2pjZHRCL09MRUxiVU80NE5WZ3hrSWhXQ0xyQ1UKZXc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
    tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS1FJQkFBS0NBZ0VBejh1V2VDdzY2NEcwTVAvMmR0K01paG5hVEdCNmtUUE1GQmFhbXYyQ1JhbkRaeW82CkNBcmRsMFMvNGxqRDkzRWtobnNUd2I4ODJKanRqTnhIVWtOc1hGeVdhL083Tm9ZTXVGUjBMTkJmZmhra281d2gKRUFDNU5obUpiclNWcEUxV2JmdXZyeisybWlUandqejVqRUlQdXBXMG5mTHAxMUVxM3lJOHlmYVk1a3VpR1BMdQpsdG1seEdubTExTFNWVHhscTkxeGhwVEFpUEZCSHhkUkxUU2VQZzhpTlhJbzRhek9OVnB6bHU4UGdkTUJsd3N3CjJsN2FJUUpET0puaEw4ZWFVRk5IdjdqUHlFWDErVDVvREwxbDRLOEJrd1pnRmpkU2I3SGVxclBkdzEyVURvZ1gKMmVUVnBZek5lVExIMHIwSVRaQWh1VlBoWkJYSUh0bGpFOU8yV2xxNzFldzBhTUVqZ1BPdHdjZFYwT1JSQVNrMQpqQ1RHcWsyZDBtVlpKTGxtZUNXQ2c3Qmd1aHZoZFBpR2lxaXJLczdtQUpRcVZxdWgrWjJaaWJnUXRSNFk5OWdxClVvL3FzbzdGQlJEUVVkQXZvRy9ESDlmSG5DdnF2WEtXc0w3NzQwVTdxVXl0RFFhRzlpKzNFM2xTM1NMTHgrOWQKZTRFL2tjang0Rk1TUFF6dWtBUm42aitqNzNVM2pVckM1R2hJRWc0OFBxclI3NVFzRkR6c2UyR0NtNnlRK1pCNwptVlBoMi9STzVKT04wbURndEM0Q21iN0RTMXJnVmRmMCtoZWlpTkpvZkxRU2hCQy9EaHNDdnpIeUtqUHFpQ1laCkxJVTlVWCtxY0dkQ2tmbEdxSllBaGJXTlNDM1pJaVc0Mkx4bXB6Z2lNaVlob2o5bEU1c1NXb21wY0JjQ0F3RUEKQVFLQ0FnQlVraWFHZUhIdWdkYUZqdGVQb0FKQi9xMmpJaTBnUmJXTWczcWZGQWhlTSs2c1lUcEhKYXowTU8zcgp3SGJaa1hudEpkQnZyVmFsVFBCNXdQbGlHTURVZ25aU0wxdUZvRjh5OG1ScUROQ2dzTGtCd2J5UEY4eEpvWEVXCjFuYUU3Vmo4NEUrcmdzSGQwSi9GNFMwcmtZTjNUQkM3ckM3U0RGM25mTGJDK0JOWXYzV1VzK0s1RUpIdjg2NFkKK3NOU0g0ZTl3QjNCU1c5bkROR1ZSdGNxRDkxTG9yc29oM0x1RG5mS3JTcVlSbW5JUzhtODRMZ2NXRGhzOE0vTgpESXZpOTFqdDBrZEVWNEp4bjAreUJsMHd1akRwbGpDTTF5NXFQRS9YMTh1cExCVll1eEJVTGIyUFdCeEFDYU5pCjdYRDRheWtpOEVOWmV6TWptZDNkK0ZuanF1bnU5bHdpUHhKZTl2Zng5QUJqaUhPRjZmWFpKT2dPUzhESWFaazkKc0Fpb2xveCt1VklFQUl1L0x4bytFaHJyTnA0QWFGVUdJWlNyemk1UFRSZWE1ZHFkeVVuemt2TGk5T3JSejNOUQprazJjYlRYUmZtZEtCTVNkZ1czM0R4dk0vTndId0d4VUNEeDFJRUtHYTB5YnFtM3h1QnNqeXJrYVZRNmYveUhyCkI3OUJzS0YwanU1MFhtRUhRbkFsZklkc0RFRi9uaWdBeFFLM1ZEN3ZDRVdmeU9mZUJnNFo4bkVzanhxRGlOQkgKQ1NjT1hoSHVVWllZMUJ2UXJQSzF0dVJpOU1yR0N5dmxrN1JnaHBBVDVtck1OSFlVc2haRE9ISlhYRDh3WkcrdgptbllkZ2JjTU15cnJ1Yzk2TkFCN3A4Y1ViaWxySVlHNXN4NE1QYlpPOUlRaWVBQjVrUUtDQVFFQTd3elQvL09yCmJHMVR2Nkg1UDFlR0hhMXpUeG5GTzI5UDl0SVRmSWdtZjB2TkFJeGNEdExJamxrcTRQVVg4eGZMQWdWUDFFOWQKdEE0L0d4dzFqQWNRVDBNalZIS3JhWEtkRGh5WU8wSytlekcrc1MyZ0xmV1NxOGh5MGYxclNuNktMcjJhQ1U4Ywp2allVbmtCclI3Mk1tMXYvV1g5YjZyV1NjdGdLMkpZclVNWXVEUlcrbTl0L2dDdU95Y3JXYnRqQXoyMURzOHR5CnBWOWxIMlczRTM2bVR5UTJVZzZzRnJ2NGtLMXVTeGxNTWRFQU1odWhVc1lseFhQYmVBWWxxYk5tUmNlbjJPMlAKam5jZHY1elJRZ3ZRLzJXVjBBWGdnU3pkK3ZjbXRRUFJ5ZG5EeFNXY3hiNWl1MWNnbFVoN3k2elB6akNDa0NFQQo4RjdqME5YNFdOdlJEd0tDQVFFQTNvZHVKNFhCcFlqdkJTcmY4NEduRUI4T2U2VlVYOGErRUxkMmg4ZTB3NWM1Cm9rZ1BCR0oxZVpnRXQyWXJQVkJ1RExvcEphUWVLMVlpcHBabDdHUG5lWUVrSTE3OWR1WnVmWE41YTZQelRTTncKeHZTTTZ0SGFKUnJLZno5NzFGS2M4dkVUQjVadjJUZFEzWFUvR1NrcWdKMHcreXd3MHZLUVpnc2wxK2YyY0s0TQpYcktwdnU1Y2hIRkhsWDFoYmZ2N2xzanN4UkNHSXl2NkpmbkpETy9JNDgwa21SQ3hUdHJubStuZVlxZjBEcXovCkVsUC9IQVlwUUpYMmJkbEJiZFRGaDF3VGFJQUJxSXFVdmZYR0s5Z3J6bXRPNDh5dHcxVDdnS0ZTRUdiR0JiVlcKbGN5WFZIVWc4R0FYVDJYUUt4SnFmK2V4VXh2WWh0UmpKaWwzY3NKZ2VRS0NBUUVBeGxqVWh1azk4WWQ5RitKagpsMFVlQ1Aza1VWdkdwUndsTTF6M3dqcU9CczEwV2VJY2VFZzVGTE96dWxoaStOZGpJRmdiOXNPcnNqeW42K3lxCkdYZTY5cWwwWlJ1SVVzUkF3SGJGY1ZaZUNvWXAvWVVvQlRwZjZwMDFlRHRYak1ZV0RkWlFPeTBqWWtncEwyMncKRmlTV3lFbTdSQjFDdlNyUFN1OHJnSzZKWGtveDU3V0ZKSGtwLzhVa2d4Y0VlWkRyMnJDRW5taE94aHl6SVN3YQpqZGhtVWdCditnSW1rKzUvdmp2STZoTWhmNncxQjE2WnFyNnlsSFVmUXlXR2xwbytYK1BieDBqRjlxV3JUMVBrCjVYSThoYzFhVXZLdFowRTlKb0Z2NG40NjBjc1lmenBJTEdOZU5LZUVaNWx1N242REpraGw3UVVWYkZ5dmxwWVQKckZjbnpRS0NBUUJiOEc1MWk5RFBHTDFRVUQrSTl3ZFVKTkN5QzBQSjhtM3lzQ29idlVvVkNYVDVkSFluNUpvTwpxOTArL01wZW9jMW1Hc0FIV2tCUXZWekJvUi9wUS9tTi9PbzJadmVuMlZyTElCdUplb3A2VTJzeitEUUVqTUZwClZTRlc0NTdBd1lVdzVxTnJIaCtHQ2xHeHZkQmREK0lNazJWNlVPNjNLUnE1M2w4N1RnNUd6ZEkwaWZLUi9SOWkKWlA4aloxTUt3dkpXZ1JzNTdETFBjMHI5eDY3bVZtZVVudHhCRldGOFovc0xNdHY3dk5LY0FhTzlLZEViL2Z1cgpRSW81Sm1yZSt1ckZteWcxbzdXTHNmMzBZZ2dIYzEvZC8vM3ZKbENnaElzSXdSNEx3cnFML3prUDJTQ285MSt0CmtMWHd5dXJ2OE1McHA0dGZBQUU4NjZFdlVqQ1V6SFJaQW9JQkFRRE81UGgzb0d1TVB4MWRkdlFDZ0N4bWplaWIKaEVYREdGR1FzaGx2eFZtUDdQdkdQRk5pWEFnUnlvdkZhd3BZZEkyWlhXbW8xaitTS2dsM0MrQUFpL0Uyd2d5QwpKN0QrME9ralRJemVaYzNHL3VBWWNyTVc1OHVnZEZrLzROdkJyQkQvdGd3SlhNaTRKOXFsa0NjMjRSUzJVQ0ZSClZnZldnN2xUYjJmM1gzSmZrUVZlOVJ5b1R6MUpIbXQ3UXQzc0NOOFEvRzRKbWsrY3k0VXdzQ2lmRDVWWTB1d2QKaDkxcGRNdDBWbUJQS1kzRUVyemhLR29ZVHdsYnJ6YlRnWDEvNWxQeSt3Q3BjTnBxR3RxK29jZ2xTbWI5VHhrTQo2NzMxeTc3NmFKTTNEb2VZM1lDU0VLUjVzU1hxaXRFNm93TWU1Z20vR2NqMEhVK1FqcE5jRUMzaXdGeEoKLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K
  kind: Secret
  metadata:
    creationTimestamp: "2020-04-09T23:06:53Z"
    generateName: sealed-secrets-key
    labels:
      sealedsecrets.bitnami.com/sealed-secrets-key: active
    name: sealed-secrets-keyhvdtf
    namespace: kube-system
    resourceVersion: "302785"
    selfLink: /api/v1/namespaces/kube-system/secrets/sealed-secrets-keyhvdtf
    uid: cd53c49a-7ab6-11ea-a485-0adbb667fc0b
  type: kubernetes.io/tls
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

密封 Secret

从 1.14 版开始,kubectl 支持使用 Kustomize 管理 Kubernetes 对象,Kustomize 提供的资源生成器可用于创建 Kubernetes 资源,例如 Secret 和 ConfigMap。您应在 kustomization.yaml 文件中指定 Kustomize 生成器。Secret 的 YAML 清单使用 kubectlkustomize 从文本键值对生成,如下方示例所示:

cat << EOF > kustomization.yaml
namespace: octank
secretGenerator:
- name: database-credentials
  literals:
  - username=admin
  - password=Tru5tN0!
generatorOptions:
  disableNameSuffixHash: true
EOF
kubectl kustomize . > secret.yaml

使用此 Secret,利用 kubeseal 创建 SealedSecret CRD 的 YAML 清单,如下所示:

kubeseal --format=yaml < secret.yaml > sealed-secret.yaml

kubeseal 使用公钥加密 Secret,该公钥是它在运行时从在 Kubernetes 集群中运行的控制器获取的。如果用户没有直接访问集群的权限,集群管理员可以从控制器日志中检索该公钥,并授权用户获取该公钥。

然后通过 kubeseal 使用公钥文件创建 SealedSecret CRD,如下所示:

kubeseal --format=yaml --cert=public-key-cert.pem < secret.yaml > sealed-secret.yaml

生成的 Secret 的 username 和 password 键具有采用 Base64 方式编码的值,如下所示:

apiVersion: v1
kind: Secret
metadata:
  name: database-credentials
  namespace: octank
data:
password: VHJ1NXROMCE=
username: YWRtaW4=
type: Opaque

生成的带有加密值的 SealedSecret 如下所示。

请注意,原始 Secret 中的键(即username 和 password)在 SealedSecret 中未被加密,只有这些键对应的值被加密。您可以根据需要在 SealedSecret YAML 文件中更改这些键的名称,并且仍能够将更改后的键成功部署到集群中。但是,您不能更改 SealedSecret 的 namenamespace,否则会使 SealedSecret 无效,因为原始 Secret 的名称和命名空间会在加密过程中用作输入参数。这种方式提供了额外的保护措施,即使用户获得了对 SealedSecret 的 YAML 清单的访问权限(此 SealedSecret 是为在用户无权访问的 Kubernetes 集群中的特定命名空间而创建),他们也无法仅编辑清单和将 SealedSecret 部署到不同的命名空间(包括同一集群中的不同命名空间)。

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  creationTimestamp: null
  name: database-credentials
  namespace: octank
spec:
  encryptedData:
    password: AgBIpx1gp2VsE0gcHzaFgXDdhA1hu0p+D4U1ys1FsQ1h0g4IRVVJqDUh56YRFBkj9bSt9dVXevRiO27mTPwyKXP6vnS2Oe/fM7x0LMQ1oXd6gTe2NB2o8r6ek3AenTghObRn1lbKigAHFtBFYN+Pq6wQptoewsaRhv+fTxRFk1PMjK5PHu/ETwsm74KeZ3VjOzEUkrAaRN2BPML0UvLstEy/yDMHD3+hLaGP/slwd5oeyhbVWutsofpyNNwLiZ5EvKhEr6vuCQ4CF0gvtFhXVF7e84m3SmZcZ1AvYicqW026PxjbL1b42T8Hk9PUboYfydSsBUjFAmXhzHLu+FrBuHIHdbikpd4kPOOqHO9z9wt39lwhiHf6f3YgesMFmVsWpco+ghn79fumfzTCrVHdwZGQ/7oYNDrcBNQB+kKQSOy+p3W2YR6BhhoGKHxELcqMZU9/1gKHIfbHPFoJtsK1SW2KwfpGeh0kvn/NtWj5sBeLrMYD9fev9j+jaDSOkzmB7ftpNx85DhOUVYJ2O/e4qUkzf0xASLq4XzwhknPOAbaRZ8oxBHQBKZKCW6x6RUpxOUXe7uImKEdodbDbCyrcIMH7a1SILohg5jpEDUUbxQBGtt7bFt4EJoF0LKLMDvzO/R02kHhuFiIRUwH2NJM8IpupwfLVdOBEBJ8yh4agmRvAm8bioH+bQELMQvc4DZHbiIOq0L2DlkzTyw==
    username: AgA5lKkkdSFgQbO8NXgEb/ohsWlUH5ngoxa4SAdQt/kN84eowrUev71KLnZsahJ1sDHr0GQHWW32hJlemG+GnD3ASSEjvJABWwMIdpeJzi5vEPtp5fNiHZq5pmlJGnJZxHRvVlLSOfhf473r9Sbo/dS3OUvzsDabnb6hQzSVMJjS6RYTQp6JnsmvjDrOGk0P7Gik6bdgNivqmxrEiddqkanSqjS+MXMsY2HYnECr5EG7QRawq2yWl6UVQRfVLjoB9n8MAGHELkIXo5aiH1MwYdH0jnPukWZrwIsz1BbeBW4jX9wjtjXpZKKxrywd6AOhoodL6YIRGhWvRXTswQfktAdk5Y7Gu7vlnQMIkeaKjd7sXwO+TgoW4T8WTwTY7LPe4vLNI87AX1ZWCmLUexX1YlmwNJUGPIP8+WfFlkQO2YkcOt8Imt5lJI13+CGAPl13RZ5jVVAsO8HnE4GaufWRDHglb1GOAtiwnKRYp5JS0ipzQZspH+tpACFIFKLqGykMPBoRoxEqqtdouYs1b8k2cAj7uhhWaibn3f0T1gYMMuAwu5FD/A1KX0YRtcBXab2VPDOxD0cLwwjimkAjWgXrzVjCHq9yn9CyCsd9Hie56lesphmk+/kiZ0fwr5T9UZnJQrL/REplDgUVnFdLZfdY9PFCeylAeTv6KUjHsmc6O6cooPwXKFhntGDHm4GGYz9T8cfQWI7NFA==
  template:
    metadata:
      creationTimestamp: null
      name: database-credentials
      namespace: octank
    type: Opaque
status: {}

此时已不再需要与该 Secret 有关的 YAML 清单,您可以将其删除。SealedSecret 将会是唯一被部署到集群的资源,如下所示:

kubectl create namespace octank
kubectl apply -f sealed-secret.yaml 

在集群中创建 SealedSecret CRD 后,控制器会检测到她并使用私钥解封底层 Secret,然后将其部署到控制器所在的命名空间。通过查看来自控制器的日志可以看到此过程:

2020/04/09 23:06:53 HTTP server serving on :8080
2020/04/09 23:19:16 Updating octank/database-credentials
2020/04/09 23:19:16 Event(v1.ObjectReference{Kind:"SealedSecret", Namespace:"octank", Name:"database-credentials", UID:"882f5e6c-7ab8-11ea-9ebe-1243fd9383c9", APIVersion:"bitnami.com/v1alpha1", ResourceVersion:"304045", FieldPath:""}): type: 'Normal' reason: 'Unsealed' SealedSecret unsealed successfully

您可以将与 SealedSecret 有关的 YAML 文件 sealed-secret.yaml 同与集群中部署的其他资源(例如 DaemonSet、Deployment 和 ConfigMap)有关的 YAML 清单一起安全地存储在 Git 存储库中。现在,我们可以使用 GitOps 工作流并安全地管理将 Secret 资源部署到集群的过程。

确保密封密钥的安全

如果没有由控制器管理的私钥,就无法在 SealedSecret 中对已加密的数据进行解密。如果您尝试还原集群的原始状态(例如在发生灾难后,或者您想利用 GitOps 工作流从 Git 存储库中部署包括 SealedSecrets 在内的相同 Kubernetes 资源集,并建立一个单独的 Kubernetes 集群实例),新集群中部署的控制器必须使用相同的私钥才能解封 SealedSecrets。如果没有此私钥,则必须使用新的私钥/公钥对重新生成所有 SealedSecrets,这对于包含大量 Secret 资源的部署可能会是一项繁重的任务。

可以使用以下命令从控制器中检索私钥。在生产环境中,通常会使用 Kubernetes RBAC 向一组受限的客户端授予执行此操作所需的权限。

kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > master.yaml

为了测试它的工作方式,我们首先删除以下项目:控制器的安装、控制器在包含私钥的 kube-system 命名空间中创建的 Secret、名为 database-credentials 的 SealedSecret 资源以及从该资源中解封的 Secret。具体方式如下:

kubectl delete secret database-credentials -n octank
kubectl delete sealedsecret database-credentials -n octank
kubectl delete secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key
kubectl delete -f controller.yaml 

接下来,我们使用 master.yaml 文件将包含私钥的 Secret 恢复到集群中:

kubectl apply -f master.yaml 
kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key

上述命令的输出结果中,我们可以看到使用 master.yaml 文件中的私钥在 kube-system 命名空间中创建的新 Secret:

NAME                      TYPE                DATA   AGE
sealed-secrets-keyhvdtf   kubernetes.io/tls   2      5s

接下来,我们将 SealedSecret CRD、控制器和 RBAC 构件重新部署到集群中。请注意,我们对先前生成的 SealedSecret 使用了相同的 YAML 清单:

kubectl apply -f controller.yaml
kubectl logs sealed-secrets-controller-6b7dcdc847-jkrz8 -n kube-system

如新控制器的日志中所示,新控制器能够在 kube-system 命名空间中找到现有的 Secret sealed-secrets-keyhvdtf,因此不会创建新的私钥/公钥对:

2020/04/09 23:26:07 Starting sealed-secrets controller version: v0.12.1+dirty
controller version: v0.12.1
2020/04/09 23:26:07 Searching for existing private keys
2020/04/09 23:26:07 ----- sealed-secrets-keyhvdtf
2020/04/09 23:26:07 HTTP server serving on :8080

接下来,我们来部署 SealedSecret,并验证控制器是否能够成功将其密封:

kubectl apply -f sealed-secret.yaml 
kubectl logs sealed-secrets-controller-6b7dcdc847-jkrz8 -n kube-system
2020/04/09 23:26:07 Starting sealed-secrets controller version: v0.12.1
controller version: v0.12.1
2020/04/09 23:26:07 Searching for existing private keys
2020/04/09 23:26:07 ----- sealed-secrets-keyhvdtf
2020/04/09 23:26:07 HTTP server serving on :8080
2020/04/09 23:29:33 Updating octank/database-credentials
2020/04/09 23:29:33 Event(v1.ObjectReference{Kind:"SealedSecret", Namespace:"octank", Name:"database-credentials", UID:"f7d22e88-7ab9-11ea-a485-0adbb667fc0b", APIVersion:"bitnami.com/v1alpha1", ResourceVersion:"305137", FieldPath:""}): type: 'Normal' reason: 'Unsealed' SealedSecret unsealed successfully

如果包含由控制器生成的公钥/私钥对的 master.yaml 文件遭到破坏,则用户就可以解封所有 SealedSecret 清单并查看其中存储的已加密敏感信息。因此,必须通过授予最低访问权限的方式来保护此文件。有关更新密封密钥、手动管理密封密钥等的其他指导,请参阅文档

保护私钥的一种方式是将 master.yaml 文件内容作为 SecureString 参数存储在 AWS Systems Manager 参数仓库中。您可以使用 AWS Key Management Service (KMS) 客户托管密钥 (CMK) 来保护该参数,并且您可以使用键策略来限制可使用此密钥检索参数的 AWS Identity and Access Management (IAM) 委托人集。此外,您还可以在 KMS 中对此 CMK 启用自动轮换。请注意,标准层参数支持最多 4096 个字符的参数值。鉴于 master.yaml 文件的大小,您必须将其作为参数存储在高级层中。

创建 Secret 资源后,Kubernetes API 服务器会将数据持久保存在作为 Kubernetes 控制平面一部分的 etcd 数据库中。默认行为是以 Base64 编码格式持久保存 Secret 数据。

使用 Kubernetes 1.13 或更高版本启动 Amazon EKS 集群时,您可以选择使用 AWS KMS 启用或禁用 Kubernetes 密钥的信封加密。如果启用信封加密,系统将使用您选择的客户主密钥 (CMK) 对 Kubernetes 密钥进行加密,然后将其存储在 etcd 数据库中,以此方式来遵循对静态数据加密的最佳安全实践。“Using EKS encryption provider support for defense-in-depth”中详细讨论了此功能。结合使用该策略和本文中讨论的策略,可以为用户提供一种用于管理部署 Kubernetes 工作负载所需的敏感数据的稳健机制。

图片精选自 Pixabay