如何用Kubernetes设置Vault

267 阅读6分钟

在我们并不理想的世界里,我们倾向于将所有的应用程序的秘密(密码、API令牌)暴露在我们的源代码中。在众目睽睽之下储存秘密并不是一个好主意,不是吗?在DeepSource,我们已经接受了这个问题,从第一天起就在我们的基础设施中加入了一个强大的秘密管理系统。这篇文章解释了如何用HashiCorp Vault在Kubernetes中设置秘密管理。

什么是Vault?

Vault作为集中管理的服务,处理整个基础设施秘密的加密和存储。Vault管理秘密引擎中的所有秘密。Vault有一套秘密引擎供其使用,但为了简洁起见,我们将坚持使用kv(key-value)秘密引擎。

概述

Vault-Consul-Cluster

上述设计描述了一个三节点的Vault集群,其中有一个活动节点、两个备用节点和一个代表Vault节点与五节点的Consul服务器集群对话的Consul代理副车。该架构还可以扩展到一个多可用性区域,使你的集群具有高度的容错性。

你可能想知道为什么我们要使用Consul服务器,因为这个架构已经有点复杂,让人摸不着头脑。Vault需要一个后端来存储所有休息时的加密数据。它可以是你的文件系统后端、云提供商、数据库或Consul集群。

Consul的优势在于它具有容错性和高度可扩展性。通过使用Consul作为Vault的后端,你可以获得两者的优点。Consul用于持久存储静态加密数据,并提供协调,使Vault能够高度可用和容错。Vault提供更高级别的策略管理、秘密租赁、审计记录和自动撤销。

客户端通过HTTPS与Vault服务器对话,Vault服务器处理请求并将其转发给回环地址上的Consul代理。Consul客户端代理作为Consul服务器的接口,是非常轻量级的,并且自己维护的状态非常少。Consul服务器在静止状态下对秘密进行加密存储。

Consul服务器集群基本上是奇数的,因为它们需要使用共识协议来保持一致性和容错。共识协议主要是基于Raft的。寻找一个可以理解的共识算法。关于Raft的直观解释,你可以参考《数据的秘密生活》。

Kubernetes上的Vault--让你轻松摆脱操作的复杂性

DeepSource的几乎所有基础设施都在Kubernetes上运行。从分析运行到VPN基础设施,一切都在高度分布的环境中运行,而Kubernetes帮助我们实现了这一点。对于在Kubernetes中设置Vault,Hashicorp强烈建议使用Helm图表在Kubernetes上部署Vault和Consul,而不是使用世俗的清单。

前提条件

对于这个设置,我们需要安装kubectlHelm,以及一个本地minikube设置来进行部署。

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.3", GitCommit:"b3cbbae08ec52a7fc73d334838e18d17e8512749", GitTreeState:"clean", BuildDate:"2019-11-14T04:24:29Z", GoVersion:"go1.12.13", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"14+", GitVersion:"v1.14.8-gke.33", GitCommit:"2c6d0ee462cee7609113bf9e175c107599d5213f", GitTreeState:"clean", BuildDate:"2020-01-15T17:47:46Z", GoVersion:"go1.12.11b4", Compiler:"gc", Platform:"linux/amd64"}
$ helm version
version.BuildInfo{Version:"v3.0.1", GitCommit:"7c22ef9ce89e0ebeb7125ba2ebf7d421f3e82ffa", GitTreeState:"clean", GoVersion:"go1.13.4"}
$ minikube version
minikube version: v1.5.2
commit: 792dbf92a1de583fcee76f8791cff12e0c9440ad

设置

让我们把minikube启动并运行。

$ minikube start --memory 4096
😄  minikube v1.5.2 on Darwin 10.15.2
✨  Automatically selected the 'hyperkit' driver (alternates: [virtualbox])
🔥  Creating hyperkit VM (CPUs=2, Memory=4096MB, Disk=20000MB) ...
🐳  Preparing Kubernetes v1.16.2 on Docker '18.09.9' ...
🚜  Pulling images ...
🚀  Launching Kubernetes ...
⌛  Waiting for: apiserver
🏄  Done! kubectl is now configured to use "minikube"

--memory 被设置为4096MB,以确保有足够的内存来部署所有的资源。初始化过程需要几分钟,因为它检索了必要的依赖性,并开始下载多个容器镜像。

验证你的Minikube集群的状态。

