当前位置: 首页 > 科技观察

针对KubernetesSecrets不安全的问题,

时间:2023-03-20 16:11:41 科技观察

K8s提供了Secret资源供我们保存和设置一些敏感信息,例如API端点地址、各种用户密码或令牌。当不使用K8s时,可以在部署时通过配置文件或环境变量设置这些信息。然而,Secret其实并不安全。用kubectl查看过Secret的人都知道,我们可以很容易的看到Secret的原文,只要我们有相关的权限,尽管它的内容是base64编码的,基本等同于明文。所以K8s的nativesecret非常简单,不是特别适合大公司直接使用,对RBAC的挑战比较大。许多不应看到明文信息的人可能会看到它。尤其是现在很多公司都采用了所谓的GitOps理念,很多东西都需要放在VCS里面,比如git。这个问题越来越突出,因为VCS也需要设置必要的权限。简单来说,大概有几个地方是不应该看到Secret内容的人可以获取到Secret的内容:etcd存储直接通过APIserver查看节点上的文件这里我们用这个例子来看一下在K8sCondition中使用Secret。Secret定义,用户名和密码分别为admin和hello-secret:apiVersion:v1kind:Secretmetadata:name:mysecrettype:Opaquedata:username:YWRtaW4=password:aGVsbG8tc2VjcmV0Cg==Pod定义,这里我们使用Secret作为volume挂载到容器。apiVersion:v1kind:Podmetadata:name:mypodspec:containers:-name:mypodimage:docker.io/containerstack/alpine-stress命令:-topvolumeMounts:-name:foomountPath:"/etc/foo"readOnly:truevolumes:-name:foosecret:secretName:mysecretPod启动后,我们可以到容器中查看Secret作为卷挂载到容器后的文件内容。$kubectlexec-itmypodsh/#cd/etc/foo//etc/foo#ls-taltotal4drwxr-xr-x1rootroot4096Apr1408:55..drwxrwxrwt3rootroot120Apr1408:55.drwxr-xr-x2rootroot80Apr1408:55..2021_04_14_08_55_54.401661151lrwxrwxrwx1rootroot31Apr1408:55..data->..2021_04_14_08_55_54.401661151lrwxrwxrwx1rootroot15Apr1408:55password->..data/passwordlrwxrwxrwx1rootroot15Apr1408:55username->..data/username/etc/foo#ls-tal..2021_04_14_08_55_54.401661151total8drwxr-xr-x2rootroot80Apr1408:55.drwxrwxrwt3rootroot120Apr1408:55..-rw-r--r--1rootroot13Apr1408:55password-rw-r--r--1rootroot5Apr1408:55username/etc/foo#catpasswordhello-secret/etc/foo#etcd存储API服务器中的资源全部存储在etcd中,we可以直接从文件看到相关内容:#hexdump-C/var/lib/etcd/member/snap/db|grep-A5-B5hello0004364012001a0764656661756c7422002a2432|....default".*$2|00043650356637353830382d373331332d343864|5f75808-7313-48d|00043660392d396138652d386135666632326364|9-9a8e-8a5ff22cd|000436706435393200380042080898dcda830610|d592.8.B......|00043680007a0012190a0870617373776f726412|.z.....password.|000436900d68656c6c6f2d7365637265740a1211|.hello-secret...|000436a00a08757365726e616d65120561646d69|..username..admi|000436b06e1a064f70617175651a002200000000|n..Opaque.."....|000436c000000008955f00000000000000000a37|....._...7|000436d02f72656769737472792f736572766963|/registry/servic|000436e065732f656e64706f696e74732f6b7562|es/endpoints/kub|可以看出basicyaml6中的内容解码后存储的结果可以是明文,和base4类似,$ETCDCTL_API=3etcdctlget--prefix/registry/secrets/default/mysecret|hexdump-cetcd本来就是存储明文数据的,好像从1.7开始就支持加密存储了,而且直接访问etcd也不行Soeasyphysically,通过APIserver访问APIserver就简单多了,只要有权限,从任意节点访问APIserver都可以得到secret的明文内容。$kubectlgetsecretmysecret-oyamlapiVersion:v1data:password:aGVsbG8tc2VjcmV0Cg==username:YWRtaW4=kind:Secretmetadata:creationTimestamp:"2021-04-14T08:55:52Z"name:mysecretnamespace:defaultresourceVersion:"2196"selfdefault/nameLink://secrets/mysecretuid:25f75808-7313-48d9-9a8e-8a5ff22cdd59type:在Opaque节点上,也可以看到节点上的Secret文件内容。找到foovolume的挂载点:#mount|grepfootmpfson/var/lib/kubelet/pods/280451e8-512b-489c-b5dd-df2b1a3c9b29/volumes/kubernetes.io~secret/foottypetmpfs(rw,relatime)查看文件下面的volume内容:#ls-tal/var/lib/kubelet/pods/280451e8-512b-489c-b5dd-df2b1a3c9b29/volumes/kubernetes.io~secret/foototal4drwxrwxrwt3rootroot1204month1416:55.drwxr-xr-x2rootroot804month1416:55。.2021_04_14_08_55_54.401661151lrwxrwxrwx1rootroot314月1416:55..data->..2021_04_14_08_55_54.401661151lrwxrwxrwx1rootroot154月1416:55password->..data/passwordlrwxrwxrwx1rootroot154月1416:55username->..data/usernamedrwxr-xr-x4rootroot40964月1416:55..#cat/var/lib/kubelet/pods/280451e8-512b-489c-b5dd-df2b1a3c9b29/volumes/kubernetes.io~secret/foo/passwordhello-secret针对上述几点可能泄露Secret的第三方解决方案,大概的解决方案不难想到如下:etcd加密API服务器严格执行权限设计,加强node节点用户权限管理和系统安全但是,要保证Secret的绝对安全,以上方案缺一不可,缺一不可相当于在墙上留下一个洞。社区和公共云提供商有一些产品和解决方案,我们可以参考。shyiko/kubesec:Kubernetes的安全秘密管理(使用gpg、谷歌云KMS和AWSKMS后端)/kubeseckubesec只对Secret中的数据进行加密/解密,支持以下密钥管理服务或软件:AWSKeyManagementServiceGoogleCloudKMSGnuPGbitnami-labs/sealed-secretsBitnami也是K8s领域的知名公司,拥有输出了许多技术和最佳实践。此图来自SealedSecrets:ProtectingyourpasswordsbeforetheyreachKubernetesSealeSecret将secret资源的整个加密保存为SealedSecret资源,解密只能由集群中的controller进行。SealeSecret提供了一个kubeseal工具来加密秘密资源。这个过程需要一个公钥(publickey),它是从SealeSecret控制器中获取的。不过从文档来看,SealeSecret控制器加解密所依赖的key也是保存在一个普通的Secret中。这不是问题吗?同时也增加了SealeSecret控制器的运维成本。Mozilla/sops严格来说,sops和K8s没有任何关系。它只是一个加密文件编辑器,支持YAML/JSON/ENV/INI等文件格式。它支持AWSKMS、GCPKMS、AzureKeyVault、age和PGP等服务和应用。如果你有兴趣,可以查看它的主页。KubernetesExternalSecretsKubernetesExternalSecrets是知名域名服务商godaddy开发的一款开源软件,可以将存储在外部KMS中的机密信息直接传输到K8s中。目前支持的KSM包括:AWSSecretsManagerAWSSystemManagerHashicorpVaultAzureKeyVaultGCPSecretManagerAlibabaCloudKMSSecretManager通过自定义controller和CRD实现。具体架构图如下:具体来说,用户需要创建一个ExternalSecret类型的Resources来将外部KMS数据映射到K8sSecret。但是这种方式只有两个优点:统一密钥管理,或者使用已有的密钥资产密钥信息不想放在VCS等,对于防止Sercet信息泄露没有效果,因为它的明文资源还是可以的存储在API服务器/etcd中,见上文。也就是说,ExternalSecrets真正做的是将外部KMS中的key映射到K8s中的Secret资源中,这对于保证K8s集群中数据的安全用处不大。KamusKamus也提供了对key进行加密的方法(命令行工具),key只能通过K8s中的controller进行解密。但是K8s中存储的Secret是加密的,用户不能像ExternalSecrets那样直接获取Secret的明文内容。Kamus包含3个组件,分别是:EncryptAPIDecryptAPIKeyManagementSystem(KMS)KMS是对外部加密服务的封装,目前支持以下服务:-AES-AWSKMS-AzureKeyVault-GoogleCloudKMSKamus使用服务账号作为unit对secret进行加密,然后Pod会通过服务账号请求Kamus的解密服务来解密secret。对于K8s来说,可以通过init容器来实现解密:定义一个基于内存的emptyDir,业务容器和init容器使用同一个volume,init容器解密后,将数据存放在volume中,然后业务容器可以使用解密后的机密数据了。VaultbyHashiCorpHashiCorp不用多说,也是云计算/DevOps领域最好的公司之一。Vault本身是一种用于管理机密数据的类似KMS的服务。对于K8s的nativesecret,它提供了以下两种方式的支持:AgentSidecarInjector/vault-k8sVaultCSIProviderAgentSidecarInjector这种方式和上面的Kamus类似,同样需要两个组件:Mutationwebhook:负责修改pod定义,injectinit/sidecaragent-sidecar:负责获取和解密数据,并将数据保存到指定的volume/path中Vaultagentsidecarinjector不仅提供初始化secret的init容器,还通过定期更新secretsidecar,它非常接近原始秘密的实现。应用程序只需要读取文件系统上指定的文件,而不管外部如何获取加密信息。下面是来自官方博客的例子:Podinfo:spec:template:metadata:annotations:vault.hashicorp.com/agent-inject:"true"vault.hashicorp.com/agent-inject-secret-helloworld:"secrets/中helloworld"vault.hashicorp.com/role:"myapp"的定义,vault-k8s会将保险库代理注入pod并使用secrets/helloworld对其进行初始化。Pod运行后,在/vault/secrets下可以找到一个名为helloworld的文件。$kubectlexec-tiapp-XXXXXXXXX-capp--cat/vault/secrets/helloworlddata:map[密码:foobarbazpassusername:foobaruser]元数据:map[created_time:2019-12-16T01:01:58.869828167Zdeletion_time:destroyed:falseversion:1]当然这个数据是原始数据,没有格式化。如果要指定输出到文件的格式,可以使用库的模板功能。VaultCSIProvider部分请参考下方社区解决方案部分。社区解决方案当然社区没有理由不知道原生secret的问题,所以社区也有KubernetesSecretsStoreCSIDriver,通过CSI接口将Secrets集成到K8s中的解决方案。SecretsStoreCSI驱动程序(secrets-store.csi.k8s.io)允许K8s从外部KMS挂载到Pod以卷的形式挂载多个secret。使用SecretsStoreCSIDriver,一般流程如下:DefineSecretProviderClassapiVersion:secrets-store.csi.x-k8s.io/v1alpha1kind:SecretProviderClassmetadata:name:my-providerspec:provider:vault#acceptedprovideroptions:azureorvaultorgcpparameters:#provider-特定参数作为Pod配置Volumekind:PodapiVersion:v1metadata:name:secrets-store-inlinespec:containers:-image:k8s.gcr.io/e2e-test-images/busybox:1.29name:busyboxcommand:-"/bin/sleep"-“10000”volumeMounts:-name:secrets-store-inlinemountPath:"/mnt/secrets-store"readOnly:truevolumes:-name:secrets-store-inlinecsi:driver:secrets-store.csi.k8s.ioreadOnly:truevolumeAttributes:secretProviderClass:"my-provider"Pod启动后,可以确认解密后的数据:$kubectlexecsecrets-store-inline--ls/mnt/secrets-store/foo总结以上总结根据互联网公开资料,有没有亲身经历过,有些地方可能会有误解。要想深入了解,还需要自己去确认。但总的来说,社区的方案可能是最简单的,部署也不是很麻烦,但基本上和原来的秘钥没有关系。..