Vault 是 hashicorp 推出的 secrets 管理、加密即服务与权限管理工具。它的功能简介以下:node
在使用 Vault 以前,咱们是以携程开源的 Apollo 做为微服务的分布式配置中心。python
Apollo 在国内很是流行。它功能强大,支持配置的继承,也有提供 HTTP API 方便自动化。
缺点是权限管理和 secrets 管理比较弱,也不支持信息加密,不适合直接存储敏感信息。所以咱们如今切换到了 Vault.mysql
目前咱们本地的 CI/CD 流水线和云上的微服务体系,都是使用的 Vault 作 secrets 管理.git
首先看一下 Vault 的架构图:github
能够看到,几乎全部的组件都从属于「安全屏障(security barrier)」,
Vault 能够简单地被划分为 Storage Backend、安全屏障(security barrier) 和 HTTP API 三个部分。web
「安全屏障(security barrier)」是 Vault(金库) 周围的加密「钢铁」和「混凝土」,Storage Backend 和 Vault 之间的全部数据流动都须要通过「屏障(barrier)」。
barrier 确保只有加密数据会被写入 Storage Backend,加密数据在通过 barrier 的过程当中被验证与解密。
和银行金库(bank vault)很是相似,barrier 也必须先解封,才能容许读取内部的数据。算法
Storage Backend(后端存储): Vault 自身不存储数据,所以须要为它配置一个「Storage Backend」。
「Storage Backend」是不受信任的,只用于存储加密数据。sql
Initialaztion(初始化): vault 在首次启动时须要初始化,这一步生成一个「加密密钥(encryption key)」用于加密数据,加密完成的数据才能被保存到 Storage Backend.docker
Unseal(解封): Vault 启动后,由于不知道「加密密钥(encryption key)」,它会进入「封印(sealed)」状态,在「Unseal」前没法进行任何操做。
「加密密钥」被「master key」保护,咱们必须提供「master key」才能完成 Unseal 操做。
默认状况下,vault 使用沙米尔密钥共享算法
将「master key」分割成五个「Key Shares(分享密钥)」,必需要提供其中任意三个「Key Shares」才能重建出「master key」从而完成 Unseal.
「Key Shares」的数量,以及重建「master key」最少须要的 key shares 数量,都是能够调整的。
沙米尔密钥共享算法也能够关闭,这样 master key 将被直接用于 Unseal.
在解封完成后,Vault 就能够开始处理请求了。
HTTP 请求进入后的整个处理流程都由 vault core 管理,core 会强制进行 ACL 检查,并确保审计日志(audit logging)完成记录。
客户端首次链接 vault 时,须要先完成身份认证,vault 的「auth methods」模块有不少身份认证方法可选:
身份验证请求流经 Core 并进入 auth methods,auth methods 肯定请求是否有效并返回「关联策略(policies)」的列表。
ACL Policies 由 policy store 负责管理与存储,由 core 进行 ACL 检查。
ACL 的默认行为是拒绝,这意味着除非明确配置 Policy 容许某项操做,不然该操做将被拒绝。
在经过 auth methods 完成了身份认证,而且返回的「关联策略」也没毛病以后,「token store」将会生成并管理一个新的 token,
这个 token 会被返回给客户端,用于进行后续请求。
相似 web 网站的 cookie,token 也都存在一个 lease 租期或者说有效期,这增强了安全性。
token 关联了相关的策略 policies,策略将被用于验证请求的权限。
请求通过验证后,将被路由到 secret engine。若是 secret engine 返回了一个 secret(由 vault 自动生成的 secret),
Core 会将其注册到 expiration manager,并给它附加一个 lease ID。lease ID 被客户端用于更新(renew)或吊销(revoke)它获得的 secret.
若是客户端容许租约(lease)到期,expiration manager 将自动吊销这个 secret.
Core 负责处理审核代理(audit brok)的请求及响应日志,将请求发送到全部已配置的审核设备(audit devices)。
Secret Engine 是保存、生成或者加密数据的组件,它很是灵活。
有的 Secret Engines 只是单纯地存储与读取数据,好比 kv 就能够看做一个加密的 Redis。
而其余的 Secret Engines 则链接到其余的服务并按需生成动态凭证。
还有些 Secret Engines 提供「加密即服务(encryption as a service)」 - transit、证书管理等。
经常使用的 engine 举例:
官方建议经过 Helm 部署 vault,大概流程:
推荐用于本地开发测试环境,或者其余不须要高可用的环境。
docker-compose.yml
示例以下:
version: '3.3' services: vault: # 文档:https://hub.docker.com/_/vault image: vault:1.6.0 container_name: vault ports: # rootless 容器,内部不能使用标准端口 443 - "443:8200" restart: always volumes: # 审计日志存储目录,默认不写审计日志,启用 `file` audit backend 时必须提供一个此文件夹下的路径 - ./logs:/vault/logs # 当使用 file data storage 插件时,数据被存储在这里。默认不往这写任何数据。 - ./file:/vault/file # 配置目录,vault 默认 `/valut/config/` 中全部以 .hcl/.json 结尾的文件 # config.hcl 文件内容,参考 cutom-vaules.yaml - ./config.hcl:/vault/config/config.hcl # TLS 证书 - ./certs:/certs # vault 须要锁定内存以防止敏感值信息被交换(swapped)到磁盘中 # 为此须要添加以下能力 cap_add: - IPC_LOCK # 必须手动设置 entrypoint,不然 vault 将以 development 模式运行 entrypoint: vault server -config /vault/config/config.hcl
config.hcl
内容以下:
ui = true // 使用文件作数据存储(单节点) storage "file" { path = "/vault/file" } listener "tcp" { address = "[::]:8200" tls_disable = false tls_cert_file = "/certs/server.crt" tls_key_file = "/certs/server.key" }
将如上两份配置保存在同一非文件夹内,同时在 ./certs
中提供 TLS 证书 server.crt
和私钥 server.key
。
而后 docker-compose up -d
就能启动运行一个 vault 实例。
推荐用于生产环境
经过 helm 部署:
# 添加 valut 仓库 helm repo add hashicorp https://helm.releases.hashicorp.com # 查看 vault 版本号 helm search repo hashicorp/vault -l | head # 下载某个版本号的 vault helm pull hashicorp/vault --version 0.9.0 --untar
参照下载下来的 ./vault/values.yaml
编写 custom-values.yaml
,
部署一个以 mysql
为后端存储的 HA vault,配置示例以下:
global: # enabled is the master enabled switch. Setting this to true or false # will enable or disable all the components within this chart by default. enabled: true # TLS for end-to-end encrypted transport tlsDisable: false injector: # True if you want to enable vault agent injection. enabled: true replicas: 1 # If multiple replicas are specified, by default a leader-elector side-car # will be created so that only one injector attempts to create TLS certificates. leaderElector: enabled: true image: repository: "gcr.io/google_containers/leader-elector" tag: "0.4" ttl: 60s # If true, will enable a node exporter metrics endpoint at /metrics. metrics: enabled: false # Mount Path of the Vault Kubernetes Auth Method. authPath: "auth/kubernetes" certs: # secretName is the name of the secret that has the TLS certificate and # private key to serve the injector webhook. If this is null, then the # injector will default to its automatic management mode that will assign # a service account to the injector to generate its own certificates. secretName: null # caBundle is a base64-encoded PEM-encoded certificate bundle for the # CA that signed the TLS certificate that the webhook serves. This must # be set if secretName is non-null. caBundle: "" # certName and keyName are the names of the files within the secret for # the TLS cert and private key, respectively. These have reasonable # defaults but can be customized if necessary. certName: tls.crt keyName: tls.key server: # Resource requests, limits, etc. for the server cluster placement. This # should map directly to the value of the resources field for a PodSpec. # By default no direct resource request is made. # authDelegator enables a cluster role binding to be attached to the service # account. This cluster role binding can be used to setup Kubernetes auth # method. https://www.vaultproject.io/docs/auth/kubernetes.html authDelegator: enabled: true # Enables a headless service to be used by the Vault Statefulset service: enabled: true # clusterIP controls whether a Cluster IP address is attached to the # Vault service within Kubernetes. By default the Vault service will # be given a Cluster IP address, set to None to disable. When disabled # Kubernetes will create a "headless" service. Headless services can be # used to communicate with pods directly through DNS instead of a round robin # load balancer. # clusterIP: None # Configures the service type for the main Vault service. Can be ClusterIP # or NodePort. #type: ClusterIP # If type is set to "NodePort", a specific nodePort value can be configured, # will be random if left blank. #nodePort: 30000 # Port on which Vault server is listening port: 8200 # Target port to which the service should be mapped to targetPort: 8200 # This configures the Vault Statefulset to create a PVC for audit # logs. Once Vault is deployed, initialized and unseal, Vault must # be configured to use this for audit logs. This will be mounted to # /vault/audit # See https://www.vaultproject.io/docs/audit/index.html to know more auditStorage: enabled: false # Size of the PVC created size: 10Gi # Name of the storage class to use. If null it will use the # configured default Storage Class. storageClass: null # Access Mode of the storage device being used for the PVC accessMode: ReadWriteOnce # Annotations to apply to the PVC annotations: {} # Run Vault in "HA" mode. There are no storage requirements unless audit log # persistence is required. In HA mode Vault will configure itself to use Consul # for its storage backend. The default configuration provided will work the Consul # Helm project by default. It is possible to manually configure Vault to use a # different HA backend. ha: enabled: true replicas: 3 # Set the api_addr configuration for Vault HA # See https://www.vaultproject.io/docs/configuration#api_addr # If set to null, this will be set to the Pod IP Address apiAddr: null # config is a raw string of default configuration when using a Stateful # deployment. Default is to use a Consul for its HA storage backend. # This should be HCL. # Note: Configuration files are stored in ConfigMaps so sensitive data # such as passwords should be either mounted through extraSecretEnvironmentVars # or through a Kube secret. For more information see: # https://www.vaultproject.io/docs/platform/k8s/helm/run#protecting-sensitive-vault-configurations config: | ui = true listener "tcp" { address = "[::]:8200" cluster_address = "[::]:8201" tls_disable = false tls_cert_file = "/etc/certs/vault.crt" tls_key_file = "/etc/certs/vault.key" } storage "mysql" { address = "<host>:3306" username = "<username>" password = "<password>" database = "vault" ha_enabled = "true" } service_registration "kubernetes" {} # Example configuration for using auto-unseal, using Google Cloud KMS. The # GKMS keys must already exist, and the cluster must have a service account # that is authorized to access GCP KMS. #seal "gcpckms" { # project = "vault-helm-dev-246514" # region = "global" # key_ring = "vault-helm-unseal-kr" # crypto_key = "vault-helm-unseal-key" #} # Vault UI ui: # True if you want to create a Service entry for the Vault UI. # # serviceType can be used to control the type of service created. For # example, setting this to "LoadBalancer" will create an external load # balancer (for supported K8S installations) to access the UI. enabled: true publishNotReadyAddresses: true # The service should only contain selectors for active Vault pod activeVaultPodOnly: false serviceType: "ClusterIP" serviceNodePort: null externalPort: 8200
如今使用自定义的 custom-values.yaml
部署 vautl:
kubectl create namespace vault # 安装/升级 valut helm upgrade --install vault ./vault --namespace vault -f custom-values.yaml
官方文档:Initialize and unseal Vault - Vault on Kubernetes Deployment Guide
经过 helm 部署 vault,默认会部署一个三副本的 StatefulSet,可是这三个副本都会处于 NotReady 状态(docker 方式部署的也同样)。
接下来还须要手动初始化(initalize)并解封(unseal) vault,才能 Ready
:
kubectl exec -ti vault-0 -- vault operator init
# 每一个实例都须要解封三次! ## Unseal the first vault server until it reaches the key threshold $ kubectl exec -ti vault-0 -- vault operator unseal # ... Unseal Key 1 $ kubectl exec -ti vault-0 -- vault operator unseal # ... Unseal Key 2 $ kubectl exec -ti vault-0 -- vault operator unseal # ... Unseal Key 3
这样就完成了部署,可是要注意,vault 实例每次重启后,都须要从新解封!也就是从新进行第二步操做!
每次重启都要手动解封全部 vault 实例,实在是很麻烦,在云上自动扩缩容的状况下,vault 实例会被自动调度,这种状况就更麻烦了。
为了简化这个流程,能够考虑配置 auto unseal 让 vault 自动解封。
自动解封目前有两种方法:
简单起见,也能够写个 crontab 或者在 CI 平台上加个定时任务去执行解封命令,以实现自动解封。
Vault 自己是一个复杂的 secrets 工具,它提供了 Web UI 和 CLI 用于手动管理与查看 Vault 的内容。
可是做为一名 DevOps,咱们固然更喜欢自动化的方法,这有两种选择:
Web UI 适合手工操做,而 sdk/terraform-provider-vault
则适合用于自动化管理 vault.
咱们的测试环境就是使用 pulumi-vault
完成的自动化配置 vault policy 和 kubernetes role,而后自动化注入全部测试用的 secrets.
使用 pulumi 管理 vault 配置的优点是很大的,由于云上资源的敏感信息(数据库帐号密码、资源 ID、RAM子帐号)都是 pulumi 建立的。
再结合使用 pulumi_valut,就能实现敏感信息自动生成后,当即保存到 vault 中,实现彻底自动化。
后续微服务就能够经过 kubernetes 认证,直接从 vault 读取敏感信息。
或者是写入到本地的 vault 中留作备份,在须要的时候,管理员能登入进去查看相关敏感信息。
pulumi_vault 自己挺简单的,声明式的配置嘛,直接用就是了。
可是它必定要求提供 VAULT_TOKEN
做为身份认证的凭证(实测 userpass/approle 都不能直接使用,会报错 no vault token found
),并且 pulumi 还会先生成临时用的 child token,而后用这个 child token
进行后续的操做。
首先安全起见,确定不该该直接提供 root token!root token 应该封存,除了紧急状况不该该启用。
那么应该如何生成一个权限有限的 token 给 vault 使用呢?
个人方法是建立一个 userpass 帐号,经过 policy 给予它有限的权限。
而后先手动(或者自动)登陆获取到 token,再将 token 提供给 pulumi_vault 使用。
这里面有个坑,就是必须给 userpass 帐号建立 child token 的权限:
path "local/*" { capabilities = ["read", "list"] } // 容许建立 child token path "auth/token/create" { capabilities = ["create", "read", "update", "delete", "list"] }
不给这个权限,pulumi_vault 就会一直报错。。
前面提到过 vault 支持经过 Kubernetes 的 ServiceAccount + Role 为每一个 Pod 单独分配权限。
首先启用 Vault 的 Kubernetes 身份验证:
# 配置身份认证须要在 vault pod 中执行,启动 vault-0 的交互式会话 kubectl exec -n vault -it vault-0 -- /bin/sh export VAULT_TOKEN='<your-root-token>' export VAULT_ADDR='http://localhost:8200' # 启用 Kubernetes 身份验证 vault auth enable kubernetes # kube-apiserver API 配置,vault 须要经过 kube-apiserver 完成对 serviceAccount 的身份验证 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
若是你没这个需求,请跳过这一节。
详见 Install the Vault Helm chart configured to address an external Vault
kubernetes 也能够和外部的 vault 实例集成,集群中只部署 vault-agent.
这适用于多个 kubernetes 集群以及其余 APP 共用一个 vault 实例的状况,好比咱们本地的多个开发测试集群,就都共用着同一个 vault 实例,方便统一管理应用的 secrets.
首先,使用 helm chart 部署 vault-agent,接入外部的 vault 实例。使用的 custom-values.yaml
示例以下:
global: # enabled is the master enabled switch. Setting this to true or false # will enable or disable all the components within this chart by default. enabled: true # TLS for end-to-end encrypted transport tlsDisable: false injector: # True if you want to enable vault agent injection. enabled: true replicas: 1 # If multiple replicas are specified, by default a leader-elector side-car # will be created so that only one injector attempts to create TLS certificates. leaderElector: enabled: true image: repository: "gcr.io/google_containers/leader-elector" tag: "0.4" ttl: 60s # If true, will enable a node exporter metrics endpoint at /metrics. metrics: enabled: false # External vault server address for the injector to use. Setting this will # disable deployment of a vault server along with the injector. # TODO 这里的 https ca.crt 要怎么设置?mTLS 又该如何配置? externalVaultAddr: "https://<external-vault-url>" # Mount Path of the Vault Kubernetes Auth Method. authPath: "auth/kubernetes" certs: # secretName is the name of the secret that has the TLS certificate and # private key to serve the injector webhook. If this is null, then the # injector will default to its automatic management mode that will assign # a service account to the injector to generate its own certificates. secretName: null # caBundle is a base64-encoded PEM-encoded certificate bundle for the # CA that signed the TLS certificate that the webhook serves. This must # be set if secretName is non-null. caBundle: "" # certName and keyName are the names of the files within the secret for # the TLS cert and private key, respectively. These have reasonable # defaults but can be customized if necessary. certName: tls.crt keyName: tls.key
部署命令和 经过 helm 部署 vault 一致,只要更换 custom-values.yaml
就行。
vault-agent 部署完成后,第二步是为 vault 建立 serviceAccount、secret 和 ClusterRoleBinding,以容许 vault 审查 kubernetes 的 token, 完成对 pod 的身份验证. yaml 配置以下:
--- apiVersion: v1 kind: ServiceAccount metadata: name: vault-auth namespace: vault --- apiVersion: v1 kind: Secret metadata: name: vault-auth namespace: vault annotations: kubernetes.io/service-account.name: vault-auth type: kubernetes.io/service-account-token --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: role-tokenreview-binding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:auth-delegator subjects: - kind: ServiceAccount name: vault-auth namespace: vault
如今在 vault 实例这边,启用 kubernetes 身份验证,在 vault 实例内,执行以下命令:
vault 实例内显然没有 kubectl 和 kubeconfig,简便起见,下列的 vault 命令也能够经过 Web UI 完成。
export VAULT_TOKEN='<your-root-token>' export VAULT_ADDR='http://localhost:8200' # 启用 Kubernetes 身份验证 vault auth enable kubernetes # kube-apiserver API 配置,vault 须要经过 kube-apiserver 完成对 serviceAccount 的身份验证 # TOKEN_REVIEW_JWT: 就是咱们前面建立的 secret `vault-auth` TOKEN_REVIEW_JWT=$(kubectl -n vault get secret vault-auth -o go-template='{{ .data.token }}' | base64 --decode) # kube-apiserver 的 ca 证书 KUBE_CA_CERT=$(kubectl -n vault config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' | base64 --decode) # kube-apiserver 的 url KUBE_HOST=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.server}') vault write auth/kubernetes/config \ token_reviewer_jwt="$TOKEN_REVIEW_JWT" \ kubernetes_host="$KUBE_HOST" \ kubernetes_ca_cert="$KUBE_CA_CERT"
这样,就完成了 kubernetes 与外部 vault 的集成!
接下来须要作的事:
其中第一步和第二步均可以经过 vault api 自动化完成.
第三步能够经过 kubectl 部署时完成。
方便起见,vault policy / role / k8s serviceaccount 这三个配置,都建议和微服务使用相同的名称。
上述配置中,role 起到一个承上启下的做用,它关联了 k8s serviceaccount 和 vault policy 两个配置。
好比建立一个名为 my-app-policy
的 vault policy,内容为:
# 命名规则:"<engine-name>/data/<path>/*" path "my-app/data/*" { # 只读权限 capabilities = ["read", "list"] }
而后在 vault 中建立 k8s role my-app-role
:
my-app-account
,并建立好这个 serviceaccount.my-app-policy
这以后,每一个微服务就能经过 serviceaccount 从 vault 中读取 my-app
中的全部信息了。
下一步就是将配置注入到微服务容器中,这须要使用到 Agent Sidecar Injector。
vault 经过 sidecar 实现配置的自动注入与动态更新。
具体而言就是在 Pod 上加上一堆 Agent Sidecar Injector 的注解,若是配置比较多,也可使用 configmap 保存,在注解中引用。
须要注意的是 vault-inject-agent 有两种运行模式:
示例:
apiVersion: apps/v1 kind: Deployment metadata: labels: app: my-app name: my-app namespace: default spec: minReadySeconds: 3 progressDeadlineSeconds: 60 revisionHistoryLimit: 3 selector: matchLabels: app: my-app strategy: rollingUpdate: maxUnavailable: 1 type: RollingUpdate template: metadata: annotations: vault.hashicorp.com/agent-configmap: my-app-vault-config # vault 的 hcl 配置 vault.hashicorp.com/agent-init-first: 'true' # 是否提早初始化 vault.hashicorp.com/agent-inject: 'true' vault.hashicorp.com/agent-limits-cpu: 250m vault.hashicorp.com/agent-requests-cpu: 100m vault.hashicorp.com/secret-volume-path: /app/secrets labels: app: my-app spec: containers: - image: registry.svc.local/xx/my-app:latest imagePullPolicy: IfNotPresent # 此处省略若干配置... serviceAccountName: my-app-account
常见错误:
namespace not authorized
auth/kubernetes/config
中的 role 没有绑定 Pod 的 namespacepermission denied
vault
实例的日志,应该有对应的错误日志,极可能是 auth/kubernetes/config
没配对,vault 没法验证 kube-apiserver 的 tls 证书,或者使用的 kubernetes token 没有权限。service account not authorized
auth/kubernetes/config
中的 role 没有绑定 Pod 使用的 serviceAccountvault-agent 的配置,须要注意的有:
config.hcl
配置,注意 agent-init
vautl-agent 的 template 说明:
目前来讲最流行的配置文件格式应该是 json/yaml,以 json 为例,
对每一个微服务的 kv 数据,能够考虑将它全部的个性化配置都保存在 <engine-name>/<service-name>/
下面,而后使用以下 template 注入配置(注意这里使用了自定义的左右分隔符 [[
和 ]]
):
{ [[ range secrets "<engine-name>/metadata/<service-name>/" ]] "[[ printf "%s" . ]]": [[ with secret (printf "<engine-name>/<service-name>/%s" .) ]] [[ .Data.data | toJSONPretty ]], [[ end ]] [[ end ]] }
template 的详细语法参见: https://github.com/hashicorp/consul-template#secret
注意:v2 版本的 kv secrets,它的 list 接口有变动,所以在遍历 v2 kv secrets 时,
必需要写成range secrets "<engine-name>/metadata/<service-name>/"
,也就是中间要插入metadata
。
官方文档彻底没提到这一点,我经过 wireshark 抓包调试,对照官方的 KV Secrets Engine - Version 2 (API) 才搞明白这个。
这样生成出来的内容将是 json 格式,不过有个不兼容的地方:最后一个 secrets 的末尾有逗号 ,
渲染出的效果示例:
{ "secret-a": { "a": "b", "c": "d" }, "secret-b": { "v": "g", "r": "c" }, }
由于存在尾部逗号(trailing comma),直接使用 json 标准库解析它会报错。
那该如何去解析它呢?我在万能的 stackoverflow 上找到了解决方案:yaml 彻底兼容 json 语法,而且支持尾部逗号!
以 python 为例,直接 yaml.safe_load()
就能完美解析 vault 生成出的 json 内容。
vault 能够接入各大云厂商的帐号体系,显然能够用于管理阿里云的, 实现 ACCESS_KEY/SECRET_KEY 的自动轮转。
具体配置方法,待续。。。