为何要加密?
在Kubernetes中,Secret是用来帮我们存储敏感信息的,比如密码、证书等,但是在默认的情况下,Secret只是做了简单的base64编码,任何人都可以非常容易的对其进行解密获取到原始数据。
比如通过以下方法生成一个secret对象:
$ echo -n "coolops" | kubectl create secret generic mysecret --dry-run --from-file=secret=/dev/stdin -o yaml > secret.yaml $ cat secret.yaml apiVersion: v1 data: secret: Y29vbG9wcw== kind: Secret metadata: creationTimestamp: null name: mysecret
其他人只要拿到secret的值,就可以对其进行解密获取到真实数据,如下:
$ echo "Y29vbG9wcw==" | base64 -d coolops
这样就非常的不安全,有点"掩耳盗铃"的意思。
在Kubernetes集群中,Etcd是集群数据库,存储着集群所以的资源数据,其中也包括Secrets,所以拿下了这个数据库就等于拿下了整个集群,所以在生产环境中对其进行加密是非常有必要的。
如何进行加密?
静态加密
kubernetes 1.13版本之后,提供静态加密方式,其主要是通过kube-apiserver来控制Secrets的加解密,而在Etcd中存储的是加密后的信息,所以攻击者拿下了etcd,也无法轻易的拿到Secrets保存的敏感数据。
!! 当前集群是使用kubeadm安装,版本1.18.9
(1)创建加密配置文件,保存到master节点/etc/kubernetes/pki/static-secret-encryption.yaml中
apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets providers: - aescbc: keys: - name: mysecret secret: DJqYaMMpY2DNlHz+HYrFYOUSh5SXKWiVOwLf6nQX9ss= - identity: {}
其中secret是加密密钥,使用如下命令生成:
$ head -c 32 /dev/urandom | base64
(2)修改kube-apiserver启动参数,位置/etc/kubernetes/manifests/kube-apiserver.yaml
...... spec: containers: - command: - kube-apiserver - --encryption-provider-config=/etc/kubernetes/pki/static-secret-encryption.yaml ......
!! 注意:kube-apiserver的加密参数,在1.14之后是--encryption-provider-config
(3)重启kube-apiserver
(4)验证加密
首先创建一个secret资源,如下:
$ kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
然后查看etcd中的数据
$ ETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key get /registry/secrets/default/secret1 | hexdump -C 00000000 2f 72 65 67 69 73 74 72 79 2f 73 65 63 72 65 74 |/registry/secret| 00000010 73 2f 64 65 66 61 75 6c 74 2f 73 65 63 72 65 74 |s/default/secret| 00000020 31 0a 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 63 |1.k8s:enc:aescbc| 00000030 3a 76 31 3a 6d 79 73 65 63 72 65 74 3a b6 90 7d |:v1:mysecret:..}| 00000040 b3 40 9b 4d 63 eb 71 cf ca 16 52 a0 91 82 c3 69 |.@.Mc.q...R....i| 00000050 2b 6f e5 1c ae 88 58 0d 4f 08 c9 29 57 69 f5 e6 |+o....X.O..)Wi..| 00000060 e2 7c 42 79 bb 84 22 3a 90 54 5e d4 ac f6 a6 a6 |.|By..":.T^.....| 00000070 47 e2 b2 67 29 d8 c4 c6 61 9e 84 62 9d 3c 7c b0 |G..g)...a..b.<|.| 00000080 6c 5f 5e f3 da 08 34 17 ef a3 a7 9c 76 31 02 98 |l_^...4.....v1..| 00000090 54 5c 21 05 af 8d a8 dc 39 04 4d 84 bf a8 d1 f6 |T\!.....9.M.....| 000000a0 58 f4 90 30 22 46 14 a5 e6 19 3a 51 48 86 99 a7 |X..0"F....:QH...| 000000b0 ed f1 5f 8e 4a 1c 30 cb 5f ec ba 3d e2 0a 1d 93 |.._.J.0._..=....| 000000c0 7c 57 68 6b d2 01 51 49 fd 81 56 72 6d ca 98 e6 ||Whk..QI..Vrm...| 000000d0 99 59 84 15 bc 5d 7d f7 95 75 b2 cb 4f ff 8d d1 |.Y...]}..u..O...| 000000e0 ae 29 0d 27 df fa 59 b4 e2 37 2c 33 83 9e e4 73 |.).'..Y..7,3...s| 000000f0 55 ce 89 cc c0 5f 3d e4 df 90 8d 70 91 f9 81 b1 |U...._=....p....| 00000100 e7 0c ee 71 cf 81 22 6f 6c 45 74 51 0c f7 5f 4d |...q.."olEtQ.._M| 00000110 1f 9a be 51 05 cd fd b2 74 0b 29 30 e2 24 ea 57 |...Q....t.)0.$.W| 00000120 0e 8a cb 1f 55 7a 2e 4c 0e 1a 4e 09 c6 0a |....Uz.L..N...| 0000012e
注意在数据头部出现k8s:enc:aescbc:v1:
,说明数据已经被正确加密,使用的是aescbc
算法,使用的密钥为mysecret
。
接下来查看读取的时候能否正常被解密,如下:
$ kubectl get secrets secret1 -o yaml apiVersion: v1 data: mykey: bXlkYXRh kind: Secret metadata: creationTimestamp: "2021-01-22T02:27:48Z" managedFields: - apiVersion: v1 fieldsType: FieldsV1 fieldsV1: f:data: .: {} f:mykey: {} f:type: {} manager: kubectl operation: Update time: "2021-01-22T02:27:48Z" name: secret1 namespace: default resourceVersion: "26907503" selfLink: /api/v1/namespaces/default/secrets/secret1 uid: 9020c914-3785-404f-a7b2-0743ff49c19d type: Opaque $ echo "bXlkYXRh" | base64 -d mydata
可以发现能否正常解密。
不知道你有没有发现,只有存在etcd里的数据被加密了,我们在集群使用kubectl get secrets secret1 -o yaml
获取到的仍然只是简单的进行了base64转码,所以一旦我们的节点被攻破,secrets也就暴露在外面了。
密钥管理服务KMS plugin
KMS 加密驱动使用封套加密模型来加密 etcd 中的数据。数据使用数据加密秘钥(DEK)加密;每次加密都生成一个新的 DEK。这些 DEK 经一个秘钥加密秘钥(KEK)加密后在一个远端的 KMS 中存储和管理。KMS 驱动使用 gRPC 与一个特定的 KMS 插件通信。这个 KMS 插件作为一个 gRPC 服务器被部署在 Kubernetes 主服务器的同一个主机上,负责与远端 KMS 的通信。
现在基本云厂商都提供KMS服务。这里以阿里云为例。
(1)开通密钥管理服务
(2)创建密钥
(3)然后部署kms-plugin
apiVersion: apps/v1 kind: Deployment metadata: name: ack-kms-plugin namespace: kube-system spec: selector: matchLabels: name: ack-kms-plugin template: metadata: labels: name: ack-kms-plugin spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - preference: {} weight: 100 requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node operator: In values: - master restartPolicy: Always tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule volumes: - name: kmssocket hostPath: path: /var/run/kmsplugin type: DirectoryOrCreate containers: - name: ack-kms-plugin image: registry.{{ .Region }}.aliyuncs.com/acs/ack-kms-plugin:v1.0.2 imagePullPolicy: Always command: - ack-kms-plugin - --gloglevel=5 - --key-id={{ .KeyId }} - --path-to-unix-socket=/var/run/kmsplugin/grpc.sock livenessProbe: exec: command: - ack-kms-plugin - health - --path-to-unix-socket=/var/run/kmsplugin/grpc.sock initialDelaySeconds: 30 failureThreshold: 3 timeoutSeconds: 5 periodSeconds: 300 env: - name: ACCESS_KEY_ID #not required if you want plugin help to pull the sts credentials value: {{ .AK }} - name: ACCESS_KEY_SECRET #not required if you want plugin help to pull the sts credentials value: {{ .AK_Secret }} - name: CREDENTIAL_INTERVAL #not required if you want plugin help to pull the sts credentials value: {{ .Credential_Interval }} volumeMounts: - name: kmssocket mountPath: /var/run/kmsplugin readOnly: false
其中需要更改的地方:
- {{ .Region }}:阿里巴巴云区域 ID, 如果您的群集部署在 ECS 上, 您可以通过curl http://100.100.100.200/latest/meta-data/region-id
- {{ .KeyId }}:Kms 服务列表中用于秘密加密的阿里云 Kms 密钥 ID
- {{ .AK }}:授权的角色ID
- {{ .AK_Secret }}:授权的角色密钥
- {{ .Credential_Interval }}:凭据轮询间隔
(4)创建kms插件配置文件/etc/kubernetes/kmsplugin/encryptionconfig.yaml
apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets providers: - kms: name: grpc-kms-provider endpoint: unix:///var/run/kmsplugin/grpc.sock cachesize: 1000 timeout: 3s - identity: {}
(5)修改kube-apiserver配置清单文件/etc/kubernetes/manifests/kube-apiserver.yaml
在启动参数里加入:
...... spec: containers: - command: - kube-apiserver - --encryption-provider-config=/etc/kubernetes/kmsplugin/encryptionconfig.yaml ......
然后再配置挂载,如下:
... volumeMounts: - name: kms-sock mountPath: /var/run/kmsplugin - name: kms-config mountPath: /etc/kubernetes/kmsplugin ... volumes: - name: kms-sock hostPath: path: /var/run/kmsplugin - name: kms-config hostPath: path: /etc/kubernetes/kmsplugin
(6)重启kube-apiserver
(7)验证
现在,群集应使用信封加密方案,使用阿里云 KMS 的给定密钥加密密钥 (KEK) 对 中的秘密进行加密
1. 创建新机密
$ kubectl create secret generic secret1 -n default --from-literal=mykey=mydata
2. 使用 etcdtl,读取主节点中的 etcd中的机密:
ps:[.local-ip] 应替换为主节点 IP 之一。
$ ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.pem --cert=/etc/kubernetes/pki/etcd/etcd-client.pem --key=/etc/kubernetes/pki/etcd/etcd-client-key.pem --endpoints=https://{{.local-ip}}:2379 get /registry/secrets/default/secret1
3. 验证存储的机密是否前缀,指示我们的 kms 提供商已加密生成的数据。
k8s:enc:kms:v1:grpc-kms-provider
4. 验证密钥是否已正确解密:
$ kubectl get secrets secret1 -o yaml
使用第三方插件
还可以使用其他插件,比如sealed-secrets和vault。