微服务开发实战

使用加密插件加密secrets中的数据

2019-08-17  本文已影响51人  暴走的初号机

众所周知,kubernetes中提供了一种名为secrets的对象,用于存放集群内部使用的各类敏感数据,比如数据库用户名、密码、各种token、证书等等,从而使得敏感信息和普通配置文件有效解耦。但是默认情况下secrets信息在etcd中是以base64编码形式保存的明文,本篇文章说明如何通过插件加密存储机密数据。

加密插件配置

总体来说配置比较简单,跟着官网的说明做就ok。官方连接:https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/。这里面值得注意的是,kube-apisever的加密插件配置参数为--encryption-provider-config,在1.13版本之前是--experimental-encryption-provider-config,该参数在1.14版本之后已经被正式废弃。

配置文件示例:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
    - secrets
    providers:
    - identity: {}
    - aesgcm:
        keys:
        - name: key1
          secret: c2VjcmV0IGlzIHNlY3VyZQ==
        - name: key2
          secret: dGhpcyBpcyBwYXNzd29yZA==
    - aescbc:
        keys:
        - name: key1
          secret: c2VjcmV0IGlzIHNlY3VyZQ==
        - name: key2
          secret: dGhpcyBpcyBwYXNzd29yZA==
    - secretbox:
        keys:
        - name: key1
          secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=

其中resources可以是多组独立的配置,每组配置下定义了该组资源的加解密的策略,比如这个配置文件定义了secrets资源的加解密策略。providers定义了加解密的实际提供者,目前k8支持的provider如下所示:

名称 加密类型 强度 速度 密钥长度 其它事项
identity N/A N/A N/A 不加密写入的资源。当设置为第一个 provider 时,资源将在新值写入时被解密。
aescbc 填充 PKCS#7 的 AES-CBC 最强 32字节 建议使用的加密项,但可能比 secretbox 稍微慢一些。
secretbox XSalsa20 和 Poly1305 更快 32字节 较新的标准,在需要高度评审的环境中可能不被接受。
aesgcm 带有随机数的 AES-GCM 必须每 200k 写入一次 最快 16, 24, 或者 32字节 建议不要使用,除非实施了自动密钥循环方案。
kms 使用信封加密方案:数据使用带有 PKCS#7 填充的 AES-CBC 通过 data encryption keys(DEK)加密,DEK 根据 Key Management Service(KMS)中的配置通过 key encryption keys(KEK)加密 最强 32字节 建议使用第三方工具进行密钥管理。为每个加密生成新的 DEK,并由用户控制 KEK 轮换来简化密钥轮换。配置 KMS 提供程序

其中identity就是明文,不加密。其余就是各类加解密算法,建议使用aescbc,足够用了,其实就是使用CBC模式、PKCS#7填充的aes256加密。这里要注意的是,providers中可以设置多个加密provider,每个provider可以设置多个加密的密钥。

加解密规则

kube-apiserver配置

到这里按照常规流程你一定想kubectl create -f来创建这个资源了,如果你这么做了,不出意外的话会看到如下的报错:

error: unable to recognize "encrypt.conf": no matches for kind "EncryptionConfiguration" in version "apiserver.config.k8s.io/v1"

这是因为,kube-apiserver的相关资源,是不能通过kubectl命令来创建的,官方文档并没有明确说明,其实也很好理解,自己怎么创建自己嘛!这个资源,只能是通过配置启动参数在kube-apisever启动的时候来加载。这边我使用kubeadm安装的集群,配置文件位置在/etc/kubernetes/manifests,找到kube-apiserver.yaml,这个就是kube-apiserver启动用的配置文件(用其他方式安装的也类似,只要找到这个配置文件就可以)。所以说这个加密插件的启动,目前来说貌似只能在私有集群中实现,如果你用的是gke、ake、tke这样的云服务商提供的集群就不行了。

tips: kube-apiserver这个pod和普通pod不同,是一个静态pod(static pod),也就是直接启动在特定的node上,由该宿主机的kubelet直接控制。pod不会漂移,配置文件也在宿主机上面,kubelet在启动时,通过读取/etc/kubernetes/manifests里面的配置信息,直接拉起pod。事实上如果你使用kubeadm,则会安装4个静态pod,分别是etcd、kube-apiserver、kube-scheduler、kube-controller-manager。不难看出这些就是保障k8集群正常运作的核心组件,其他插件诸如core-dns、kube-proxy、calico等都是以daemonset或者deployment形式运行的普通pod。在修改了/etc/kubernetes/manifests里的配置信息后,kubelet会自动重启该静态pod。

我们在这个配置文件中加入如下信息:

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --encryption-provider-config=/etc/kubernetes/pki/encrypt.conf
    - --advertise-address=192.168.31.241
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
.....

其中encryption-provider-config就是配置插件插件启动时读取的配置文件所在位置,这边我们把前面写的配置文件命名为encrypt.conf,放在/etc/kubernetes/pki目录下,通过阅读配置文件,我们可以看到kube-apiserver在启动的时候会挂载三个宿主机目录,其中就有/etc/kubernetes/pki。所以你把配置文件放在这里,kube-apiserver启动的时候就能正确找到这个配置文件了。

加密功能验证

