文章目录
- 简介
- GitOps 密钥管理方案
- 安装 Sealed-Secrets
- 安装 CLI 工具
- 安装 Controller 控制器
- 示例应用介绍
- 创建 ArgoCD 应用
- 创建加密后的 Secret 对象
- 创建 Image Pull Secret 对象
- 创建 Secret 对象
- 推送到代码仓库
- 验证 Secret
- 原理解析
- 核心关注点
简介
在实际的业务场景下,出于安全需要,Git 仓库往往会包含下面这些机密信息。
- 镜像拉取凭据的 Secret 对象,它可以为集群提供拉取镜像的权限。
- 外部数据库连接信息。
- 外部中间件如 MQ 连接信息。
- 第三方服务的 API KEY,例如云厂商和短信服务商。
在 GitOps 工作流中,这些机密信息都会通过 Kubernetes 对象存放在 Git 仓库,在大部分情况下它们是 Configmap 或 Secret 对象。虽然 Secret 被设计为存储 Kubernetes 的机密信息,但它只是 Base64 编码后的结果,不具备加密性质,这也就意味着机密信息完全是以明文的方式暴露的,这是非常不安全的。所以,我们有必要对这些机密信息进行加密。本次主要来学习加密 Git 仓库中机密信息的方法,进一步提升 GitOps 的安全性。
GitOps 密钥管理方案
在 GitOps 工作流中,常用的密钥管理方案有下面三种。
- Sealed-Secrets
- External-Secrets
- Vault
其中,
Sealed-Secrets
在易用性方面有比较大的优势,社区活跃度也比较高,也更容易和 GitOps 工作流结合。它的工作原理也非常简单,它利用非对称加密算法对 Secret 对象进行加密,使用的时候在集群内自动进行解密,这样就可以将加密后的密钥安全地存储在 Git 仓库中。
External-Secrets
需要外部密钥管理服务的支持,例如AWS Secrets Manager
、Google Secrets Manager
、Azure Key Vault
等,如果在你的项目里用到了这些密钥管理服务,可以考虑使用它。
Vault 是 HashiCorp 开源的一款密钥管理工具,要将它和 ArgoCD 结合使用需要额外的插件,配置起来比较繁琐。
安装 Sealed-Secrets
Sealed-Secrets 分为两部分,本地的 CLI 工具和运行在集群的控制器,在使用之前我们需要先安装它们。
安装 CLI 工具
kubeseal 命令行工具是本地和集群 Sealed-Secrets 服务交互的工具,我们需要用它来加密机密信息。
Mac:
$ brew install kubeseal
Linux:
https://github.com/bitnami-labs/sealed-secrets/releases
安装 Controller 控制器
Sealed-Secrets 控制器负责对加密的信息进行解密,并生成 Kubernetes 原生的 Secret 对象。以 Helm 方式安装。
~# helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
"sealed-secrets" has been added to your repositories
$ helm install sealed-secrets -n kube-system --set-string fullnameOverride=sealed-secrets-controller sealed-secrets/sealed-secrets
示例应用介绍
Deployment资源如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-spring
spec:
......
spec:
imagePullSecrets:
- name: github-regcred
containers:
- name: sample-spring
image: ghcr.io/lyzhang1999/sample-kotlin-spring:latest
ports:
- containerPort: 8080
name: http
env:
- name: PASSWORD
valueFrom:
secretKeyRef:
key: password
name: sample-secret
示例应用镜像存放到了 GitHub Package 仓库,也就是域名为 ghcr.io 的镜像仓库中,同时设置了私有仓库类型。在没有向 Kubernetes 集群提供拉取凭据的情况下,是无法拉取镜像的,这意味着直接将这个工作负载部署到集群将得到 ImagePullBackOff 的事件。
imagePullSecret
是镜像拉取凭据,这个凭据需要通过kubeseal
创建。
为工作负载配置了 Env 环境变量,它的值来源于名为sample-secret
的Secret
对象。这个 Secret 也需要通过kubeseal
来创建。
创建 ArgoCD 应用
修改 sealed-secret/application.yaml 文件
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: spring-demo
spec:
project: default
source:
# 替换为你的 Git 仓库地址,注意将仓库权限设置为公开,并使用 https 协议
repoURL: https://github.com/xxx/kubernetes-example.git
$ cd sealed-secret
$ kubectl apply -f application.yaml
通过ArgoCD控制台可以看到我们的应用其实是不健康的,是因为没有镜像凭证导致!
创建加密后的 Secret 对象
我们对两种 Secret 对象进行加密,它们分别是镜像拉取凭据和普通 Secret 对象。然后将加密后的 Secret 对象推送到 Git 仓库中,以便 ArgoCD 将它一并部署到集群内。
创建 Image Pull Secret 对象
cat sealed-secret/image-pull-secret.yaml
kind: Secret
type: kubernetes.io/dockerconfigjson
apiVersion: v1
metadata:
name: github-regcred
data:
.dockerconfigjson: xxxxxxxxxjpU1T1RrNloyaPV2QzWTBWRlRWSmpWbIRQTlROeWRVOXN
创建加密后的 Secret 对象
$ kubeseal -f image-pull-secret.yaml -w manifest/image-pull-sealed-secret.yaml --scope cluster-wide
-f 参数
指定了原始 Sceret 对象文件,也就是image-pull-secret.yaml
。-w 参数
表示将加密后的 Secret 对象写入 manifest 目录的image-pull-sealed-secret.yaml
文件内,这样 ArgoCD 就可以将它一并部署到集群内。–scope 参数
表示加密后的 Secret 对象可以在集群的任何命名空间下使用.
从以下内容可以看出,原始的 Secret 对象被转化为了 SealedSecret 对象,并且增加了 encryptedData 字段,它可以存放加密后的内容。
cat manifest/image-pull-sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
annotations:
sealedsecrets.bitnami.com/cluster-wide: "true"
creationTimestamp: null
name: github-regcred
spec:
encryptedData:
.dockerconfigjson: AgBgEUOxC1i2AuZJ2LzPiSfYblycy71NGv1SapA46ugFlWyKRaUg+WQGDHr6W6m+/8mBPvDuKh40xrszBEeaN212qNbbyb87tb1fZ8v9g7DmcsYp5I3VBSQ+9sljoXlf8XmTyGnohl6ZV5i79muSzhmJhNJAofOGVX4O52RvGjP8P9LvLYS7rlV/Nv49F5tnJqaEtZbYlxpQ5WggFFyOZ+LSaR/wkS0anOW/k6ZU/KHWijnvBKl/YRBbXsPHnyJpkFmGhN8hvZkaUZYpRZ+mbkdYMPw6HAgUMiyMWnbbzRBheJmiFafKV9RRfqfZoTaHubLIXdpFRrHdRS6SojYUuJFrVTM9xXRdpadC9T0cRCwvKGGGRVbNosOWhPtB2DkwzptQOL+6KMAlBHFrOKdkVULKVveJV269X85NcQDH40ZZMuCTMPIItC8hs6pqOheQ0SvaYrVri1GkEXovUYbNArhnUPnUuUf/zMTbQ5sYOGb20ST1HbBJqiTvIn54N22tg0ANhTaRSuQoW7yxd7ZGno2xNiyoIYk/6r7m3rRUtmBXR8+VD1bmuandH+Bpb4rnYDmZUSEFuhXm/d/szgoaE+s6b/RHhml7WsaPXQEmOInaoe3WvwZvTa9htLKJq2XzHkPMHa5H4vPZ4+1MyM13o1R8GLYuwI5gFqsyDfnLRQ2bXMbAwiSFkhQ947RpXHmG0Y29opLeNnjDt93gGFfo20wIYwl5YhOALpV3K5vKL1gAmRq1urAtDGSnCZkrMQKbEtQUKPJrzgmftAanzScKyVrFkQ8lG7CBv9xt42acvYJL0gIyVUKdXFay6qN4/GyYx4lQvLYOAMctkafluI2EZQweasetM8g2js+uAUJn1+WtUqtE2Tljd+avc7sJwWpEZfpW2BpcXAOGC4pLxLVKjm8EKLTru4vi5TOF0bfOvZJGBnEFuZQMYpme
template:
metadata:
annotations:
sealedsecrets.bitnami.com/cluster-wide: "true"
creationTimestamp: null
name: github-regcred
type: kubernetes.io/dockerconfigjson
创建 Secret 对象
cat sample-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: sample-secret
data:
password: YWRtaW4K
$ kubeseal -f sample-secret.yaml -w manifest/sample-sealed-secret.yaml --scope cluster-wide
运行命令后,在 manifest 目录下生成 sample-sealed-secret.yaml 文件,它包含加密后的 Secret 内容。
推送到代码仓库
$ git add .
$ git commit -a -m 'add secret'
$ git push origin main
进入 ArgoCD 控制台的应用详情,手动点击 “SYCN”按钮同步刚才新增的 Secret 对象。现在可以看到应用状态变成了 Healthy 健康状态,并且 Sealed-Secret 控制器对刚才创建的 SealedSecret 对象进行了解密,还重新创建了原始的 Kubernetes Secret 对象以供 Deployment 工作负载使用,如下图所示:
验证 Secret
临时配置应用端口转发:
$ kubectl port-forward svc/sample-spring 8081:8080 -n secret-demo
$ curl http://localhost:8081/actuator/env/PASSWORD
{"property":{"source":"systemEnvironment","value":"******"},"activeProfiles":[],"propertySources":[{"name":"server.ports"},{"name":"servletConfigInitParams"},{"name":"servletContextInitParams"},{"name":"systemProperties"},{"name":"systemEnvironment","property":{"value":"******","origin":"System Environment Property \"PASSWORD\""}},{"name":"random"},{"name":"Config resource 'class path resource [application.yml]' via location 'optional:classpath:/'"},{"name":"Management Server"}]}
从返回结果可以发现,应用已经成功获取到了 PASSWORD 环境变量,这说明 Sealed-Secret 控制器也已经生成了原始的 Secret 对象。
通过以上演示,在 GitOps 工作流中,当我们在集群内安装了Sealed-Secret 控制器
之后,只需要在 Git 仓库存储加密后的SealedSecret
对象即可,不再需要存储原始的Kubernetes Secret
对象了。
原理解析
按照架构图上的序号来依次理解每一个步骤。
由于加解密过程涉及到非对称加密,所以当我们在集群内安装 Sealed-Secret 控制器时,控制器会在集群范围内查找私钥/公钥对,如果没找到,则会生成一个新的 RSA 密钥对,并存储在部署 Sealed-Secret 命名空间下的 Secret 对象中,通过命令来查看:
$ kubectl get secret -n kube-system
NAME TYPE DATA AGE
sealed-secrets-keyj78wj kubernetes.io/tls 2 131m
上面输出的结果中,以 sealed-secrets 开头的 Secret 对象就是存储 RSA 密钥的对象。
要获取 RSA 密钥,可以进一步查看:
$ kubectl get secret sealed-secrets-keyj78wj -n kube-system -o yaml
当我们在本地使用 kubeseal 加密一个 Secret 对象时,kubeseal 会从集群内下载 RSA 公钥,并使用它来加密 Secret 对象,然后生成加密后的 SealedSecret CRD 资源,也就是 SealedSecret 对象。
当集群内控制器监听到有新的 SealedSecret 对象被部署时,它会使用集群内的 RSA 私钥来解密信息,并且在集群内重新生成 Secret 对象,以便提供给工作负载使用。
核心关注点
1.由于
Sealed-Secret
的加解密过程需要使用 RSA 密钥对,而 RSA 密钥对又是在部署控制器时自动生成的,所以,你需要额外留意存储 RSA 密钥的 Kubernetes Secret 对象。
2.尤其是在需要进行集群迁移时, 需要对它进行备份,如果遗失,将无法对 Git 仓库存储的 SealedSecret 对象解密,你需要重新从原始的 Secret 对象那里生成加密后的 SealedSecret 对象。
3.要对已有的 RSA 密钥进行备份,首先你可以导出存储它的 Secret 对象,并保存为 backup-sealed-secret-rsa.yaml 文件。
4.此外,出于安全考虑,Sealed-Secret 控制器默认每 30 天会生成新的 RSA 密钥对,但旧的 RSA 密钥对并不会被删除,所以通过旧的 RSA 密钥加密的 Secret 对象依然可以被解密。
$ kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > backup-sealed-secret-rsa.yaml
$ kubectl apply -f backup-sealed-secret-rsa.yaml
接着部署 Sealed-Secret 控制器。 此时,因为 kube-system 命名空间下已存在 RSA 密钥对,Sealed-Secret 会默认使用它作为解密的密钥,这样就完成了集群的迁移工作。