咱们接着第十篇文章继续讨论,通过kubectl -n vault exec -it vault-0 -- vault kv get agents/julia来访问保存在Vault中的隐私数据,本质上使用的是root token,因为root账户具备操作系统的所有权限,因此我们需要更加安全的应用和Vault的集成方式。
笔者在容器化部署安全模型的文章中曾经介绍过几个安全的原则,其中最被大家熟知的就是最小权限原则(least privilege principal),因此在应用程序和Vault集成的场景下,我们也需要除了时候用root token之外给应用授权访问Vault中敏感数据的方法。Vault提供了多种认证方式来简化和应用的集成,包括但不限于:用户名&密码模式,TLS证书模式,咱们前边使用的Token模式等等。
Vault提供的每种认证授权模式(Auth Method)可以关联一个或者多个策略(Policies)来定义访问Vault中不同path的权限集合。每个policy中包含了若干Capabilities定义,提供了更加细粒度的权限控制,其中Capabilities,Policies和Auth Method的关系如下图所示:

Vault为我们默认提供了如下列表所示的capabalities:
1,create:允许在指定的路径上创建数据
2,read:允许在指定的路径上读取数据
3,delete:允许在指定的路径上删除数据
4,list:允许在指定的路径上列出所有的数据
Policy可以通过Json文件或者HCL(HashiCorp Configuration Language,此语言和Json兼容)来编写,我们可以通过客户端的命令行工具将HCL文件提交给Vault Server。接下来咱通过一个例子来实战一下Capabilities,Policies和Auth Method这几个概念。
在自己的机器上创建文件agents-policy.hcl,文件中定义了访问Vault路径agents的权限(包括read和list),如下对文件cat的输出所示:
➜ Kubernetes安全 cat agents-policy.hcl
path "agents/data/*" {
capabilities = ["list", "read"]
}
path "agents/metadata/*" {
capabilities = ["list", "read"]
}
如上边的输出所示,我们为agents路劲上的资源定义了list和read的权限,接下来我们基于这hcl文件来在Kubernetes集群中创建具体的policy对象,运行命令:cat agents-policy.hcl | kubectl -n vault exec -it vault-0 -- vault policy write agents_reader - 创建了名为agents_reader的策略。
有了Policy之后,接下来我们基于policy来创建访问令牌(access token),由于policy文件中只定义了read和list这两个capabilities,因此使用这个token的应用程序只具备读取和罗列的能力。在自己的机器上运行:export AGENTS_APP_TOKEN=$(kubectl -n vault exec -it vault-0 -- vault token create -policy=agents_reader -format=yaml | grep client_token | awk '{ print $2 }')。
命令执行后会将token赋值给变量AGENTS_APP_TOKEN,我们使用kubectl -n vault exec -it vault-0 -- vault token lookup $AGENTS_APP_TOKEN来查看具体的Token信息,输出如下:

如上图所示,默认情况下,TTL会被设置为32天。不过在大部分企业中,32天的有效期时间略长,缩减令牌的有效期是提升安全和减少潜在攻击的一种有效的手段,比如Token有效期越长,被攻破造成的数据泄漏的几率就增大。我们可以通过-ttl来显示的在vault token create命令中设定符合企业安全规范的有效期。
接下来我们来验证一下这个Token是否只持有policy定义的read和list的权限。首先通过命令kubectl -n vault exec -it vault-0 -- cp /home/vault/.vault-token /home/vault/.vault-token.root来备份当前的root token,以便验证完之后恢复用。接下来使用新的token来登陆Vault:echo $AGENTS_APP_TOKEN | kubectl -n vault exec -it vault-0 -- vault login -,在笔者的机器上输出如下:

接着通过命令来验证一下登陆用户具备list所有key的能力,笔者的机器上输出如下:
➜ Kubernetes安全 kubectl -n vault exec -it vault-0 -- vault kv list /agents
Keys
----
config
jessika
julia
wangwang
yunpan
接着我们来验证一下权限之外的操作是否被允许,在命令行执行:kubectl -n vault exec -it vault-0 -- vault secrets list来罗列所有enabled的secrets engine,输出如下:

