通过vault在Kubernetes中注入密钥

173次阅读

共计 8188 个字符,预计需要花费 21 分钟才能阅读完成。

Vault是什么?

在 Kubernetes 系统中提供了一个 Secret 对象来存储私密的数据,但是也只是简单的做了一次 Base64 编码而已,虽然比直接暴露要好点了,但是如果是一些安全性要求非常高的应用直接用 Secret 显然也还是不够的。

如何在云上应用中管理和保护用户的敏感信息是一个经常令开发者头疼的问题,用户的密码口令,证书秘钥等私密信息时常未经加密被随意的放置在配置文件,代码仓库或是共享存储里,而对于普通的开发者来说,设计和实现一套完整的秘钥管理系统是一个很大的挑战。且不论令人生畏的加解密算法,很多的云应用仍然将一些敏感配置信息仅仅经过base64等一些简单的hash运算就放置在某个公共的配置中心上,而很多时候这些敏感信息会从应用的某行异常日志或是某段监控告警中泄露出去;

Vault的出现给了上述问题一个解决方案,它是HashiCorp公司(旗下还有Vagrant,Terraform,Consul等知名产品)维护的开源软件,它的设计思想基于云原生背景下动态基础设施的特点,在云上的不同网络层以及不同的服务之间已经很难找到传统的信任边界,服务之间更加强调以身份(identity)为核心的认证和访问控制,而不是像传统静态基础设施中以IP、主机地址作为信任凭证。

Vault在Kubernetes中的应用场景

Vault 作为企业级的 secret 管理工具,是一些大客户在业务上云过程中的安全强需求,尤其是国外市场。在Kubernetes集群中主要有以下应用场景:

  • 作为部署在Kubernetes集群中的应用对外提供秘钥管理服务,支持与多家主流云厂商秘钥服务以及多种secrets形式的对接,支持多种数据库服务的存储对接,同时支持多种认证形式的对接。
  • 作为一个公共的加密服务(Encryption as a Service)而不做后端存储的对接,帮助用户应用剥离繁琐的加密加解密逻辑。
  • 面向政府、金融等对数据安全规格有很高要求的客户,Vault支持基于Two-man原则利用私钥分割算法对后端服务进行加解封,并结合k8s的高可用部署形态为企业提供更加安全可靠的secret管理能力。
    当然这里只是列举了一些Vault原生提供的能力,作为一个在Kubernetes集群上直接运行的安全应用,任何一个面向k8s的应用工具都可以利用其安全能力。

安装

这里直接使用 Vault 官方提供的 chart 包安装即可

[mervinwang@kubelab ~]$ helm install vault hashicorp/vault --namespace vault --set "server.dev.enabled=true"
NAME: vault
LAST DEPLOYED: Mon Jan 17 17:49:13 2022
NAMESPACE: vault
STATUS: deployed
REVISION: 1
NOTES:
Thank you for installing HashiCorp Vault!

Now that you have deployed Vault, you should look over the docs on using
Vault with Kubernetes available here:

https://www.vaultproject.io/docs/

Your release is named vault. To learn more about the release, try:

  $ helm status vault
  $ helm get manifest vault

上面的命令就会在 vault 命名空间下面安装一个名为 vault 的 Helm release:

[mervinwang@kubelab ~]$ helm ls -n vault
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
vault   vault           1               2022-01-17 17:49:13.562653474 +0800 CST deployed        vault-0.18.0    1.9.0      
[mervinwang@kubelab ~]$ kubectl get po -n vault 
NAME                                    READY   STATUS    RESTARTS   AGE
vault-0                                 1/1     Running   0          2m45s
vault-agent-injector-5989b47887-7wsdd   1/1     Running   0          2m45s

看到上面的两个 Vault 相关的 Pod 运行成功则证明已经安装成功了,所以安装是很方便的,接下来重点看下如何使用。

使用

配置 vault

假如现在有一个需求是希望 Vault 将数据库的用户名和密码存储在应用的 internal/database/config 路径下面,首先要创建 secret 需要线开启 kv secret 引擎,并将用户名和密码放在指定的路径中。

进入 vault-0 容器的命令行交互终端:

[mervinwang@kubelab ~]$ kubectl exec -it -n vault vault-0 /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
/ 

internal 路径下面开启 kv-v2 secrets 引擎:

/ $ vault secrets enable -path=internal kv-v2
Success! Enabled the kv-v2 secrets engine at: internal/

