👇👇 关注后回复 “进群” ,拉你进程序员交流群 👇👇 作者丨int32bit
来源丨int32bit
我们知道Kubernetes提供了Secret对象用于管理业务敏感数据,比如用户名密码、私钥等。Secret最终默认是存储在etcd中的,为了保证Secret的安全,需要保证两点:
(1)密钥的传输是安全的,这一点很容易做到,etcd配置使用TLS即可,整个传输过程均使用HTTPS协议。
(2)密钥的存储是安全的,这在Kubernetes v1.7也已经实现了,Kubernetes支持配置Secret使用多种加密方式存储,支持的加密算法如AES-CBC、AES-GCM等,在配置文件中支持自定义加密Key以及轮转,具体可以参考Encrypting Secret Data at Rest [1] 。
这看似已经完美解决了,但是对于Kubernetes平台这一侧来说,虽然Secret在etcd是加密存储的,但在平台侧看到的是解密的数据,即众所周知的Base64编码,当然只要控制好Secret的访问权限则可以实现保护数据的安全。
但是如果我们使用类似Git/SVN的版本控制工具来管理这些应用的Manifests yaml文件,或者使用类似GitOps的CD集成工具,这些Base64编码的Secret就很不安全了,总不会有人把Secret直接存储在Git或者SVN上吧。
通常的做法是Secret从Git仓库中分离出来由管理员管理,当应用部署时手动先创建这些Secret实例,这种需要人工干预的方式效率非常低,也违背了应用持续集成的高效率快敏捷的原则。
二、Bitnami SealedSecret方案 Bitnami提供了一种可行方案,其开源的SealedSecrets [2] 工具能支持对Kubernetes的Secret使用非对称加密算法加密并转化为新的CRD对象SealedSecret,SealedSecret由于是加密后的数据因此可以安全地push到代码仓库中,当应用部署时Kubernetes创建SealedSecret对象实例,控制器会监听SealedSecret对象实例状态,自动地对SealedSecret实例进行解密并映射为对应的Kubernetes Secret对象,相当于对Secret做了一层中间加密存储过程。
在Kubernetes集群中配置使用SealedSecret也很简单,只需要安装其控制器:
$ kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.16.0/controller.yaml
并安装加密客户端工具即可,该工具用于对Kubernetes原生Secret加密为SealedSecret对象:
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.16.0/kubeseal-linux-amd64 -O kubeseal
创建一个demo secret并通过kubeseal加密:
# kubectl create secret generic demo-secret \ --from-literal=username=int32bit \ --from-literal=password=NoMoreSecret \ -o yaml --dry-run=client \ | kubeseal -o yaml | tee demo-secret.yaml apiVersion: bitnami.com/v1alpha1 kind: SealedSecret metadata: name: demo-secret namespace: sealed-secrets spec: encryptedData: password: Ag...9A== # 非常长的加密数据 username: Ag...8C== # 非常长的加密数据 template: data: null metadata: name: demo-secret namespace: sealed-secrets
如上通过kubeseal加密Secret后生成了新的SealedSecret对象demo-secret.yaml
,可以安全地push到Git仓库中。
当应用部署时直接基于这个新的对象创建SealedSecret:
# kubectl apply -f demo-secret.yaml sealedsecret.bitnami.com/demo-secret created
SealedSecret Controller会自动创建对应的Secret:
# kubectl get sealedsecrets demo-secret -o yaml apiVersion: v1 data: password: Tm9Nb3JlU2VjcmV0 username: aW50MzJiaXQ= kind: Secret metadata: name: demo-secret namespace: sealed-secrets ownerReferences: - apiVersion: bitnami.com/v1alpha1 controller: true kind: SealedSecret name: demo-secret uid: 3273d7bb-9629-4fe2-bbfd-75631ce24a87 resourceVersion: "46585715" uid: 88eb269b-87f7-48a3-8032-65502648665a type: Opaque
从ownerReferences
可以看出这个Secret就是由SealedSecret自动创建的,应用不需要做任何修改完全无侵入就可以读取Secret值。
当然你也可以不用kubeseal工具加密,选择自己实现加密,只需要使用对应的公钥即可,公钥可以通过工具或者API获取:
kubeseal --fetch-cert | openssl x509 -text -noout
不过需要注意的是,SealedSecret使用非对称加密算法,我们知道公钥用于加密,私钥用于解密,而私钥是托管在部署SealedSecret的Kubernetes集群的,在另一个Kubernetes集群是没法解密的。这就导致无法实现跨多环境场景下做自动化集成,比如SIT、UAT、DEV环境如果用的是不同的Kubernetes集群,相对应的加密SealedSecret Manifest文件不一样,SIT的环境的SealedSecret无法在UAT环境中解密。
当然你可以把私钥导出来,再导入到另一个集群,即所有环境的Kubernetes集群共享使用相同的密钥对,这样解决了所有的集群都可以解密的问题。不过这样也存在非常大的私钥安全风险,一旦私钥泄露了,所有环境的Secret也会全部泄露。
三、Kamus方案 前面介绍的SealedSecret使用自己的私钥加密,与特定的集群关联在一起。实际生产环境中可能需要外部更安全专业的密钥管理工具,甚至使用硬件加密机。
如果使用Helm工具管理应用,可以考虑helm-secrets [3] 插件,支持集成AWS KMS、GCP KMS等密钥托管工具。
不过我更推荐Kamus [4] ,这也是一款针对Kubernetes Secret加密的开源工具,与SealedSecret不一样的主要有两点:
(1)提供多种Provider支持各种云上的密钥托管工具,比如Azure KeyVault、 Google Cloud KMS、AWS KMS等。 (2)遵循零信任模型,即只有关联了指定的ServiceAccount的应用可以解密,其他应用没有任何办法拿到解密数据,相当于做了一层严格的权限管控。这个和网络总讲的微分段零信任模型原则是一样的:) 如果在公有云场景,Kamus显然是非常适合的,能直接与公有云的KMS服务联动起来统一对密钥进行管理。
四、Vault方案 在自建私有云环境下,可能无法与公有云上的KMS服务交互,因此前面介绍的Kamus无法应用到私有环境中,可以考虑使用Hashicorp公司开源的Vault工具作为替代方案,Vault作为一款企业级密钥信息管理工具,提供了非常完善的工具和方法集成Kubernetes,实现了Kamus相同的功能,原理也是大体相同的。
关于Vault的相关介绍可以参考Vault官方文档 [5] ,这里不对Vault本身做太多的介绍。
如果是测试的话如下命令即可快速部署一个demo环境:
# helm repo add hashicorp https://helm.releases.hashicorp.com # helm repo update # cat helm-vault-values.yml csi: enabled: true injector: enabled: true server: dev: enabled: true # helm install vault hashicorp/vault --values helm-vault-values.yml
测试时直接使用root token认证,实际生产时应该创建严格管控的用户以及权限Policy策略。
写入测试数据如下:
kubectl exec -it vault-0 -- /bin/sh
vault secrets enable -path=internal kv-v2 vault kv put internal/database/config username="int32bit" password="db-secret-password"
此时可以通过vault CLI或者API获取写入的数据:
/ $ vault kv get internal/database/config ====== Metadata ====== Key Value --- ----- created_time 2021-06-08T10:20:12.21987623Z deletion_time n/a destroyed false version 2 ====== Data ====== Key Value --- ----- password db-secret-password username int32bit
此时Kubernetes应用可以通过vault API获取secret数据,建议通过initContainer完成Vault认证并获取数据写入到应用共享的volume中,应用直接从volume中读取数据。
不过这种方式虽然只需要InitContainer拥有Vault凭证,应用起来后InitContainer就销毁了,但还是意味着Vault凭证需要托管在Kubernetes中,这就是先有鸡还是先有蛋的问题,因此这种方式并不推荐在生产上使用,推荐的方法参考如下章节介绍。
4.1 Vault使用Kubernetes认证 前面介绍了直接读取Vault数据的方法有个问题,就是从vault中获取数据需要认证凭证(Vault的用户名密码或者token等),这个认证凭证又是敏感数据,虽然通过initContainer已经大大降低了vault认证信息泄露的风险,因为vault的认证信息只会存在initContainer中,而应用起来后initContainer就自动销毁了。不过存储在Kubernetes中还是不太安全的,万一泄露,则加密的数据自然不攻自破。
好在vault支持Kubernetes认证,这个初步听起来好像有点奇怪,Kubernetes作为vault的认证服务器?
是的,Vault借助了Kubernetes的TokenReviews功能,用于校验Token是否合法。以最简单的ServiceAccount为例,我们知道ServiceAccount是唯一由Kubernetes托管的用户认证实体,当然一般不用做自然人用户使用,而是给运行在Kubernetes的Pod应用使用,通过关联ServiceAccount给Pod授权再熟悉不过了,关于如何使用以及关联rolebingding/clusterrolebingding不再介绍,这里仅简单介绍下Kubernetes ServiceAccount认证的原理
其实ServiceAccount认证本质就是JWT Token认证,这个token会自动以volume的形式挂载到pod的/run/secrets/kubernetes.io/serviceaccount/token
路径上:
kubectl exec -t -i vault-0 -- cat /run/secrets/kubernetes.io/serviceaccount/token
这个JWT Token有三个部分内容构成,分别为头部元数据(加密算法、版本)、主体内容Body以及签名。
通过如下脚本可以解码头部元数据以及主体内容:
#!/bin/bash decode_base64_url () { LEN=$((${#1} % 4)) RESULT="$1 " if [ $LEN -eq 2 ]; then RESULT+='==' elif [ $LEN -eq 3 ]; then RESULT+='=' fi echo "$RESULT " | tr '_-' '/+' | base64 -d }# 解码JWT decode_jwt () { JWT_RAW=$1 for line in $(echo "$JWT_RAW " | awk -F '.' '{print $1,$2}' ); do RESULT=$(decode_base64_url "$line " ) echo "$RESULT " done } decode_jwt $@
解码后的内容如下:
{"alg" :"RS256" ,"kid" :"VnxYZVWpj5NIRNkn-mgm3kjPARoCriR6VocXAzINSi8" } { "iss" : "kubernetes/serviceaccount" , "kubernetes.io/serviceaccount/namespace" : "vault" , "kubernetes.io/serviceaccount/secret.name" : "vault-token-psh5d" , "kubernetes.io/serviceaccount/service-account.name" : "vault" , "kubernetes.io/serviceaccount/service-account.uid" : "a0865e8b-798d-4426-a3d2-87c475387e44" , "sub" : "system:serviceaccount:vault:vault" }
这其实和x509证书的数字签名非常相似,头部数据声明了加密算法和版本协议,主体内容包含了用户信息,包括uid、name等。需要注意的是头部元数据和主体内容是完全公开的,并没有加密,仅使用了类似Base64的编码,不需要任何公钥私钥谁都可以完全解码,所以千万不要把敏感数据放到JWT Token中。
而签名部分则是为了确保Token完整性以及真实性,保证两点:一是这个Token确定是可信任的Kubernetes颁发,二是这个Token没有被人篡改过。这个JWT Token是由kube-controller-manager生成颁发的,通过--service-account-private-key-file
参数指定颁发Token的私钥文件,如果Kubernetes使用kubeadm部署则默认路径为/etc/kubernetes/pki/sa.key
,公钥为sa.pub
。
这样校验这个Token是不是合法的只需要拿到私钥对应的公钥sa.pub
即可,如果签名一致则说明Token是合法的,如果内容被篡改过,则必然签名不一致。
ServiceAccount不仅可以用于给Pod做Kubernetes的API认证,还可以用于运行在Kubernetes的微服务应用之间的认证。打个比方假设你的应用A需要调用数据仓库B,但是B尚未实现认证,如何最快速的给B加上认证功能?我觉得最简单的办法就是利用Kubernetes的ServiceAccount,简易步骤如下:
(1)使用Kubernetes创建一个ServiceAccount db_admin
并关联到应用A对应的Deployment上。(2)A应用每次调用B的时候必须读取/run/secrets/kubernetes.io/serviceaccount/token
内容放到Header中并发送到B。(3)B校验这个ServiceAccount,解码JWT Token并校对是不是db_admin
,同时校验Token是否合法,方法有两个,一是使用公钥sa.pub
进行JWT Token校验,二是直接调用Kubernetes的tokenreviews。
learnk8s.io有一个很好的例子可以参考Authentication between microservices using Kubernetes identities [6]
甚至可以不用修改B的代码,通过Pod的Ambassador Pattern模式实现代理认证即可,参考Extending applications on Kubernetes with multi-container pods [7] 。
但是特别需要注意的是默认的ServiceAccount Token一旦创建就是永久有效,目前不支持自动轮转,手动轮转只能把ServiceAccount删了再重新创建,并且ServiceAccount不支持自定义audience从而做进一步的权限控制,换句话说只要关联了这个ServiceAccount就具有一样的权限。
社区很早就发现了这个问题,因此设计了ProjectedServiceAccountToken
,在实际生产中推荐使用ProjectedServiceAccountToken
,与ServiceAccount默认的Token最大的不同是支持设置TTL(过期时间)以及audience(颁发给谁)。
如下是一个使用ProjectedServiceAccountToken
的测试Pod,注意需要禁用默认ServiceAccount Token挂载,设置automountServiceAccountToken
为false
:
apiVersion: v1 kind: Pod metadata: labels: run: test name: test spec: serviceAccount: internal-app automountServiceAccountToken: false volumes: - name: api-token projected: sources: - serviceAccountToken: path: internal-app expirationSeconds: 600 audience: data-store containers: - image: nginx imagePullPolicy: IfNotPresent name: test volumeMounts: - mountPath: /var/run/secrets/tokens name: api-token dnsPolicy: ClusterFirst restartPolicy: Always
查看Token:
kubectl exec -t -i test -- cat /run/secrets/tokens/internal-app eyJhbGciOi...
解码后的内容如下:
{ "aud" : [ "data-store" ], "exp" : 1623318918
, "iat" : 1623318318 , "iss" : "https://kubernetes.default.svc.cluster.local" , "kubernetes.io" : { "namespace" : "vault" , "pod" : { "name" : "test" , "uid" : "f3f54b31-cc59-49cc-a080-9214d4c71a8a" }, "serviceaccount" : { "name" : "internal-app" , "uid" : "e9faced0-e429-4985-9595-7ff4eb48b1e6" } }, "nbf" : 1623318318 , "sub" : "system:serviceaccount:vault:internal-app" }
从原来的Token中我们发现明显多了iat、exp以及aud等字段,分别为颁发时间、过期时间以及audience。
在校验Token时除了校验Token的合法性,我们只需要再校验下exp是否过期以及aud字段是否为指定的用户即可。
更多Projected volume可以参考官方文档Kubernetes Volumes [8] 。
了解了ServiceAccount的如上认证原理,也就清楚了Vault是如何利用Kubernetes做认证的原理。
Vault开启Kubernetes认证也非常简单:
/ $ vault auth enable kubernetes Success! Enabled kubernetes auth method at: kubernetes/ / $ vault write auth/kubernetes/config \ > token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) " \ > kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR :443" \ > kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt Success! Data written to: auth/kubernetes/config
当然也可以通过Vault Web页面操作:
vault use k8s auth 我们给应用授权,进行严格权限管控,创建一个只读的Vault policy权限:
vault policy write internal-app - < path "internal/data/database/config" { capabilities = ["read" ] } EOF
关联Kubernetes的ServiceAccount internal-app
,即只允许internal-app
这个ServiceAccount读取如上Vault数据:
$ vault write auth/kubernetes/role/internal-app \ bound_service_account_names=internal-app \ bound_service_account_namespaces=default \ policies=internal-app \ ttl=24h Success! Data written to: auth/kubernetes/role/internal-app
这种方式不再需要写入静态的Vault用户名密码以及Token,并与Kubernetes认证关联起来,从而解决了Vault认证问题。
解决了认证问题,接下来解决如何读取Vault数据问题,Vault支持如下三种方式:
(1)通过InitContainer Agent注入; (3)通过Secrets Store CSI挂载。 4.2 通过InitContainer读取数据 这种方式原理比较简单,就是前面介绍的通过一个InitContainer读取ServiceAccount Token向Vault认证,认证后读取数据写入到共享的Volume中。
为了演示我们创建一个Demo应用,我们前面已经创建了Policy并关联internal-app ServiceAccount,这个Policy允许读取internal/data/database/config
数据。因此我们这个Demo Deployment只需要关联internal-app这个ServiceAccount就具有读权限。
这个应用通过initContainer获取用户名和密码并写入到一个emptyDir的Volume中:
apiVersion: apps/v1 kind: Deployment metadata: labels: app: demo-app name: demo-app spec: replicas: 1 selector: matchLabels: app: demo-app template: metadata:
labels: app: demo-app spec: serviceAccountName: internal-app initContainers: - image: curlimages/curl imagePullPolicy: IfNotPresent name: get-token volumeMounts: - name: secret mountPath: /secrets command: - /bin/sh - -c - | echo "Install jq ..." apk add jq -X https://mirrors.cloud.tencent.com/alpine/latest-stable/main TOKEN=$(curl -sSL -X POST \ -d "{\"jwt\": \"$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)\", \"role\": \"internal-app\"}" \ $VAULT_ADDR/v1/auth/kubernetes/login \ | jq -r '.auth.client_token') echo "Authenticate to vault with kubernetes serviceaccount ..." curl -H "X-Vault-Token: $TOKEN" $VAULT_ADDR/v1/internal/data/database/config >/tmp/data echo "Extract data from secret ..." jq -r '.data.data.username' /secrets/username jq -r '.data.data.password' /secrets/password echo "Done" env: - name: VAULT_ADDR value: http://vault:8200 securityContext: runAsUser: 0 # 需要root权限安装jq工具,否则无权限。 containers: - image: busybox imagePullPolicy: IfNotPresent name: demo-app volumeMounts: - name: secret mountPath: /secrets command: - /bin/sh - -c - 'while true; do echo "The username is $(cat /secrets/username) and the password is $(cat /secrets/password)" && sleep 5;done' volumes: - name: secret emptyDir: {}
状态为Running后查看应用日志:
# kubectl logs demo-app-fb65f6659-r4zxc The username is int32bit and the password is db-secret-password
如上输出表明Demo应用成功地获取到了用户名和密码。
如上我们是通过initContainer手动写的脚本调用Vault API获取数据,只是为了验证其原理,实际这么操作有点麻烦。其实官方已经提供了现成的Agent InitContainer自动认证并获取数据,并且支持基于模板渲染,推荐使用这种方式而不是自己写一个InitContainer。
首先定义Vault配置以及需要渲染的模板:
kind: ConfigMap metadata: name: example-vault-agent-config namespace: vault apiVersion: v1 data: vault-agent-config.hcl: | exit_after_auth = true pid_file = "/home/vault/pidfile" auto_auth { method "kubernetes" { mount_path = "auth/kubernetes" config = { role = "internal-app" } } sink "file" { config = { path = "/home/vault/.vault-token" } } } template { destination = "/etc/secrets/index.html" contents = < Some secrets:
{{- with secret "internal/data/database/config" }} {{ end }} EOT }
如上首先配置认证的路径以及角色,然后配置了渲染HTML模板,经典的Jinja2。
部署Demo应用如下:
apiVersion: v1 kind: Pod metadata: name: vault-agent-example namespace: vault spec: serviceAccountName: internal-app volumes:
- configMap: items: - key: vault-agent-config.hcl path: vault-agent-config.hcl name: example-vault-agent-config name: config - emptyDir: {} name: shared-data initContainers: - args: - agent - -config=/etc/vault/vault-agent-config.hcl - -log-level=debug env: - name: VAULT_ADDR value: http://vault:8200 image: vault imagePullPolicy: IfNotPresent name: vault-agent volumeMounts: - mountPath: /etc/vault name: config - mountPath: /etc/secrets name: shared-data containers: - image: nginx imagePullPolicy: IfNotPresent name: nginx-container ports: - containerPort: 80 volumeMounts: - mountPath: /usr/share/nginx/html name: shared-data
原理和前面的方式一样,都是通过InitContainer获取数据写入到Volume中,只是这个InitContainer替换使用为官方的镜像。
此时我们访问Demo nginx应用:
# curl $(kubectl get pod vault-agent-example -o jsonpath="{.status.podIP}")<html > <body > <p > Some secrets:p ><ul > <li ><pre > username: int32bitpre >li ><li ><pre > password: db-secret-passwordpre >li >ul >body >html >
可见应用成功获取到用户名和密码信息。
4.3 通过injector自动注入 前面介绍了通过InitContainer获取数据,并自动从Vault获取数据渲染模板。
Vault还支持通过injector自动注入Vault值,只需要通过注解配置路径以及模板即可:
apiVersion: apps/v1 kind: Deployment metadata: name: demo-injector-example labels: app: demo-injector-example spec: selector: matchLabels: app: demo-injector-example replicas: 1 template: metadata: annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "internal-app" vault.hashicorp.com/agent-inject-secret-database-config.txt: "internal/data/database/config" vault.hashicorp.com/agent-inject-template-database-config.txt: | {{- with secret "internal/data/database/config" -}}
username = {{ .Data.data.username}} and password = {{ .Data.data.password}} {{- end -}} labels: app: demo-injector-example spec: serviceAccountName: internal-app containers: - name: demo-app image: busybox imagePullPolicy: IfNotPresent command: - /bin/sh - -c - 'while true; do cat /vault/secrets/database-config.txt && echo && sleep 5;done'
如上通过注解指定了渲染的模板文件database-config.txt
以及相关配置,并没有手动指定InitContainer以及共享的Volume,但其实原理是一样的,vault-agent-injector
会自动给Pod添加上InitContainer以及共享volume。
通过如下命令可以查看注入的InitContainer:
# kubectl get pod demo-injector-example-65767b48f9-tdzct -o jsonpath='{.spec.initContainers[*].name}' vault-agent-init
查看注入的Volume如下:
# kubectl get pod demo-injector-example-65767b48f9-tdzct -o jsonpath='{.spec.volumes[*].name}' | tr ' ' '\n' internal-app-token-q2dxj home-init home-sidecar
其中/vault/secrets
就是共享的volume用于存储Vault值。
我们知道InitContainer运行完后就销毁了,这样当Vault数据更新时就无法更新Pod的数据了。
为了解决这个问题,Injector不仅注入了InitContainer还注入了一个SideCar容器,用于监听Vault数据变化并更新Vault值。
# kubectl logs demo-injector-example-65767b48f9-tdzct -f -c demo-app username = int32bit and password = db-secret-password username = int32bit and password = db-secret-password username = int32bit and password = new-password
如上当我们更新password时,demo输出了新的值。
4.4 通过Secrets Store CSI挂载 除了前面介绍的InitContainer以及Injector注入的方法,我们还可以使用Kubernetes社区的Secrets Store CSI驱动,这种方式与使用原生Secret方式比较类似,关于Secrets Store CSI Driver可以参考The Secrets Store CSI Driver [9] 。
使用Secret Store CSI方式需要安装CSI驱动:
helm install csi secrets-store-csi-driver/secrets-store-csi-driver
Secret Store CSI支持各种不同的Provider,如AWS、GCP、Azure以及我们要用的Vault,通过SecretProviderClass指定Provider并配置。
apiVersion: secrets-store.csi.x-k8s.io/v1alpha1 kind: SecretProviderClass metadata: name: vault-database spec: provider: vault secretObjects: - data: - key: password objectName: "db-password" secretName: mirror-secret type: Opaque parameters: vaultAddress: "http://vault.vault:8200" roleName: "internal-app" objects: | - objectName: "db-password" secretPath: "internal/data/database/config" secretKey: "password"
配置完后我们就能像原生Secret一样,通过volume挂载Secret,只是volume声明参数不一样而已,需要指定前面定义的secretProviderClass
。
apiVersion: apps/v1 kind: Deployment metadata: labels: app: demo name: demo spec: replicas: 1 selector: matchLabels: app: demo template: metadata: labels: app: demo spec: serviceAccountName: internal-app containers: - image: busybox imagePullPolicy: IfNotPresent name: webapp command: - /bin/sh - -c - 'while true; do echo $(date "+%Y-%m-%d %H:%M:%S"): The secret is $(cat /mnt/secrets-store/db-password); sleep 1;done' volumeMounts: - name: secrets-store-inline mountPath: "/mnt/secrets-store" readOnly: true volumes: - name: secrets-store-inline csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "vault-database"
volume中定义了使用secrets-store.csi.k8s.io
CSI驱动并指定了vault-database
,应用起来后我们可以验证是否正常获取数据:
# kubectl logs demo-6ffb6d6f-rfgvq 2021-06-15 01:44:34: The secret is new-password
回到前面SecretProviderClass
定义,我们还声明了mirror-secret,当Secret被引用时,会自动创建一个Kubernetes原生的Secret,这个Secret名为mirror-secret
:
# kubectl get secret mirror-secret -o yaml apiVersion: v1 data: password: bmV3LXBhc3N3b3Jk kind: Secret metadata: creationTimestamp: "2021-06-15T01:44:33Z" labels: secrets-store.csi.k8s.io/managed: "true" name: mirror-secret namespace: vault ownerReferences: - apiVersion: apps/v1 kind: ReplicaSet name: demo-6ffb6d6f uid: 94f14cc7-6a77-4356-9d80-02972b00ff64 resourceVersion: "51551831" uid: 5e50a1bd-84b1-4839-99a4-763230400e00 type: Opaque
但是需要注意是这个Secret只是为了给原生的Kubernetes工具所管理,因为我们很多工具如Kubernetes Dashboard并不支持SecretProviderClass资源的管理,因此创建对应的原生Secret方便实现Secret统一纳管。
我们从ownerReferences
看出这个Secret并不是SecretProviderClass创建的,而是ReplicaSet
,换句话说,只有当应用部署时引用这个SecretProviderClass才会创建这个原生Secret,当应用删除时,这个Secret将会同时被移除。
与Injector模式一样,使用Secret Store CSI方式挂载Vault,也支持自动更新,通过--rotation-poll-interval 参数可以指定轮询参数变化频率。
五、结论 本文首先介绍了Kubernetes Secret加密存储的必要性,然后分别介绍了Bitnami 的SealedSecret方案、开源的Kamus方案以及开源的Vault方案,其中单集群环境下SealedSecret方案是最简单的,而Kamus适合公有云环境下,与公有云KMS服务联动。Vault非常适合在私有环境下使用,提供与Kubernetes Secret集成的方法也比较多,可以从中选择一项适合企业管理的即可。
参考资料 [1] Encrypting Secret Data at Rest: https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/
[2] SealedSecrets: https://github.com/bitnami-labs/sealed-secrets
[3] helm-secrets: https://github.com/jkroepke/helm-secrets
[4] Kamus: https://github.com/Soluto/kamus
[5] Vault官方文档: https://learn.hashicorp.com/collections/vault/kubernetes
[6] Authentication between microservices using Kubernetes identities: https://learnk8s.io/microservices-authentication-kubernetes#:~:text=The%20Kubernetes%20API%20verifies%20Service%20Account%20identities.%20In,valid%20or%20not%20%E2%80%94%20yes%2C%20it%27s%20that%20simple.
[7] Extending applications on Kubernetes with multi-container pods: https://learnk8s.io/sidecar-containers-patterns
[8] Kubernetes Volumes: https://kubernetes.io/docs/concepts/storage/volumes/#projected
[9] The Secrets Store CSI Driver: https://secrets-store-csi-driver.sigs.k8s.io/getting-started/usage.html