验证完这个Token持有的权限和Policy定义的权限一致后,我们先来恢复root token:kubectl -n vault exec -it vault-0 -- cp /home/vault/.vault-token.root /home/vault/.vault-token,然后执行刚才未授权的操作:kubectl -n vault exec -it vault-0 -- vault secrets list,可以看出root token可以返回所有enabled的secret engine。
有了上边知识的铺垫,接下来咱们通过部署一个应用程序来展示如何将应用程序和Vault集成起来,提供敏感数据的安全配置访问。首先我们创建一个名叫agents-vault-token的Kubernetes Secrets对象,这个对象包含了应用程序访问Vault需要的令牌,并且这个令牌会被作为环境变量注入到容器进程中(关于如何在Kubernetes中管理应用程序配置,读者可以参阅笔者系列文章)。我们直接沟通过命令行来创建这个Secrets:kubectl create secret generic agents-vault-token --from-literal=token=$(echo -n $AGENTS_APP_TOKEN | tr -d '\r\n')。
接着我们需要在Kubernetes上定义一个叫agents的Service Account,笔者这里要强调的是,大家务必在Kubernetes上的每个应用长须创建独立的Service Account并单独授权,这是安全管理的最佳实践。在自己的机器上创建serviceaccount.yml文件,并包含如下的内容:
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: agents
然后在自己的K8S环境上执行kubectl apply -f serviceaccont.yml来将对应的SC创建出来。接着在自己的机器上创建名为deployment_token_auth.yml的yaml文件,这个deployment会部署笔者编写的一个springboot应用程序,这个应用程序通过环境变量中提供的token来安全的访问Vault中的键值对数据,Yaml文件如下:

部署成功后,等待POD中的容器成功运行起来,先通过kubectl port-forward来开通到POD的访问,然后执行curl命令,在笔者的机器上输出如下图所示:

通过将VaultToken注入到容器的环境变量,我们的应用程序就可以使用安全令牌来访问Vault中保存的数据,虽然说这种方式看起来很合理,但是需要先创建secrets,并且需要提前创建Token,管理Token等操作,所有这些需要手工完成的操作,从安全角度来说,都具有一定的风险。
因为我们的应用程序和Vault部署在Kubernetes平台上,因此我们可以充分利用Kubernetes提供的Kubernetes Auth Method机制。具体来说Kubernetes Auth Methods简化了应用程序和Vault集成的配置工作,我们不再需要花费额外的精力来管理访问Vault令牌的生命周期。
不过从原理上讲,Kubernetes Auth Method并没有弱化Token的概念,而是通过另外一种机制替换了管理源自Vault的安全令牌,这种机制就是使用源自Kubernetes Service Account的JWT(Json Web Tokens)令牌。具体来说,运行在Kubernetes平台上的应用程序通过挂载到Pod中的/var/run/secrets/kubernetes.io/serviceaccount/token文件提供的令牌来访问Vault。
除此之外,我们还需要在Vault应用中创建对应的Roles(角色)来和Kubernetes的Service Account映射,同时我们在Vault中为角色定义对应的policies,来为Service Account授权具体的访问权限。说的有点绕,我们直接来上图看看:

接下来我们在本地的minikube集群上实战一下Kubernetes Auth Method这种机制。首先需要在Vault集群上enable这种机制,通过运行命令:kubectl -n vault exec -it vault-0 -- vault auth enable kubernetes。
另外如图1.7所示,Vault需要验证部署在Kubernetes上的应用使用的Token是否依然有效,而Kubernetes的TokenReview API正好提供了这种基于JWT Tokens反查的能力,具体来说,Vault将受到的JWT Token发送给TokenReview接口,认证通过后,账户的详细信息会返回给Vault来进行后续处理。
为了让Vault能够调用Kubernetes的TokenReview接口,我们首先创建一个叫vault-tokenreview的ServiceAccount,具体的yaml文件如下:
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-tokenreview
将上边的YAML文件部署到Kubernetes集群后,我们接着需要给这个账户授予调用Token Review API的权限。幸运的是,在Kubernetes中,以及存在一个叫system:auth-delegator的ClusterRole,我们要做的就是创建一个叫vault-tokenreview-binding的ClusterRoleBinding对象,在自己的机器上创建vault-tokenreview-binding.yml,内容如下:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vault-tokenreview-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: vault-tokenreview
namespace: vault
将这个binding对象创建后,我们接下来解决应用程序如何获取Token的问题。首先执行命令SA_SECRET_NAME=$(kubectl -n vault get serviceaccount vault-tokenreview -o jsonpath={.secrets[0].name})来找到包含JWT token的Secrets,接着对获取的数据进行base64解码,获取token的数据:VAULT_TOKENREVIEW_SA_TOKEN=$(kubectl -n vault get secret $SA_SECRET_NAME -o jsonpath='{.data.token}' | base64 -d)。
接着我们把Kubernetes集群的配置信息提供给Vault服务,运行命令:kubectl -n vault exec -it vault-0 -- vault write auth/kubernetes/config kubernetes_host="https://kubernetes.default.svc" kubernetes_ca_cert="@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" token_reviewer_jwt=$VAULT_TOKENREVIEW_SA_TOKEN。运行成功后,Vault就能成功认证来自于部署在Kubernetes上的应用程序访问请求,从应用程序的角度,为了访问保存在Vault中的隐私数据,应用程序必须被赋予对应的Role。
Role由name,namespace以及Vault policies组成,在自己的机器上运行命令:kubectl -n vault exec -it vault-0 -- vault write auth/kubernetes/role/agents bound_service_account_names=agents bound_service_account_namespaces=vault policies=agents_reader来创建名叫agents的Role。接着我们需要更新deployment部署yaml文件,把之前用的QUARKUS_VAULT_AUTHENTICATION_CLIENT_TOKEN替换为QUARKUS_VAULT_AUTHENTICATION_KUBERNETES_ROLE,更新后的YAML文件如下所示:

