原文作者:dahukkk
原文链接:https://developer.aliyun.com/article/727671
Vault是什么?
如何在云上应用中管理和保护用户的敏感信息是一个经常令开发者头疼的问题,用户的密码口令,证书秘钥等私密信息时常未经加密被随意的放置在配置文件,代码仓库或是共享存储里,而对于普通的开发者来说,设计和实现一套完整的秘钥管理系统是一个很大的挑战。且不论令人生畏的加解密算法,很多的云应用仍然将一些敏感配置信息仅仅经过 base64 等一些简单的 hash 运算就放置在某个公共的配置中心上,而很多时候这些敏感信息会从应用的某行异常日志或是某段监控告警中泄露出去;不仅如此,对于一个集中式的秘钥管理系统,如何面向用户进行更细粒度的访问鉴权也是一个难题。
Vault 的出现给了上述问题一个解决方案,它是 HashiCorp 公司(旗下还有 Vagrant,Terraform,Consul 等知名产品)维护的开源软件,它的设计思想基于云原生背景下动态基础设施的特点,在云上的不同网络层以及不同的服务之间已经很难找到传统的信任边界,服务之间更加强调以身份(identity)为核心的认证和访问控制,而不是像传统静态基础设施中以 IP、主机地址作为信任凭证。为此 Vault 提供了以下几个功能点:
- Secret存储形式的多样性,任意的kv形式敏感信息(如数据库密码,证书,ssh登录秘钥,openapi身份凭证等);
- 存储格式的多样性,支持插件式的存储引擎扩展,可对接如 AWS,Consul,NoSQL,KV,PKI,SSH 等多种插件引擎;
- 支持与各类平台的认证对接,可动态生成认证凭据或配置信息;
- 支持基于 Shamir 算法的私钥分割完成 Vault 后端的加封和解封操作,同时支持高可用的部署形态;
- 支持各类 secret 的动态生成,续租,撤销和滚动更新;
- 完备的审计日志;
- 完备的 CLI 和 RESTful API
Vault 与 k8s 的集成
Vault 松耦合的架构使其支持与多种 secret 引擎和相应的存储后端对接,同时支持与多种认证服务器的交互。
本节我们主要介绍 Vault 与 k8s 的集成。
2.1 Vault 在 Kubernetes 中的应用场景
Vault 作为企业级的 secret 管理工具,是一些大客户在业务上云过程中的安全强需求,尤其是国外市场。在 Kubernetes 集群中主要有以下应用场景:
- 作为部署在 Kubernetes 集群中的应用对外提供秘钥管理服务,支持与多家主流云厂商秘钥服务以及多种 secrets 形式的对接,支持多种数据库服务的存储对接,同时支持多种认证形式的对接。
- 作为一个公共的加密服务(Encryption as a Service)而不做后端存储的对接,帮助用户应用剥离繁琐的加密加解密逻辑。
- 面向政府、金融等对数据安全规格有很高要求的客户,Vault 支持基于 Two-man 原则利用私钥分割算法对后端服务进行加解封,并结合 k8s 的高可用部署形态为企业提供更加安全可靠的 secret 管理能力。
当然这里只是列举了一些 Vault 原生提供的能力,作为一个在 Kubernetes 集群上直接运行的安全应用,任何一个面向 k8s 的应用工具都可以利用其安全能力。
2.2 安装 Vault
Vault 支持 helm 化安装,在其官方文档中我们可以找到关于启动参数的详细配置说明,同时在阿里云容器服务的应用目录 apphub 中我们也可以通过控制台在 ACK 集群中方便的安装 Vault
另外 Vault 的默认安装也集成了其控制台的安装,通过负载均衡服务或 ingress 路由的方式我们可以在公网访问其 UI,在 vault pod 的日志中我们可以找到登录使用的 root token,在控制台中可以方便的设定与存储引擎和认证方式的对接,同时还可以进行基于策略的访问控制配置。
2.3 认证方式的集成
当用户希望在 k8s pod 的业务逻辑中与 Vault 服务端通讯,获取需要的 secrets 时,首先 Vault 会对这个 pod 中的请求进行认证,那么这个 pod 中的 Vault 请求认证凭据应该如何获取呢?如上所述,Vault 后端支持多种认证方式的对接,对于 Kubernetes,Vault 支持基于 K8s Service Account Token 的认证。
使用上,Vault 管理员首先需要在后端 enable kubernetes 的认证方式,生成一个与 Vault 交互的指定 sa,然后通过 CLI 或 API 将 sa token 和集群 ca,公网地址等信息写入到 Vault 后端中,并配置与 vault 后端的 ACL 策略绑定。详细步骤请参见官方文档。
当然在 pod 应用中可以并不局限于一定使用基于 sa 的 kubernetes 认证方式。比如[kubernetes-vault]( https://github.com/Boostport/kubernetes-vault
)。该项目使用 Vault 中的[ AppRole ](https://www.vaultproject.io/docs/auth/approle.html
)认证方式,在该认证模式中,管理员可以为不同的 pod 创建不同的 Vault 原生 role 模型并绑定到对应的 policy 上,同时可以基于 role 创建 secret_id,secret_id 对应的 token 可作为与 Vault 进行认证的临时凭证。kubernetes-vault 利用了 AppRole 的认证交互模式,首先已经完成安装的 kubernetes-vault controller 会去 Watch 集群中所有 pod 的创建,当发现新建 pod 的部署模板中有指定 annotation 的 init-container 存在时,controller 会根据模板中指定的 vault role id 去 Vault 请求获取其对应的 secret_id 并发送给 init-container 中 kubernetes-vault 的客户端,在应用容器启动前 kubernetes-vault 客户端用 controller 返回的 secret_id 和 role_id 去 Vault 请求真正的 login token 并最终写入到与应用 pod 共享的挂载目录中;同时客户端会根据 token 过期时间进行定时的轮转,保证其可用性。下图为 kubernetes-vault 工作流程图:
在社区也存在不少基于 k8s 与 Vault 进行认证对接的其他方案,其设计思路大同小异,基本都采用了通过 init-container 或 sidecar 方式引入一个额外的客户端去 Vault 请求指定认证模式下的短时凭证并共享给业务容器使用。
在容器服务控制台的应用目录 apphub 中,我们同样可以找到 kubernetes-vault,方便开发者使用 helm 直接在集群指定命名空间一键部署。Vault 也计划在后续自己的官方版本 helm chart 中增加配置项以支持上述登录认证 secret 的动态注入。
Vault 与阿里云 RAM 的集成
当我们在应用中需要访问阿里云资源时,需要使用 RAM 账号对应的 AK 或是 STS 临时 credentials 作为访问相应资源接口的凭证。如果使用账号 AK,如何使其能够被应用逻辑获取的同时保证 AK 的安全性一直是一个头疼的问题;如果使用临时 sts token,由于其时效性,我们也需要在考虑安全性的同时思考如何进行临时访问凭证的轮转。相比较两种方式,使用 sts 临时凭证的方式肯定在安全上是更为推荐的方式,同时对这种动态 secret 的安全管理也正是 Vault 的优势所在。本节我们来介绍下 Vault 与阿里云 RAM 在认证方式和 secret 管理引擎上的集成。
3.1 认证方式的集成
首先在认证方式上,Vault 服务端的 role 模型可以与 RAM role 进行一对一的映射匹配,用户可以使用 Vault提供的 OpenAPI 或是 CLI,通过传入扮演RAM role 返回的临时凭证调用GetCallerIdentity 接口,然后 Vault 服务端会根据请求返回的角色 arn id 在其后端存储中查找是否有对应的权限策略配置,如果存在则认证成功并返回一个可用于调用 Vault 其他后端接口的访问 token。
3.2 Vault Secret 引擎与 RAM 的集成
当我们需要在业务应用逻辑中使用阿里云资源时,通常需要通过角色扮演的方式获取一个 RAM 返回的临时凭证,然后通过这个临时凭证完成与 RAM 的鉴权过程。由于凭证的时效性,我们在保证其安全性的同时还要维护一个对应的秘钥轮转机制。Vault 的 secret 引擎实现了与阿里云 RAM 的对接插件,帮助我们安全、动态的管理 RAM 凭证,其主要步骤 如下:
1.开启后端引擎
2.在 RAM 控制台为 Vault 服务器创建专属子账号并绑定定制化权限策略
3.获取 Vault 子账号对应的 AK 并通过 Vault CLI/API 写入到后端指定路径下
4.在 Vault 后端写入业务中希望获取的 RAM 凭证所对应的策略定义或角色,其中策略定义支持 inline 和 remote 策略两种形式,所谓 inline 模式是指直接在 api 请求中写入策略模板,remote 模式指写入 RAM 中存在的策略类型和名称,比如:
$ vault write alicloud/role/policy-based \
remote_policies='name:AliyunOSSReadOnlyAccess,type:System' \
remote_policies='name:AliyunRDSReadOnlyAccess,type:System'
角色模式需要用户指定希望被扮演的角色 arn,另外需要 Vault 子账号在该角色的受信实体里,一个示例如下:
$ vault write alibaba/role/role-based \
role_arn='acs:ram::5138828231865461:role/hastrustedactors'
5.在具体的业务应用中,只需要通过调用 Vault 的 creds/policy-based 或 role-based 接口即可动态获取相应的 RAM 访问凭证,下面是一个角色扮演返回临时 token 的 CLI 调用示例:
$ vault read alicloud/creds/role-based
Key Value
--- -----
lease_id alicloud/creds/role-based/f3e92392-7d9c-09c8-c921-575d62fe80d9
lease_duration 59m59s
lease_renewable false
access_key STS.L4aBSCSJVMuKg5U1vFDw
secret_key wyLTSmsyPGP1ohvvw8xYgB29dlGI8KMiH2pKCNZ9
security_token CAESrAIIARKAAShQquMnLIlbvEcIxO6wCoqJufs8sWwieUxu45hS9AvKNEte8KRUWiJWJ6Y+YHAPgNwi7yfRecMFydL2uPOgBI7LDio0RkbYLmJfIxHM2nGBPdml7kYEOXmJp2aDhbvvwVYIyt/8iES/R6N208wQh0Pk2bu+/9dvalp6wOHF4gkFGhhTVFMuTDRhQlNDU0pWTXVLZzVVMXZGRHciBTQzMjc0KgVhbGljZTCpnJjwySk6BlJzYU1ENUJuCgExGmkKBUFsbG93Eh8KDEFjdGlvbkVxdWFscxIGQWN0aW9uGgcKBW9zczoqEj8KDlJlc291cmNlRXF1YWxzEghSZXNvdXJjZRojCiFhY3M6b3NzOio6NDMyNzQ6c2FtcGxlYm94L2FsaWNlLyo=
expiration 2018-08-15T21:58:00Z
如何在 K8s 应用中使用 Vault Secret
在了解了 Vault 的基本概念以及与 Kubernetes 的认证交互流程后,我们进入客户最为关心的话题。如何在 k8s pod 应用中方便地获取 Vault 服务端管理的 secret。社区针对此问题也有激烈的讨论和不少相关解决方案,方案主要集中在两个方向:
[定时同步进程](https://github.com/hashicorp/vault/issues/7364
):使用一个同步进程定时地从 Vault 服务端获取指定范围的秘钥更新并同步到 K8s 集群中的 secret 模型,代表的项目有 vaultingkube 和[secrets-manager]( https://github.com/tuenti/secrets-manager
) 。其主要设计思想也不尽相同,以 secrets-manager 为例,首先用户可以通过 CRD 定义在 Vault 中关注的 secret 数据源,然后 secrets-manager 对应的 controller 会在 Reconcile 函数中定时对比指定管理范围内的 K8s secret 和 vault secret 的状态,如果不一致则进行一次调协。而用户在 pod 应用中可以直接引用原生 secret 模型中的内容获取远端 Vault 服务器中的秘钥。
当然社区中也存在一些对这种秘钥同步方案的质疑,比如认为该方案在秘钥同步的传输过程和用户 pod 使用原生 secret 的 rest 交互中会增加攻击面,但是该方案在部署实施上比较友好,也得到了很多用户的支持。
CSI 插件形式集成:该方案基于 CSI plugin 将 Vault 中的秘钥通过 volume 的形式挂载到 pod 应用中。
secrets-store-csi-driver 通过实现一套基于 CSI 规范的 driver 机制可以对接不同厂商的后端存储,而 Vault secret 的 driver(secrets-store.csi.k8s.com)允许 kubelet 将各类企业级秘钥存储中的 secret 通过 volume 挂载,一旦 attach 动作完成,秘钥数据即挂载到了容器对应的文件系统中。在 CSI driver 的基础上,不同的秘钥管理后端可以实现定制化的 provider 去对接 CSI driver 框架中的规定接口。provider 的功能概括如下:
- 对接后端秘钥管理系统,提供秘钥获取等必须的接口实现
- 适配当前 CSI driver 的接口定义
- 通过框架中的回调函数无需调用 Kubernetes API 即可将从后端获取的秘钥数据挂载到指定路径下
HashiCorp 官方也基于此框架实现了一套对接 Vault 的 Provider。这里我们以此为例具体来看下在一个 k8s pod 应用中如何通过 CSI plugin 的方式使用 Vault 中管理的 secret 秘钥。
1 首先我们创建一个开启了 CSI 存储插件的 ACK 集群,然后参考文档在集群中部署 Vault 服务端,为了便于验证这里我们使用 dev 模式省去 unseal 解封等流程,同时配置 provider 与 Vault 交互的认证模式和相应的访问控制策略
然后通过 cli 向 Vault 后端写入测试数据
2 通过官方提供的 helm 方式安装 Secret Store CSI Driver,命令如下:
helm install . -n csi-secrets-store --namespace dev --set providers.vault.enabled=true
安装成功后如下图所示:
3 在集群中创建 secretproviderclasses 实例用于 Secret Store CSI Driver 与 Vault 的参数对接,一个示例如下,注意这里的 vault 服务端地址可通过 kubectl get service vault 获取。
4 最后我们来看下如何在应用 pod 中对接上述provider 实例获取对应的 Vault 秘钥。这里 pod 对于上述 vault provider 的使用分为两种方式:
1)如果 pod 运行的目标集群版本在 v1.15.0 以上,且集群 apiserver 和节点 kubelet manifest 配置均开启了 CSIInlineVolume=true
的 feature-gates,则我们可以在 pod 中的 volume 字段内置声明需要使用的 csi provider 实例。
kind: Pod
apiVersion: v1
metadata:
name: nginx-secrets-store-inline
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
volumes:
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.com
readOnly: true
volumeAttributes:
secretProviderClass: "vault-foo"
···
2)如果目标集群不支持 CSI 的 Inline Volume 特性,我们需要首先创建使用 csi 的 pv 和对应的 pvc 实例,一个 pv 模板示例如下:
···
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-vault
spec:
capacity:
storage: 1Gi
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
csi:
driver: secrets-store.csi.k8s.com
readOnly: true
volumeHandle: kv
volumeAttributes:
providerName: "vault"
roleName: "example-role"
vaultAddress: http://172.21.12.21:8200
vaultSkipTLSVerify: "true"
objects: |
array:
- |
objectPath: "/foo"
objectName: "bar"
objectVersion: ""
在 pod 实例模板中引用指定 pvc 即可在 pod 中获取到 vault,这里我们在 ACK 集群以 pv/pvc 模式为例创建一个 nginx 应用容器实例并在其中挂载上文中我们创建的 secretproviderclasses 实例:
相比于 secrets-manager 等采用 secret 定时同步的方式,使用 CSI 对接指定 Vault secret provider 实例的方式虽然在实施步骤上比较复杂,同时在应用中也无法动态获取 Vault 后端 secret 的变更,但是该方案避免了 secret 在同步链路上频繁传输的安全风险,同时也客服了之前 describe po 可能造成的秘钥泄露,在整体安全性上要高出不少。大家可以根据实际应用场景选择适合自己的方式。
“阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术公众号。”