internal/exampleapp/config 路径下面添加一个用户名和密码的秘钥:

/ $ vault kv put internal/database/config username="db-readonly-username" password="db-secret-password"
Key                Value
---                -----
created_time       2022-01-17T09:52:19.712366013Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1
/ $ 

创建完成后可以通过如下命令校验上面创建的 secret:

/ $ vault kv get internal/database/config
======= Metadata =======
Key                Value
---                -----
created_time       2022-01-17T09:52:19.712366013Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
password    db-secret-password
username    db-readonly-username

这样我们就将用户名和密码信息存储在了 Vault 中,Vault 提供了一个 Kubernetes 认证的方法可以让客户端通过使用 Kubernetes ServiceAccount 进行身份认证。

开启 Kubernetes 认证

开启 Kubernetes 认证方式:

/ $ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/

Vault 会接受来自于 Kubernetes 集群中的任何客户端的服务 Token。在身份验证的时候,Vault 通过配置的 Kubernetes 地址来验证 ServiceAccount 的 Token 信息。通过 ServiceAccount 的 Token、Kubernetes 地址和 CA 证书信息配置 Kubernetes 认证方式:

/ $ vault write auth/kubernetes/config \
> token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
> kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \
> kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Success! Data written to: auth/kubernetes/config

其中 token_reviewer_jwtkubernetes_ca_cert 都是 Kubernetes 默认注入到 Pod 中的,而环境变量 KUBERNETES_PORT_443_TCP_ADDR 也是内置的表示 Kubernetes APIServer 的内网地址。为了让客户端读取上一步定义在 internal/database/config 路径下面的 secret 数据,还需要为该路径授予 read 的权限。

这里我们创建一个名为 internal-app 的策略名称,该策略会启用对路径 internal/database/config 中的 secret 的读取权限:

/ $ vault policy write internal-app - <<EOH
> path "internal/data/database/config" {
>   capabilities = ["read"]
> }
> EOH
Success! Uploaded policy: internal-app

然后创建一个名为 internal-app 的 Kubernetes 认证角色:

/ $ vault write auth/kubernetes/role/internal-app \
>   bound_service_account_names=internal-app \
>   bound_service_account_namespaces=default \
>   policies=internal-app \
> ttl=24h
Success! Data written to: auth/kubernetes/role/internal-app

该角色将 Kubernetes default 命名空间下面的名为 internal-app 的 ServiceAccount 与 Vault 的 internal-app 策略连接在了一起,认证后返回的 Token 有24小时的有效期。最后直接退出 vault-0

到这里 Vault 相关的准备工作已经完成了。

接下来就是如何在 Kubernetes 中来读取上面我们的 Secret 数据。上面我们在 default 命名空间下面定义了一个名为 internal-app 的 ServiceAccount,该对象还不存在,首先先创建:(vault-sa.yaml)

apiVersion: v1
kind: ServiceAccount
metadata:
  name: internal-app  # 需要和上面的 bound_service_account_names 一致
  namespace: default  # 需要和上面的 bound_service_account_namespaces 一致
[mervinwang@kubelab ~]$ kubectl apply -f vault-sa.yaml
serviceaccount/internal-app created
[mervinwang@kubelab ~]$ kubectl get sa | grep internal-app
internal-app   1         11s

部署测试