废话不多说,我们赶紧把这个更新后的YAML文件部署到Kubernetes中(如果之前的应用还在运行,可以运行kubectl delete deployment来删除)。读者可以自行验证这种机制的工作情况,在笔者的minikube环境中,我们可以通过springboot应用程序成功访问Vault中的旅行应用中的导游信息。
到这里为止,咱已经完整介绍了如何通过Token或者Kubernetes Auth Method来访问保存在Vault中的敏感数据,但是这里有个问题,应用程序从某种程度上强依赖于Vault。云原生应用有个非常重要的特点就是和不和具体的平台和组件绑定,并且不见得所有的应用程序都能通过修改源代码来完和Vault集成,因此我们最后来聊聊如何应对这种挑战。
对于应用程序来说,Vault中保存的数据可以看成是配置信息,那么问题就变成了我们如何让Vault中的信息对应用程序可见。读到这里我希望看过笔者前边关于Kubernetes配置管理的同学能有共鸣,坦白讲这种问题的通用解法就是通过挂载数据卷。
具体来说,我们可以通过在容器实例之间共享一个emptyDir的存储卷来实现:POD中有两个容器,一个容器负责和Vault交互读取隐私数据并存储到数据卷,而相同的数据卷挂载到应用程序容器实例中,这样应用就能访问到Vault中保存的敏感信息了。
我们在Kubernetes中可以有两种方式来实现共享卷的这种模式:1,初始化容器,初始化容器是一种特殊的容器,在应用程序容器运行前执行,在Vault的场景下,我们可以在初始化容器中访问Vault来获取应用程序需要的敏感数据,这样当应用程序容器运行的时候,数据就已经准备好了;2,边车容器,边车容器和应用程序容器肩并肩运行,在Vault的场景下,边车容器持续的和Vault进行通信来获取最新的配置数据,然后将数据保存在和应用程序共享的数据卷中。
显然这两种模式都需要一些额外的工作,比如我们如果使用边车容器,那么就需要给每个需要访问Vault的应用手动配置边车代理,这显然又回到了我们熟悉“手动”模式。为了让添加边车容器自动化,HashiCorp提供了Vault Agent Injector这个POD,用来自动注入边车代理。
Vault Agent Injector运行在Kubernetes中,时刻监控哪些需要访问Vault容器的POD对象(在POD的YAML文件中通过annotations来标明)。建设我们定义了一个POD需要访问Vault,那么Vault Agent Injector会截获这个POD请求,并在写入ETCD之前注入代理容器的配置信息,这个功能需要借助于Kubernetes的MutatingWebhookConfiguration功能,具体的执行过程如下图所示:

最后我们来修改应用的部署YAML文件使其包含被Vault Agent Injector识别的annotation,然后我们来验证一下这种自动化容器注入机制是否按照预期工作。首先我们通过命令:kubectl -n vault exec -it vault-0 -- vault kv put agents/config mission="30天精通Kubernetes安全" coordinator="云攀虚拟化技术社"来创建一个新的名叫config的secrets,接着修改我们的Deployment文件,其实不光包含自动注入代理容器,也包含额外的信息以什么形式挂载到容器中,核心修改部分如下图所示:

接着,我们将这个更新后的应用重新部署到Kubernetes集群(可以先删除老的agents,然后kubectl apply),待应用运行起来之后,我们执行kubectl -n vault get pod -l=app=agents来查看应用的状态,输出如下:
➜ Kubernetes安全 kubectl -n vault get pod -l=app=agents
NAME READY STATUS RESTARTS AGE
agents-5fbbdbf8-p4pdf 2/2 Running 0 5s
从输出的结果可看到,POD中运行了两个容器实例,我们可以通过kubectl describe pod来查看包含的两个容器信息:

最后,我们在自己的机器上执行命令:kubectl -n vault exec -it $(kubectl -n vault get pods -l=app=agents -o jsonpath={.items[0].metadata.name}) -c agents -- cat /vault/secrets/config.properties来查看从Vault中读取到的,以文件挂载到容器中的数据,输出如下:
➜ Kubernetes安全 kubectl -n vault exec -it $(kubectl -n vault get pods -l=app=agents -o jsonpath={.items[0].metadata.name}) -c agents -- cat /vault/secrets/config.properties
coordinator: 云攀虚拟化技术社
mission: 30天精通Kubernetes安全
好了,这篇文章的内容就这么多了,我们下篇文章继续讨论如何基于阿里云提供的KMS来存储我们的隐私数据,敬请期待!