$ minikube status
host: Running
kubelet: Running
apiserver: Running
kubeconfig: Configured

host,kubelet,apiserver 应该报告它们正在运行。在成功执行后,kubectl 将被自动配置为与这个最近启动的集群通信。

在Kubernetes上运行Vault的推荐方式是使用Helm图。这将安装和配置所有必要的组件,以便在几种不同的模式下运行Vault。让我们安装Vault Helm图表(本帖部署的是0.3.3版本),其前缀为vault 的荚。

$ helm install --name vault \
    --set "server.dev.enabled=true" \
    https://github.com/hashicorp/vault-helm/archive/v0.3.0.tar.gz
NAME:   vault
LAST DEPLOYED: Fri Feb 8 11:56:33 2020
NAMESPACE: default
STATUS: DEPLOYED

RESOURCES:
..

NOTES:
..

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

  $ helm status vault
  $ helm get vault

为了验证,获取默认命名空间内的所有pod。

$ kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
vault-0                                 1/1     Running   0          80s
vault-agent-injector-5945fb98b5-tpglz   1/1     Running   0          80s

创建一个秘密

你在后面的步骤中部署的应用程序希望Vault能存储一个用户名和密码,存储在路径internal/database/config 。要创建这个秘密,需要启用kv秘密引擎,并在指定的路径上放一个用户名和密码。

vault-0 pod上启动一个交互式shell会话。

$ kubectl exec -it vault-0 /bin/sh
/ $

你的系统提示符被替换成一个新的提示符/ $ 。在这个提示下发出的命令会在vault-0 容器上执行。

在路径内部启用kv-v2的秘密。

/ $ 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     2019-12-20T18:17:01.719862753Z
deletion_time    n/a
destroyed        false
version          1

验证秘密是否在路径internal/database/config 中被定义。

$ vault kv get internal/database/config
====== Metadata ======
Key              Value
---              -----
created_time     2019-12-20T18:17:50.930264759Z
deletion_time    n/a
destroyed        false
version          1

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

让Kubernetes熟悉Vault

Vault提供了一种Kubernetes认证方法,使客户能够使用Kubernetes服务账户令牌进行认证。

启用Kubernetes认证方法。

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

Vault接受来自Kubernetes集群内任何客户端的该服务令牌。在验证过程中,Vault通过查询配置的Kubernetes端点来验证服务账户令牌是否有效。

配置Kubernetes认证方法以使用服务账户令牌、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写给容器的文件。环境变量KUBERNETES_PORT_443_TCP_ADDR 引用了Kubernetes主机的内部网络地址。客户端要读取上一步定义的秘密数据,在internal/database/config,需要授予路径internal/data/database/config 的读取能力。

写出名为internal-app 的策略,使路径上的秘密有读取能力。internal/data/database/config

/ $ 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服务账户(internal-app )和命名空间(default )与Vault策略(internal-app )相连。认证后返回的令牌的有效期为24小时。

最后,退出vault-0 pod。

创建一个Kubernetes服务账户

Vault Kubernetes认证角色定义了一个名为internal-app 的Kubernetes服务账户。这个服务账户还不存在。

查看exampleapp-service-account.yml 中定义的服务账户。

$ kubectl get serviceaccounts
NAME                   SECRETS   AGE
default                1         43m
vault                  1         34m
vault-agent-injector   1         34m

应用服务账户定义来创建它。

$ kubectl apply --filename service-account-internal-app.yml
serviceaccount/internal-app created

验证服务账户是否已经创建:这里的服务账户名称与配置Kubernetes认证时创建internal-app 角色时分配给bound_service_account_names 字段的名称一致。

从sidecar到应用程序的秘密注入

查看orgchart应用程序的部署。

$ cat deployment-01-orgchart.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: orgchart
  labels:
    app: vault-agent-injector-demo
spec:
  selector:
    matchLabels:
      app: vault-agent-injector-demo
  replicas: 1
  template:
    metadata:
      annotations:
      labels:
        app: vault-agent-injector-demo
    spec:
      serviceAccountName: internal-app
      containers:
        - name: orgchart
          image: jweissig/app:0.0.1

新部署的名称是orgchartspec.template.spec.serviceAccountName 定义了在此容器下运行的服务账户internal-app

应用deployment-01-orgchart.yml 中定义的部署。

$ kubectl apply --filename deployment-01-orgchart.yml
deployment.apps/orgchart created

该应用程序在default 名称空间中作为一个pod运行。