kube-apisever重启后,我们就可以尝试一下,看看加密功能是否生效。这里我们用到的配置文件如下:

kind: EncryptionConfiguration
apiVersion: apiserver.config.k8s.io/v1
resources:
  - resources:
    - secrets
    providers:
    - aescbc:
        keys:
        - name: key1
          secret: SSM5rRRrQ9+8MsA2cHeRfb7KG9rvF/wsqHOgoQAv5bM=
    - identity: {}

这个配置保证了我们新建的secrets资源都会默认使用aescbc加密算法,并且使用key1中定义的这个密钥来加密数据。注意identity这个参数必须要设置,否则所有我们之前建立的secrets都会无法访问。具体原因其实上面已经讲过,大家可以想一下为什么。

现在我们来新建一个serctes资源来验证一下

kubectl create secret generic secret1 -n default --from-literal=mykey=mydata

之后登陆入容器etcd中

kubectl exec -it etcd-miwifi-r1cm-srv -n kube-system /bin/sh

执行etcdctl命令查看刚才建立的secret1密钥的内容,这里要注意的是etcd默认的api版本是v2,k8默认使用的版本是v3,两者互不兼容,所以在执行的时候需要在命令前显式的加上ETCDCTL_API=3来告诉etcdctl我要调用的是v3 api,或者使用环境变量export指定也可以。由于v3默认开启了ssl认证,所以在调用的时候还需要加上连接认证信息,这部分内容可以在etcd.yamllivenessProbe这个配置中查看到,把这条命令复制出来,后面加上secret1的路径,就可以查看到secret内容了

/ # 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 6b 65 79 31  3a d5 62 9c a5 45 d3 76  |:v1:key1:.b..E.v|
00000040  50 b5 4f 44 66 22 a6 37  2d 95 87 e9 93 65 72 a4  |P.ODf".7-....er.|
00000050  2d 97 b1 b6 44 b0 8e 7c  27 ba 99 61 86 56 a7 97  |-...D..|'..a.V..|
00000060  21 03 eb 46 93 a9 ba f7  c1 63 fe 5c 34 12 9d 54  |!..F.....c.\4..T|
00000070  ba 3e 73 d5 71 b4 b9 28  ac 0e 66 6e a2 09 44 48  |.>s.q..(..fn..DH|
00000080  cf c6 da 4a 24 6d 49 06  dd f4 e6 85 ff ab e0 e3  |...J$mI.........|
00000090  ed 59 07 98 c2 3e 33 9e  91 f7 9a 9e d1 7f db 65  |.Y...>3........e|
000000a0  f8 60 40 2d 7c 86 1a f2  8b 37 67 c8 83 d3 5e 7b  |.`@-|....7g...^{|
000000b0  fa 51 35 f1 ee d7 51 28  81 a3 9b bd 6d 80 bb e7  |.Q5...Q(....m...|
000000c0  b8 0e 4b 85 0e 90 f3 50  41 0a                    |..K....PA.|

注意在数据头部出现k8s:enc:aescbc:v1:,说明数据已经被正确加密,使用的是aescbc算法,使用的密钥为key1

接下来我们看下kube-apiserver在读取的时候是否正确解密了,执行下面的命令

[root@MiWiFi-R1CM-srv manifests]# kubectl get secrets secret1 -o yaml
apiVersion: v1
data:
  mykey: bXlkYXRh
kind: Secret
metadata:
  creationTimestamp: "2019-08-16T15:12:14Z"
  name: secret1
  namespace: default
  resourceVersion: "549592"
  selfLink: /api/v1/namespaces/default/secrets/secret1
  uid: 3a74e0fd-c038-11e9-95ca-0800279f163b
type: Opaque

得到mykey的base64编码数据bXlkYXRh,将其decode一下

[root@MiWiFi-R1CM-srv manifests]# echo -n "bXlkYXRh" | base64 --decode
mydata

没错,正是我们设置的sercets机密数据,试验成功!

总结

通过kubernetes提供的加密插件,使得etcd中存放的secrets数据都以密文的形式存放,这无异大大提高了数据安全性。但是要明确一点,加密插件只是加密了etcd中保存的数据,这意味着你执行kubectl get secrets mysecret -o yaml这样的命令看到的仍然是明文,在容器内部注入的secrets文件或者环境变量看到的也是明文,原因当然是kube-apiserver在从etcd中取出数据的时候已经帮你自动解密了。如果你有全程加密的需求(比如说想在容器内看到的也是密文),这显然是kubernetes这种平台层的工具做不到的,因为这已经涉及到了应用的改造。

其实就目前的实际使用场景看,如果你有将etcd直接暴露给集群内第三方服务使用或者直接暴露给外部服务使用的需求(一般非常少),那么你最好使用加密插件,否则会面临机密数据泄漏的风险。而如果etcd仅供k8s的系统组件来使用的话,由于kubernetes本身已经有比较完善的rbac机制,那么你只要做好kube-apiserver的权限管理即可,例如:

那么其实也未必需要加密,k8s默认提供的secrets策略已经完全能够满足要求(毕竟你即使在etcd中加密了,有kubectl权限的和有访问secrets权限的账号还是可以看到明文)。

上一篇下一篇

猜你喜欢

热点阅读