部署应用,在应用中使用上面创建的 sa 对象:(demo.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vault-demo
  labels:
    app: vault-demo
spec:
  selector:
    matchLabels:
      app: vault-demo
  template:
    metadata:
      labels:
        app: vault-demo
    spec:
      serviceAccountName: internal-app  # 使用上面创建的 serviceaccount 对象
      containers:
        - name: vault
          image: cnych/vault-demo:0.0.1

其中比较重要的就是 spec.template.spec.serviceAccountName 字段需要使用上面我们创建的名为 internal-app 的这个 ServiceAccount 资源对象,同样也是直接创建即可:

[mervinwang@kubelab ~]$ kubectl get po | grep vault
vault-demo-5fddc5d8c8-hf4jx            1/1     Running   0          47s

正常的情况是我们部署的 Vault 中的 vault-agent-injector 这个程序会去查找 Kubernetes 集群中部署应用的 annotations 属性进行处理,我们当前的 Deployment 中没有配置相关的信息,所以我们这里的 vault-demo-5fddc5d8c8-hf4jx 这个 Pod 中是获取不到任何 secret 数据的,可以通过如下所示的命令进行验证:

[mervinwang@kubelab ~]$ kubectl exec -it vault-demo-5fddc5d8c8-hf4jx -- ls /vault/secrets/
ls: /vault/secrets/: No such file or directory
command terminated with exit code 1

可以看到在容器中现在没有对应的 secret 数据。这个时候我们就需要通过 annotations 来添加一些获取 secret 数据的一些说明:(spec-patch.yaml)

spec:
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "internal-app"
        vault.hashicorp.com/agent-inject-secret-database-config.txt: "internal/data/database/config"

上面的 annotations 定义了部分 vault 相关的信息,都是以 vault.hashicorp.com 为前缀开头的信息:

  • agent-inject 用于标识启用 Vault Agent 注入服务
  • role 表示 Vault Kubernetes 身份验证的角色
  • agent-inject-secret-FILEPATH 为写入 /vault/secrets 的文件 database-config.txt 的路径上加上前缀,对应的值是 Vault 中定义的 secret 数据存储路径。

直接使用上面定义的 annotations 来给上面的 Deployment 打一个补丁:

[mervinwang@kubelab ~]$ kubectl patch deployment vault-demo --patch "$(cat spec-patch.yaml)"
deployment.apps/vault-demo patched

现在新的 Pod 中会包含两个容器,一个是我们定义的 vault-demo 容器,另一个是名为 vault-agent 的 Vault Agent 容器。在 Pod 中自动添加一个 vault-agent 的 Sidecar 容器其实也是利用了 Mutating Admission Webhook 来实现的,和 Istio 实现的机制是一样的:

[mervinwang@kubelab ~]$ kubectl get po | grep vault
vault-demo-646d7bb95b-xz4zp            2/2     Running   0          18m

通过vault在Kubernetes中注入密钥

现在我们可以查看 vault-agent 容器的日志

[mervinwang@kubelab ~]$ kubectl logs -f vault-demo-646d7bb95b-xz4zp vault-agent
==> Vault agent started! Log data will stream in below:

==> Vault agent configuration:

                     Cgo: disabled
               Log Level: info
                 Version: Vault v1.9.0

2022-01-17T10:08:45.408Z [INFO]  sink.file: creating file sink
2022-01-17T10:08:45.408Z [INFO]  sink.file: file sink configured: path=/home/vault/.vault-token mode=-rw-r-----
2022-01-17T10:08:45.408Z [INFO]  sink.server: starting sink server
2022-01-17T10:08:45.409Z [INFO]  template.server: starting template server
2022-01-17T10:08:45.409Z [INFO] (runner) creating new runner (dry: false, once: false)
2022-01-17T10:08:45.409Z [INFO] (runner) creating watcher
2022-01-17T10:08:45.409Z [INFO]  auth.handler: starting auth handler
2022-01-17T10:08:45.409Z [INFO]  auth.handler: authenticating
2022-01-17T10:08:45.441Z [INFO]  auth.handler: authentication successful, sending token to sinks
2022-01-17T10:08:45.441Z [INFO]  auth.handler: starting renewal process
2022-01-17T10:08:45.441Z [INFO]  sink.file: token written: path=/home/vault/.vault-token
2022-01-17T10:08:45.441Z [INFO]  template.server: template server received new token
2022-01-17T10:08:45.441Z [INFO] (runner) stopping
2022-01-17T10:08:45.441Z [INFO] (runner) creating new runner (dry: false, once: false)
2022-01-17T10:08:45.441Z [INFO] (runner) creating watcher
2022-01-17T10:08:45.441Z [INFO] (runner) starting
2022-01-17T10:08:45.445Z [INFO]  auth.handler: renewed auth token

vault-agent 容器会管理 Token 的整个生命周期和 secret 数据检索,我们定义的 secret 数据会被添加到应用容器的 /vault/secrets/database-config.txt 路径下面

[mervinwang@kubelab ~]$ kubectl exec -it vault-demo-646d7bb95b-xz4zp -c vault -- cat /vault/secrets/database-config.txt
data: map[password:db-secret-password username:db-readonly-username]
metadata: map[created_time:2022-01-17T09:52:19.712366013Z custom_metadata:<nil> deletion_time: destroyed:false version:1]
[mervinwang@kubelab ~]$ 

到这里 secret 数据就成功的存储在了我们的应用容器中。

[参考链接](

正文完
 
mervinwang
版权声明:本站原创文章,由 mervinwang 2022-06-10发表,共计8188字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
文章搜索