获取default 命名空间内的所有pod。

$ kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
orgchart-69697d9598-l878s               1/1     Running   0          18s
vault-0                                 1/1     Running   0          58m
vault-agent-injector-5945fb98b5-tpglz   1/1     Running   0          58m

Vault-Agent注入器寻找定义特定注释的部署。在当前的部署中不存在这些注解。这意味着在orgchart-69697d9598-l878s pod内的orgchart容器上没有秘密存在。

验证是否有秘密被写入orgchart-69697d9598-l878s pod中的orgchart 容器。

$ kubectl exec orgchart-69697d9598-l878s --container orgchart -- ls /vault/secrets
ls: /vault/secrets: No such file or directory
command terminated with exit code 1

该部署正在使用默认命名空间中的internal-app Kubernetes服务账户运行pod。Vault Agent注入器只在部署包含一套非常具体的注释时才会修改。现有的部署可以对其定义进行修补,以包括必要的注释。

查看部署补丁deployment-02-inject-secrets.yml

$ cat deployment-02-inject-secrets.yml
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"

这些注解定义了部署模式的部分结构,并以vault.hashicorp.com为前缀。

  • agent-inject 启用Vault代理注入器服务
  • role 是Vault Kubernetes认证角色
  • role 是创建的Vault角色,映射回K8s服务账户
  • agent-inject-secret-FIlEPATH 前缀是文件的路径, 写到 。数值是Vault中定义的秘密的路径。database-config.txt /vault/secrets

对定义在deployment-02-inject-secrets.yml 中的orgchart部署进行修补。

$ kubectl patch deployment orgchart --patch "$(cat deployment-02-inject-secrets.yml)"
deployment.apps/orgchart patched

这个新的pod现在启动了两个容器。应用程序容器,命名为orgchart ,和Vault代理容器,命名为vault-agent

查看orgchart-599cb74d9c-s8hhm pod中的Vault-agent容器的日志。

$ kubectl logs orgchart-599cb74d9c-s8hhm --container vault-agent
==> Vault server started! Log data will stream in below:

==> Vault agent configuration:

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

2019-12-20T19:52:36.658Z [INFO]  sink.file: creating file sink
2019-12-20T19:52:36.659Z [INFO]  sink.file: file sink configured: path=/home/vault/.token mode=-rw-r-----
2019-12-20T19:52:36.659Z [INFO]  template.server: starting template server
2019/12/20 19:52:36.659812 [INFO] (runner) creating new runner (dry: false, once: false)
2019/12/20 19:52:36.660237 [INFO] (runner) creating watcher
2019-12-20T19:52:36.660Z [INFO]  auth.handler: starting auth handler
2019-12-20T19:52:36.660Z [INFO]  auth.handler: authenticating
2019-12-20T19:52:36.660Z [INFO]  sink.server: starting sink server
2019-12-20T19:52:36.679Z [INFO]  auth.handler: authentication successful, sending token to sinks
2019-12-20T19:52:36.680Z [INFO]  auth.handler: starting renewal process
2019-12-20T19:52:36.681Z [INFO]  sink.file: token written: path=/home/vault/.token
2019-12-20T19:52:36.681Z [INFO]  template.server: template server received new token
2019/12/20 19:52:36.681133 [INFO] (runner) stopping
2019/12/20 19:52:36.681160 [INFO] (runner) creating new runner (dry: false, once: false)
2019/12/20 19:52:36.681285 [INFO] (runner) creating watcher
2019/12/20 19:52:36.681342 [INFO] (runner) starting
2019-12-20T19:52:36.692Z [INFO]  auth.handler: renewed auth token

Vault Agent管理令牌的生命周期和秘密的检索。秘密在orgchart容器中呈现,路径是/vault/secrets/database-config.txt

最后,查看写入orgchart容器中的秘密。

$ kubectl exec orgchart-599cb74d9c-s8hhm --container orgchart -- cat /vault/secrets/database-config.txt
data: map[password:db-secret-password username:db-readonly-user]
metadata: map[created_time:2019-12-20T18:17:50.930264759Z deletion_time: destroyed:false version:2]

秘密成功地出现在容器中。注入容器中的秘密可以进一步被模板化,以适应应用程序的需要。

现在,你应该很好地评估Vault在高度动态的云原生基础设施中的意义,消除管理应用程序和服务秘密的操作开销,允许你的基础设施优雅地扩展。