vault

Vault使用场景

数据加密

Vault中的所有用户数据在保存和传输时都必须加密。secrets engine用于解决在数据保存、数据传输和为外部系统生成数据时遇到的挑战。如kv secrets engine可以用于数据保存,将数据保存到物理存储上。而transmit secret engine可以用于数据的加密传输,但这种secret engine不会保存任何数据。

image

访问控制

Vault提供了多种访问控制能力,如ACL、control groups和sentinel策略。此外,Vault还可以提供动态凭据管理能力。

  • ACL策略:以声明的方式提供了一种允许或禁止访问以及操作Vault特定路径的方式。
  • Control groups:提供了额外的授权因素,只有通过授权的请求才能被Vault处理
  • Sentinel策略:支持复杂的逻辑,以角色治理策略(RGP,绑定到特定令牌、身份实体或身份群体)和终端治理策略(Endpoint Governing Policies (EGP),绑定到特定路径)的形式增强访问控制
image

有时间限制的访问

Vault通过将TTL和相关凭据关联的方式来实现有时间限制的访问。支持在服务层面和插件层面或角色层面配置TTL。

当一个用户使用预先定义的角色以及用户名和密码进行认证时,会接收到一个Vault token和一个附加的TTL值。

image

灾备恢复

可以主备集群以及Automated data snapshots实现灾备恢复。

image

基于身份(Identity)的安全性

身份可以帮助Vault通过实体(Entity)或别名(Aliases)识别client,实体或别名绑定到启用策略的token。Vault可以通过identity secrets engine提供身份管理方案。

image

人类和机器认证

auth methods属于Vault的一种插件,负责为一个用户分配认证所需的身份和策略。

  • 人类auth methods包括GitHub、LDAP和 userpass。
  • 机器auth methods包括AppRole、AWS、Kubernetes 和 TLS。

大部分情况下,Vault会将身份认证管理和决策委托给相关配置好的外部身份auth methods,如Amazon Web Services、GitHub、Kubernetes等。

image

静态和动态secrets的Secrets engines

Secrets engines适用于存储、生成和加密数据的插件,secrets engines可以管理两种类型的secrets:静态和动态secrets。

静态secrets不会过期,若要修改此类secrets,则需要人工介入。静态secrets包括第三方tokens、API keys、应用keys、PKI证书、PGP keys、加密keys、用户名和密码。

动态secret是指一定时间后需要吊销的secret。动态secrets通常会和第三方平台进行集成,从Vault接收请求并生成凭据。动态secrets包括数据库keys、云提供商的凭据以及短期secrets。

image

Install

$ brew tap hashicorp/tap
$ brew install hashicorp/tap/vault
$ brew upgrade hashicorp/tap/vault

Setup

使用如下命令启动vault:

$ vault server -dev -dev-root-token-id root -dev-tls

在新窗口中执行上面命令打印的vault地址和证书:

export VAULT_ADDR='https://127.0.0.1:8200'
export VAULT_CACERT='/var/folders/hp/nqbj0l8x0jj8jfhh_6_62f6w0000gp/T/vault-tls3057506928/vault-ca.pem'

验证vault状态:

$ vault status

登陆:

$ vault login root

plugin

image

Vault支持3种插件:auth methods, secret engines和 database plugins,这些插件分为内置插件和外部插件两种。可以使用plugin_directory选项加载外部插件。

Vault Integrations给出了支持的插件列表。

Token

在客户端认证成功之后,vault会颁发一个token,用于校验客户端的访问以及客户端可以执行的操作。

使用vault cli执行命令前,需要设置通过 vault login登陆或设置VAULT_TOKEN 环境变量。

image

Token元数据

一个token有几个重要属性:

  • Token duration:token的有效时间,默认为32天。只有root token可以不设置有效时间
  • Accessor:token查找、更新和吊销使用的唯一ID,但不能用于登陆vault。token创建之后是无法被查找的,可以使用accessor来间接操作token。
  • Policies:表示通过该token授权的vault操作,可以设置一个或多个策略
$ vault token create -period=30m
Key                  Value
---                  -----
token                hvs.qp5hXcAcllG6ytHtZXEBxIeB
token_accessor       eYQSFsK1pIuhgrG1zkNxj2Tj
token_duration       30m
token_renewable      true
token_policies       ["root"]
identity_policies    []
policies             ["root"]

Token类型

主要有两种类型:service tokens 和 batch tokens,大部分场景下使用service tokens就可以了。service tokens的格式为hvs.string,如上面的hvs.qp5hXcAcllG6ytHtZXEBxIeB

orphan tokens

当使用一个token创建新的token时,新创建出来的token会作为该token的子token,当吊销一个父token后,其所有的子tokens也会被吊销。如果一个token没有父token,则这类token被称为orphan token。

Policies

Vault的策略用于允许或禁止访问某些权限,由于Vault本身是基于路径的,因此在编写策略时,只需要控制对特定路径的操作即可。

在用户或服务通过Vault认证后,会给对应的token绑定一个策略。需要注意的是,任何时候都可以修改策略,但对于一个已经颁发的token,修改后的策略并不能直接生效到该token上,必须通过吊销token并重新和Vault认证来接收更新后的策略。

策略优先级

策略越具体,则优先级越高,例如下面第一条策略允许在secret/data/creds路径下执行createupdate操作,但由于第二条策略更具体,其优先级更高,因此,在secret/data/creds/confidential路径下只能执行read操作:

#第一条策略
path "secret/data/creds" {
  capabilities = ["create", "update"]
}

#第二条策略
path "secret/data/creds/confidential" {
  capabilities = ["read"]
}
路径相同

若路径相同,则deny优先:

# Vault policy to allow access to the dev-secrets k/v v2 secrets engine
path "dev-secrets/+/root" {
  capabilities = ["read"]
}

path "dev-secrets/+/root" {
  capabilities = ["deny"]
}

如果没有deny策略且路径相同,则合并策略,下面允许在路径下执行listreadcreateupdate操作:

# Vault policy to allow access to the dev-secrets k/v v2 secrets engine
path "dev-secrets/+/root" {
  capabilities = ["list", "read"]
}

path "dev-secrets/+/root" {
  capabilities = ["create", "update"]
}

策略通配符

使用通配符,可以允许访问dev-secrets/data/creds-webapp之类的路径,注意*只能用在路径末尾:

# Vault policy to allow access to the dev-secrets k/v v2 secrets engine
path "dev-secrets/data/creds*" {
  capabilities = ["create", "list", "read", "update"]
}

若要匹配中间路径,不能使用dev-secrets/*/creds,应该使用+:

# Vault policy to allow access to the dev-secrets k/v v2 secrets engine
path "dev-secrets/+/creds" {
  capabilities = ["create", "list", "read", "update"]
}

使用显示deny策略

下面策略允许在除dev-secrets/data/root以外的dev-secrets路径上执行create, list, readupdate操作:

# Vault policy to allow access to the dev-secrets k/v v2 secrets engine
path "dev-secrets/+/*" {
  capabilities = ["create", "list", "read", "update"]
}

path "dev-secrets/+/root" { #更具体,优先级更高
  capabilities = ["deny"]
}

Role

Vault的role用于给auth method或secret engine添加更多配置。但并不是所有auth methods和secret engines都支持role,如userpass auth method就不支持role。

可以使用如下方式列出所有的role:

$ vault list auth/{auth_method}/role

下面为kubernetes auth method创建一个role。

首先启用Kubernetes auth method:

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

提供连接kubernetes所需的配置:

$ vault write auth/kubernetes/config \
    token_reviewer_jwt="$K8S_SERVICE_ACCOUNT_TOKEN" \
    kubernetes_host=https://192.168.99.100:443 \
    kubernetes_ca_cert=@ca.crt

创建一个名为hashicupsApp的role,在除该auth method要求的配置外,还提供了颁发token所需的policiesttlexplicit-max-ttl

$ vault write auth/kubernetes/role/hashicupsApp \
     bound_service_account_names=k8sHashicupsAppSA \
     bound_service_account_namespaces=k8sDevNamespace \
     policies=default,dev-secrets \
     ttl=1h \
     explicit-max-ttl=2h

Auth 和secret engine的区别

Vault使用场景中可以看到,auth 是 Vault 中用于验证身份的机制。它允许用户、服务、应用程序等 以某种方式登录 Vault,获取一个 token 来使用 Vault 的功能。而secrets 是 Vault 中用于 生成、存储和管理敏感数据的模块,称为Secrets Engine。

所有auth的mount地址都以auth/开头。

AppRole

用于给机器或apps提供认证。

image

关键参数

RoleID 和 SecretID类似机器或qpp认证所使用的用户名和密码。

  • RoleID:当使用AppRole方式登陆endpoint时,需要输入RoleID(role_id)
  • SecretID:默认登陆时需要通过secret_id输入secretID,也可以通过AppRole的bind_secret_id参数取消登陆时的SecretID参数。可以为role生成128位的随机UUID(pull模式),或自定义值(push模式)。与token类似,SecretID也有使用限制,TTLs和过期时间。

pull和push SecretID模式

如果用于登陆的SecretID是从AppRole获取到的,则为pull模式,如果由客户端设置AppRole的SecretID,则为push模式。大部分场景下推荐使用pull模式。

使用方式

API

创建role

启用approle

$ vault auth enable approle

创建一个policy:

$ vault policy write jenkins -<<EOF
# Read-only permission on secrets stored at 'secret/data/mysql/webapp'
path "secret/data/mysql/webapp" {
  capabilities = [ "read" ]
}
EOF

创建一个role,关联创建出来的策略jenkins(更多参数):

$ vault write auth/approle/role/jenkins token_policies="jenkins" \
    token_ttl=1h token_max_ttl=4h
生成RoleID和secretID

获取RoleID:

$ vault read auth/approle/role/jenkins/role-id
Key     Value
---     -----
role_id 675a50e7-cfe0-be76-e35f-49ec009731ea

生成secretID:

$ vault write -force auth/approle/role/jenkins/secret-id
Key                 Value
---                 -----
secret_id           ed0a642f-2acf-c2da-232f-1b21300d5f29
secret_id_accessor  a240a31f-270a-4765-64bd-94ba1f65703c

查看role信息

$ vault read auth/approle/role/jenkins
使用RoleID & SecretID进行登陆

通过auth/approle/login endpoint进行登陆,输入RoleID和SecretID:

$ vault write auth/approle/login role_id="675a50e7-cfe0-be76-e35f-49ec009731ea" \
    secret_id="ed0a642f-2acf-c2da-232f-1b21300d5f29"
    
Key                     Value
---                     -----
token                   s.ncEw5bAZJqvGJgl8pBDM0C5h
token_accessor          gIQFfVhUd8fDsZjC7gLBMnQu
token_duration          1h
token_renewable         true
token_policies          ["default" "jenkins"]
identity_policies       []
policies                ["default" "jenkins"]
token_meta_role_name    jenkins
使用AppRole token读取secrets
$ export APP_TOKEN="s.ncEw5bAZJqvGJgl8pBDM0C5h"
$ VAULT_TOKEN=$APP_TOKEN vault kv get secret/mysql/webapp
====== Secret Path ======
secret/data/mysql/webapp

======= Metadata =======
Key                Value
---                -----
created_time       2025-04-10T03:32:45.254602Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
db_name     users
password    passw0rd
username    admin

受策略限制,如果执行删除命令,则返回403:

$ VAULT_TOKEN=$APP_TOKEN vault kv delete secret/mysql/webapp
Error deleting secret/mysql/webapp: Error making API request.

URL: DELETE http://127.0.0.1:8200/v1/secret/data/mysql/webapp
Code: 403. Errors:

* 1 error occurred:
    * permission denied
封装SecretID

SecretID类似密码,为了防止明文传递,可以对SecretID进行封装:

$ vault write -wrap-ttl=60s -force auth/approle/role/jenkins/secret-id

Key                              Value
---                              -----
wrapping_token:                  s.yzbznr9NlZNzsgEtz3SI56pX
wrapping_accessor:               Smi4CO0Sdhn8FJvL8XvOT30y
wrapping_token_ttl:              1m
wrapping_token_creation_time:    2021-06-07 20:02:01.019838 -0700 PDT
wrapping_token_creation_path:    auth/approle/role/jenkins/secret-id

解封装的SecretID:

$ VAULT_TOKEN="s.yzbznr9NlZNzsgEtz3SI56pX" vault unwrap

Key                   Value
---                   -----
secret_id             c4086c73-4569-90c9-fd73-72c879e3b7b4
secret_id_accessor    3a2e9483-a7d2-dc19-7480-b1a025daeccc
secret_id_ttl         0s
Tips

查看role:

# 使用vault auth list查看approle的挂载路径
$ vault list /auth/<mount_path>/role

secret engine

secret engine是用于存储、生成或加密数据的组件。

静态和动态secrets

KV secrets engine 通常用于存储静态secrets。此外,Vault还能生成动态secrets,如database secret engine, kubernetes secret engine等。

KV secrets engine

KV secrets engine是常用的键值存储,可以存储单一的键值对,也可以为每个键值对存储多个版本。

下面展示了vault kv子命令和对应的API endpoints:

注意这些API endpoints并不是vault kv直接访问的路径,而是执行子命令之后,后台访问的API路径。v1无版本概念,因此直接访问实际的key路径即可,v2有版本以及软删除等概念,因此需要不同的路径来存储这些数据。

Command KV v1 endpoint KV v2 endpoint
vault kv get secret/<key_path> secret/data/<key_path>
vault kv put secret/<key_path> secret/data/<key_path>
vault kv list secret/<key_path> secret/metadata/<key_path>
vault kv delete secret/<key_path> secret/data/<key_path>

此外,KV v2还有如下子命令:

Command KV v2 endpoint
vault kv patch secret/data/<key_path>
vault kv rollback secret/data/<key_path>
vault kv undelete secret/undelete/<key_path>
vault kv destroy secret/destroy/<key_path>
vault kv metadata secret/metadata/<key_path>
判断kv secrets engine的版本

下面查看了路径为secret/的kv secrets engine的信息,map[version:2]表示版本号为2:

$ vault read sys/mounts/secret
Key                        Value
---                        -----
accessor                   kv_0b42315d
config                     map[default_lease_ttl:0 force_no_cache:false max_lease_ttl:0]
deprecation_status         supported
description                key/value secret storage
external_entropy_access    false
local                      false
options                    map[version:2]
plugin_version             n/a
running_plugin_version     v0.21.0+builtin
running_sha256             n/a
seal_wrap                  false
type                       kv
uuid                       f702a289-5bcb-6655-fe97-78518ec26429
KV version 1

非版本的kv secrets engine,针对一个key,仅存储最新的value。

enable

启用version 1 kv存储:

$ vault secrets enable -version=1 kv
usage

写入数据:

$ vault kv put kv/my-secret my-value=s3cr3t
Success! Data written to: kv/my-secret

读取数据:

$ vault kv get kv/my-secret

list keys:

$ vault kv list kv/

delete key:

$ vault kv delete kv/my-secret
TTLs

与其他secrets engines不同,KV secrets engine不会强制TTLs过期,切不会移除数据。此处的ttl仅表示建议:

$ vault kv put kv/my-secret ttl=30m my-value=s3cr3t
KV version 2

确定版本号:

$ vault read sys/mounts/kv2
Key                        Value
---                        -----
accessor                   kv_825ea02f
config                     map[default_lease_ttl:0 force_no_cache:false max_lease_ttl:0]
deprecation_status         supported
description                n/a
external_entropy_access    false
local                      false
options                    map[version:2]
plugin_version             n/a
running_plugin_version     v0.21.0+builtin
running_sha256             n/a
seal_wrap                  false
type                       kv
uuid                       d92d7308-d526-2f25-050c-e2acbf309432
enable
$ vault secrets enable -path <mount_path> -version=2 kv
write

如在secret/customer/acme路径下创建keys为customer_namecontact_email的数据:

$ vault kv put secret/customer/acme customer_name="ACME Inc." contact_email="john.smith@acme.com"

另一种方式是指定-mount:

$ vault kv put -mount <mount_path> <secret_path> <list_of_kv_values>

如:

$ vault kv put -mount=secret customer/acme customer_name="ACME Inc." contact_email="john.smith@acme.com"
====== Secret Path ======
secret/data/customer/acme

======= Metadata =======
Key                Value
---                -----
created_time       2022-06-13T13:41:45.673767Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

创建之后version会从1开始递增,多次put或patch都会增加version。

使用vault kv metadata可以查看secrets拥有的版本:

$ vault kv metadata get secret/customer/acme
限制版本数

kv-v2 secrets engine默认可以保存10个版本。使用下面方式可以将版本数限制为4,如果版本超过4,则会删除最老的版本:

$ vault write secret/config max_versions=4
read

读取指定路径下的所有key/value对,如读取上面创建的secret/customer/acme路径下的所有数据:

$ vault kv get secret/customer/acme

等价于:

$ vault kv get -mount=secret customer/acme
read指定版本
$ vault kv get -version=1 secret/customer/acme
patch

vault kv put会完全替换当前版本的secrets,使用patch可以修改单个key的value。如仅修改secret/customer/acme下的contact_email的值,而保留其他keys不变:

$ vault kv patch secret/customer/acme contact_email="admin@acme.com"
delete

删除特定版本:

$ vault kv delete -versions="4,5" secret/customer/acme

可以看到deletion_timen/a,但destroyedfalse,表示此时为软删除

$ vault kv metadata get secret/customer/acme

##...snip...
====== Version 4 ======
Key              Value
---              -----
created_time     2021-10-31T00:14:59.830407Z
deletion_time    2021-10-31T00:16:25.860618Z
destroyed        false

====== Version 5 ======
Key              Value
---              -----
created_time     2021-10-31T00:15:01.892226Z
deletion_time    2021-10-31T00:16:25.860619Z
destroyed        false
##...snip...

可以通过vault kv undelete找回delete的数据:

$ vault kv undelete -versions=5 secret/customer/acme
destroy

使用destroy可以永久删除某个版本:

$ vault kv destroy -versions=4 secret/customer/acme

或删除一个路径下的所有版本的secret:

$ vault kv metadata delete secret/customer/acme

Kubernetes secrets engine

API

不推荐使用Vault Kubernetes Auth Method,这种方式会导致在 Vault 中产生许多难以管理的独特身份。

Kubernetes Secrets Engine可以生成kubernetes service account tokens,以及(可选的)service account、role bindings和roles。生成的service account tokens有一个配置的TTL,并在过期后删除所有创建的对象。

启用Kubernetes Secrets Engine:

$ vault secrets enable kubernetes

为vault创建一个service account并创建role和RoleBinding

apiVersion: v1
kind: ServiceAccount
metadata:
  name: test-service-account-with-generated-token
  namespace: test
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: test-role-list-pods
  namespace: test
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["list"]
- apiGroups: [""]
  resources: ["serviceaccounts", "serviceaccounts/token"]
  verbs: ["create", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: test-role-abilities
  namespace: test
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: test-role-list-pods
subjects:
- kind: ServiceAccount
  name: test-service-account-with-generated-token
  namespace: test

Kubernetes 1.24+还需要创建secret:

apiVersion: v1
kind: Secret
metadata:
  name: vault-auth-secret
  namespace: test
  annotations:
    kubernetes.io/service-account.name: test-service-account-with-generated-token
type: kubernetes.io/service-account-token

/config endpoint用于配置vault连接Kubernetes,后续可以通过vault read auth/kubernetes/config读取连接配置:

$ export SA_SECRET_NAME=$(kubectl get secrets --output=json \
    | jq -r '.items[].metadata | select(.name|startswith("vault-auth-")).name')
$ export SA_JWT_TOKEN=$(kubectl get secret $SA_SECRET_NAME \
    --output 'go-template={{ .data.token }}' | base64 --decode)
$ export SA_CA_CRT=$(kubectl -n test get secret $SA_SECRET_NAME -o jsonpath="{.data['ca\.crt']}" | base64 --decode; echo)
$ export K8S_HOST=$(kubectl config view --raw --minify --flatten \
    --output 'jsonpath={.clusters[].cluster.server}')
    
$ vault write kubernetes/config \
     service_account_jwt="$SA_JWT_TOKEN" \
     kubernetes_host="$K8S_HOST" \
     kubernetes_ca_cert="$SA_CA_CRT"

现在就可以使用 Kubernetes Secrets Engine创建Vault role,绑定到kubernetes的service account test-service-account-with-generated-token:

注意role的token_default_ttl,如果过期,在执行vault write kubernetes/creds/my-role时会返回permission denied.

$ vault write kubernetes/roles/my-role \
    allowed_kubernetes_namespaces="*" \
    service_account_name="test-service-account-with-generated-token" \
    token_default_ttl="1000m"

在授予role足够的权限之后,在creds endpoint写入vault role之后就会生成并返回一个新的service account token,其lease_duration与上面创建的my-role的ttl相同。注意由于此处要生成kubernetes的serviceaccount token,因此要求my-role对应的serviceaccount 具有serviceaccounts/token资源的create权限:

$ vault write kubernetes/creds/my-role \
    kubernetes_namespace=test

Key                          Value
---                          -----
lease_id                     kubernetes/creds/my-role/TrHUCplToMe5kv8E77IG45Wr
lease_duration               16h40m
lease_renewable              false
service_account_name         test-service-account-with-generated-token
service_account_namespace    test
service_account_token        eyJhbGciOiJSUzI1NiIsImtpZCI6IjY2M3ps...

后续可以使用上述的service_account_token访问允许的Kubernetes API :

$ curl -sk $(kubectl config view --minify -o 'jsonpath={.clusters[].cluster.server}')/api/v1/namespaces/test/pods \
    --header "Authorization: Bearer eyJHbGci0iJSUzI1Ni..."
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "resourceVersion": "1624"
  },
  "items": []
}

此外还可以在创建或调节(tune) Vault role时设置默认的TTL(token_default_ttl)和最大TTL(token_max_ttl):

$ vault write kubernetes/roles/my-role \
    allowed_kubernetes_namespaces="*" \
    service_account_name="new-service-account-with-generated-token" \
    token_default_ttl="10m" \
    token_max_ttl="2h"

还可以在生成token时指定TTL:

$ vault write kubernetes/creds/my-role \
    kubernetes_namespace=test \
    ttl=20m

Key                        Value
–--                        -----
lease_id                   kubernetes/creds/my-role/31d771a6-...
lease_duration             20m0s
lease_renwable             false
service_account_name       new-service-account-with-generated-token
service_account_namespace  test
service_account_token      eyJHbGci0iJSUzI1NiIsImtpZCI6ImlrUEE...

还可以为已存在的role重新指定kubernetes role:

$ vault write kubernetes/roles/auto-managed-sa-role \
    allowed_kubernetes_namespaces="test" \
    kubernetes_role_name="test-role-list-pods"

这样就可以通过vault write kubernetes/creds/auto-managed-sa-role kubernetes_namespace=test生成token。

PKI secrets engine

API

使用PKI签发证书时,首先需要创建一个issuer,即CA证书,可以使用vault生成自签CA或使用外部CA。vault pki的几个概念:

  • issuer:表示一个CA,且需要关联一个key,否则无法颁发证书。可以通过下面命令查看一个issuer关联的key:

    $ vault read -field=key_id pki/issuer/:issuer_ref
    
  • role:表示创建证书的模版,方便颁发证书,它会关联一个issuer,本质还是通过issuer颁发证书。通过如下命令查看关联的issuer:

    $ vault read -field=issuer_ref pki/roles/my-issuer-role
    
  • urls:一般包含issuing_certificatesocsp_serverscrl_distribution_points。设置好后,会将这些信息嵌入到后续颁发的证书的扩展字段中。这样在客户端验证证书时,可以自动下载 CA 证书链(Issuing CA),可以根据 CRL 或 OCSP URL 检查证书是否已吊销。

    X509v3 Authority Information Access:
        CA Issuers - URI:http://vault.example.com:8200/v1/pki/ca
    
    X509v3 CRL Distribution Points:
        Full Name:
          URI:http://vault.example.com:8200/v1/pki/crl
    
    X509v3 OCSP:
        URI:http://vault.example.com:8200/v1/pki/ocsp
    
构建自己的CA
生成CA和私钥

证书可以来自现有的密钥对,也可以生成自签证书。通常建议维护自己的root CA,然后给vault提供一个signed intermediate CA

下面使用根证书生成中间证书,然后使用中间证书来为test.example.com证书:

image
生成root CA

启用pki secret engine:

$ vault secrets enable pki

设置secret engine的TTL ,默认为30d,下面设置为1年,为全局设置,还可以为单独证书设置TTL。

$ vault secrets tune -max-lease-ttl=8760h pki

下面生成example.com root CA,设置issuer name并将证书保存到root_2023_ca.crt文件中:

/pki/root/generate/:type 生成根证书,type字段可选:exported,会在响应中返回私钥;internal,不会返回私钥,且无法被检索到;existing,使用key_ref参数查找已有的key来创建CSR。

$ vault write -field=certificate pki/root/generate/internal \
     common_name="example.com" \
     issuer_name="root-2023" \
     ttl=87600h > root_2023_ca.crt

查看证书的issuer:

$ vault list pki/issuers/

通过证书的issuer ID获取证书和其他issuer的元数据(如是否revoked):

$ vault read pki/issuer/$(vault list -format=json pki/issuers/ | jq -r '.[]') \
 | tail -n 6

为root CA创建一个role,pki的role可以帮助颁发证书,但前提是要有CA证书。注意此处省去了参数issuer_ref="default"

$ vault write pki/roles/2023-servers allow_any_name=true

配置CA和CRL URLs,下面是全局配置,可以通过AIA字段配置单个issuer的URLs:

$ vault write pki/config/urls \
     issuing_certificates="$VAULT_ADDR/v1/pki/ca" \
     crl_distribution_points="$VAULT_ADDR/v1/pki/crl"
生成中间CA

使用上面生成的根CA来创建中间CA证书。首先在pki_int路径下启用secrets engine:

$ vault secrets enable -path=pki_int pki

pki_int secrets engine颁发的证书的TTL为43800h:

$ vault secrets tune -max-lease-ttl=43800h pki_int

通过/pki/intermediate/generate/:type endpoint创建中间CA的CSR,保存为pki_intermediate.csr:

$ vault write -format=json pki_int/intermediate/generate/internal \
     common_name="example.com Intermediate Authority" \
     issuer_name="example-dot-com-intermediate" \
     | jq -r '.data.csr' > pki_intermediate.csr

使用之前生成的root CA 私钥签发证书,endpoint/pki/root/sign-intermediate,保存为intermediate.cert.pem :

$ vault write -format=json pki/root/sign-intermediate \
     issuer_ref="root-2023" \
     csr=@pki_intermediate.csr \
     format=pem_bundle ttl="43800h" \
     | jq -r '.data.certificate' > intermediate.cert.pem

一旦签发CSR,root CA会返回一个证书,将该证书加载回vault:

$ vault write pki_int/intermediate/set-signed certificate=@intermediate.cert.pem
创建role

role是一个逻辑名称,映射到一系列生成凭据的策略,帮助颁发证书。一个role会通过issuer_ref关联到一个issuer,默认为default issuer。

role的配置参数可以控制证书的common names, alternate names等,下面是值得注意的参数:

Param Description
allowed_domains 指定role的domains,与 allow_bare_domainsallow-subdomains 选项结合使用
allow_bare_domains 指定clients是否可以请求与实际domain本身的值相匹配的证书
allow_subdomains 指定clients是否可以请求带有CNs的证书,且这些CNs是role允许的CNs的子域(注意:此包含通配符子域)。
allow_glob_domains 允许在allowed_domains中指定的名称中包含glob模式(如 ftp*.example.com)

创建一个role example-dot-com,允许子域(allow_subdomains),并通过issuer_ref指定issuer:

$ vault write pki_int/roles/example-dot-com \
     issuer_ref="$(vault read -field=default pki_int/config/issuers)" \
     allowed_domains="example.com" \
     allow_subdomains=true \
     max_ttl="720h"
请求证书

使用example-dot-com role为test.example.com域创建一组新的证书(私钥和证书),并返回颁发证书的CA和完整的CA chain。后续可以通过该操作来为相同的CN颁发新的证书:

$ vault write pki_int/issue/example-dot-com common_name="test.example.com" ttl="24h"
吊销证书

在吊销一个证书时会重新生成一个CRL,此时vault会移除所有过期的证书。

吊销一个证书时,需要输入证书的序列号:

$ vault write pki_int/revoke serial_number=<serial_number>
移除过期的证书
$ vault write pki_int/tidy tidy_cert_store=true tidy_revoked_certs=true
rotate root CA

用于更换root CA。rotate老的root CA的最大挑战是,可能存在一些长时间离线的设备,但需要在上线之后能够获取到新的root CA。可以使用root bridge CA来关联新老 root CA。

使用外部CA

通过加载外部CA的方式颁发证书。需要提供一对配对的CA和key。可以通过如下方式校验CA和key是否配对:

$ openssl x509 -noout -modulus -in ca.crt | openssl md5
$ openssl rsa -noout -modulus -in ca.key | openssl md5

加载外部CA:

$ vault write pki/config/ca pem_bundle=@root-ca.crt 

加载外部key,可以通过vault list pki/keys查看加载的keys:

$ vault write pki/keys/import pem_bundle=@root-ca.key 

查看issuer是否自动关联了key:

$ vault read -field=key_id pki/issuer/:issuer_ref

配置证书分发地址:

$ vault write pki/config/urls \
     issuing_certificates="$VAULT_ADDR/v1/pki/ca" \
     crl_distribution_points="$VAULT_ADDR/v1/pki/crl"

创建 Role,关联上面加载的issuer:

$ vault write pki/roles/my-issuer-role \
  allowed_domains="example.com" \
  allow_subdomains=true \
  generate_lease=true \
  max_ttl="72h" \
  issuer_ref="{issuer}"

使用该 Issuer 签发证书:

$ vault write pki/issue/my-issuer-role \
  common_name="app.example.com" \
  ttl="24h"
Tips
  • Vault生成证书时并不需要指定私钥,而是需要指定与issuer(CA证书)关联的role。颁发(issue)证书和签发(sign)证书的区别:

    • 基于role(:name)颁发证书,包括私钥和证书:

      Method Path Issuer
      POST /pki/issue/:name Role selected
      POST /pki/issuer/:issuer_ref/issue/:name Path selected
    • 签发证书

      基于给定的CSR和参数来签发一个受限于role(:name)的新证书,返回签发的证书和完整的CA chain。

      Method Path Issuer
      POST /pki/sign/:name Role selected
      POST /pki/issuer/:issuer_ref/sign/:name Path selected
    • 签发中间CA
      使用配置的CA证书来签发一个中间CA:

      Method Path Issuer
      POST /pki/root/sign-intermediate default
      POST /pki/issuer/:issuer_ref/sign-intermediate Selected

      签发中间CA需要用到CSR,可以使用如下接口生成中间CSR:

      Method Path Private key source (type)
      POST /pki/intermediate/generate/:type specified per request
      POST /pki/issuers/generate/intermediate/:type specified per request
      POST /pki/intermediate/cross-sign existing
  • 查看default issuer:

    $ vault read pki/config/issuers
    Key                              Value
    ---                              -----
    default                          8ceb6b59-7042-25e3-da6f-f06b745773ab
    default_follows_latest_issuer    false
    

    可以通过vault read pki/cert/ca_chainvault read pki/issuer/default查看默认issuer的证书链。

  • 查看证书内容:
    首先获取证书的序列号(注意: vault list pki/issuers获取到的并不是证书中的序列号,而是issuer的UUID),

    注意, pki/certs endpoint不包含下面序列号:

    • ca :默认issuer的CA证书
    • crl :默认issuer的CRL
    • ca_chain :默认issuer的CA信任链

    但包含vault 生成的root证书该证书有可能是默认issuer。不包含外部加载的证书(root和中间CA证书)。

    $ vault list pki/certs
    

    然后通过序列号查看证书的内容:

    $ vault read -field=certificate pki/cert/:serial|openssl x509  -noout -text
    
  • 查看吊销证书,返回证书的序列号。vault list pki/certs返回的证书中包含已吊销和未吊销的证书:

    $ vault list pki/certs/revoked
    

    查看CRL列表:

    $ vault read pki/cert/crl
    $ vault read pki/issuer/:issuer_ref/crl
    

    查看crl的内容:

    $ vault read -field=crl pki/issuer/:issuer_ref/crl| openssl crl  -text -noout
    
  • 验证证书签发

    $  vault pki verify-sign pki/issuer/:issuer_ref pki_int/issuer/:issuer_ref
    
  • 生成key

    $ vault write pki/keys/generate/exported -format=json | jq -r '.data.private_key' > root-key.pem
    

Storage

通过在vault配置文件的storage字段配置存储后端:

storage [NAME] {
  [PARAMETERS...]
}

如:

storage "file" {
  path = "/mnt/vault/data"
}

Vault支持Integrated Storage(磁盘)和external storage(如Consul、DynamoDB等)

Integrated storage(Raft)后端

使用Integrated Storage时要求提供cluster_addr,用于指定节点间Raft通信的地址和端口:

storage "raft" {
  path = "/path/to/raft/data"
  node_id = "raft_node_1"
  
  retry_join {
    leader_api_addr = "http://127.0.0.4:8200"
    leader_ca_cert_file = "/path/to/ca3"
    leader_client_cert_file = "/path/to/client/cert3"
    leader_client_key_file = "/path/to/client/key3"
  }
}
cluster_addr = "http://127.0.0.1:8201"

主要参数:

  • path:vault数据存储的文件系统路径
  • node_id:节点在Raft集群的标识符
  • retry_join:指定集群的其他节点,可以配置一个或多个,用于帮助本节点加入集群。

Vault agent

image

Auto-auth

自动认证包含两部分:

当一个工具(vault agent或vault proxy)启用自动认证后,工具会使用配置的认证方式请求一个vault token。如果请求成功,自动认证会将token写入合适的sink中。

Templates

简介

template_config用于配置template引擎的默认行为。template用于配置Vault Agent使用Consul Template语言将secrets渲染到文件。可以配置多个templatetemplate中可以通过contents直接提供需要渲染的内容,或通过source选择引用一个单独的.ctmpl文件进行渲染,如:

template_config {
  static_secret_render_interval = "10m"
  exit_on_retry_failure = true
  max_connections_per_host = 20
}

template {
  source      = "/tmp/agent/template.ctmpl"
  destination = "/tmp/agent/render.txt"
}

template {
  contents     = "{{ with secret \"secret/my-secret\" }}{{ .Data.data.foo }}{{ end }}"
  destination  = "/tmp/agent/render-content.txt"
}

template需要通过secret 方法pkiCert 方法进行渲染。前者适用于所有类型的secrets,后者仅适用于PKI secrets engine证书颁发相关的工作。

下面展示了secret方法,其中Data 字段可选,如果存在,则为vault write请求,否则为vault read请求:

{{ secret "<PATH>" "<DATA>" }}

下面从KV 存储中读取一个secret:

{{ with secret "secret/my-secret" }}
{{ .Data.data.foo }}
{{ end }}

如果只需要使用Vault Agent渲染模版,且不需要sink获取到的凭据,则可以忽略auto_authsink

更新secrets

Vault Agent会自动更新secrets/tokens。

  • 当一个secret或token更新之后,Vault Agent会在secret的租期过去2/3之后更新该secret。
  • 如果一个secret或token没有被更新且没有租期,则Vault Agent默认每5分钟拉取一次secret,可以通过static_secret_render_interval 进行配置。
  • 如果一个secret或token没有被更新且有租期,则Vault Agent默认会在90%的TTL时拉取该secret,可以通过lease_renewal_threshold进行设置

用法

token_file

创建测试数据

$ tee data.json -<<EOF
{
   "organization": "ACME Inc.",
   "customer_id": "ABXX2398YZPIE7391",
   "region": "US-West",
   "zip_code": "94105",
   "type": "premium",
   "contact_email": "james@acme.com",
   "status": "active"
}
EOF

$ vault kv put secret/customers/acme @data.json

创建一个template:

$ tee customer.json.tmpl -<<EOF
{
   {{ with secret "secret/data/customers/acme" }}
   "Organization": "{{ .Data.data.organization }}",
   "ID": "{{ .Data.data.customer_id }}",
   "Contact": "{{ .Data.data.contact_email }}"
   {{ end }}
}
EOF

创建Vault Agent配置文件:

tee agent-config.hcl -<<EOF
pid_file = "./pidfile"

vault {
   address = "$VAULT_ADDR"
   tls_skip_verify = true
}

auto_auth {
   method {
      type = "token_file"
      config = {
         token_file_path = "$HOME/.vault-token"
      }
   }
   sink "file" {
      config = {
            path = "$HOME/vault-token-via-agent"
      }
   }
}

template {
   source      = "$HOME/vault-test/customer.json.tmpl"
   destination = "$HOME/vault-test/customer.json"
}
EOF

允许Vault Agent,之后就可以在"$HOME/vault-test/customer.json"中看到渲染之后的内容:

$ vault agent -config=agent-config.hcl

可以通过-config指定多个配置文件,在运行时会组合成单个配置文件。

Kubernetes
Vault Agent auto auth

💁 注意kubernetes vault agent和kubernetes secret engines的不同之处。前者是在vault中创建绑定到kubernetes的serviceaccount的role,并赋予该role访问vault的某些数据的policy,vault agent就可以在auto_auth中使用该role来渲染模版。后者同样会在vault中创建绑定到kubernetes的serviceaccount的role,但会赋予该role操作Kubernetes资源的权限,后续就可以通过该role生成可以操作kubernetes资源的token。

创建Kubernetes的serviceaccount以及相关资源:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: vault-auth
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: role-tokenreview-binding
  namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:auth-delegator
subjects:
- kind: ServiceAccount
  name: vault-auth
  namespace: default
---
apiVersion: v1
kind: Secret
metadata:
  name: vault-auth-secret
  annotations:
    kubernetes.io/service-account.name: vault-auth
type: kubernetes.io/service-account-token

生成demo数据,后续用于Vault Agent渲染模版:

$ vault policy write myapp-kv-ro - <<EOF
path "secret/data/myapp/*" {
    capabilities = ["read", "list"]
}
EOF

$ vault kv put secret/myapp/config \
      username='appuser' \
      password='suP3rsec(et!' \
      ttl='30s'

创建kubernetes auth config。与kubernetes secret engine不同,这里用的是kubernetes auth method

$ export SA_SECRET_NAME=$(kubectl get secrets --output=json \
    | jq -r '.items[].metadata | select(.name|startswith("vault-auth-")).name')
$ export SA_JWT_TOKEN=$(kubectl get secret $SA_SECRET_NAME \
    --output 'go-template={{ .data.token }}' | base64 --decode)
$ export SA_CA_CRT=$(kubectl -n default get secret $SA_SECRET_NAME -o jsonpath="{.data['ca\.crt']}" | base64 --decode; echo)
$ export K8S_HOST=$(kubectl config view --raw --minify --flatten \
    --output 'jsonpath={.clusters[].cluster.server}')
$ vault auth enable kubernetes
$ vault write auth/kubernetes/config \
     token_reviewer_jwt="$SA_JWT_TOKEN" \
     kubernetes_host="$K8S_HOST" \
     kubernetes_ca_cert="$SA_CA_CRT" \
     issuer="https://kubernetes.default.svc.cluster.local"

创建一个role,对应kubernetes中的default命名空间的serviceaccount vault-auth,并赋予其访问demo数据的权限myapp-kv-ro:

$ vault write auth/kubernetes/role/example \
     bound_service_account_names=vault-auth \
     bound_service_account_namespaces=default \
     token_policies=myapp-kv-ro \
     ttl=24h

在使用该serviceaccount的pod中验证该role的权限是否正确:

$ KUBE_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
$ curl --request POST \
       --data '{"jwt": "'"$KUBE_TOKEN"'", "role": "example"}' \
       $VAULT_ADDR/v1/auth/kubernetes/login | python3 -m json.tool

创建Vault Agent配置文件,其中用到了上面创建的role example:

apiVersion: v1
data:
  vault-agent-config.hcl: |
    # Comment this out if running as sidecar instead of initContainer
    exit_after_auth = true

    pid_file = "/home/vault/pidfile"

    auto_auth {
        method "kubernetes" {
            mount_path = "auth/kubernetes" #role的挂载路径
            config = {
                role = "example"           #role的名称
            }
        }

        sink "file" {
            config = {
                path = "/home/vault/.vault-token"
            }
        }
    }

    template {
    destination = "/etc/secrets/index.html"
    contents = <<EOT
    <html>
    <body>
    <p>Some secrets:</p>
    {{- with secret "secret/data/myapp/config" }}
    <ul>
    <li><pre>username: {{ .Data.data.username }}</pre></li>
    <li><pre>password: {{ .Data.data.password }}</pre></li>
    </ul>
    {{ end }}
    </body>
    </html>
    EOT
    }
kind: ConfigMap
metadata:
  name: example-vault-agent-config
  namespace: default

最后执行渲染,并通过卷共享给其他pod:

apiVersion: v1
kind: Pod
metadata:
  name: vault-agent-example
  namespace: default
spec:
  serviceAccountName: vault-auth #注意使用与role绑定的serviceaccount

  volumes:
    - configMap:
        items:
          - key: vault-agent-config.hcl
            path: vault-agent-config.hcl
        name: example-vault-agent-config
      name: config
    - emptyDir: {}
      name: shared-data

  initContainers: #使用vault agent init container进行渲染
    - args:
        - agent
        - -config=/etc/vault/vault-agent-config.hcl
        - -log-level=debug
      env:
        - name: VAULT_ADDR
          value: http://EXTERNAL_VAULT_ADDR:8200
      image: vault
      name: vault-agent
      volumeMounts:
        - mountPath: /etc/vault
          name: config
        - mountPath: /etc/secrets
          name: shared-data

  containers:
    - image: nginx
      name: nginx-container
      ports:
        - containerPort: 80
      volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: shared-data
Vault Agent Injector(webhook)

这种方式vault-k8s提供的一种kubernetes mutation webhook方式(上面的configmap是自己指定的initcongtainer),监控pod的CREATEUPDATE事件,如果检测到此类事件,且annotation中包含vault.hashicorp.com/agent-inject: true annotation,则会据此变更pod规格。

它有两种方式init和sidecar,init容器会将secret生成到共享内存卷(默认为/vault/secrets,可以通过vault.hashicorp.com/secret-volume-path修改),而sidecar则会持续渲染secrets。

使用annotation渲染

与auto auth一样,需要创建一个与kubernetes serviceeaccount对应的role,以及访问策略,后续就可以在annotation vault.hashicorp.com/role中使用该role:

$ vault write auth/kubernetes/role/internal-app \
      bound_service_account_names=internal-app \
      bound_service_account_namespaces=default \
      policies=internal-app \
      ttl=24h

annotation格式为:

vault.hashicorp.com/agent-inject-secret-<unique-name>: /path/to/secret

下面例子中,第一个annotation 会被渲染到/vault/secrets/foo,第二个会被渲染到/vault/secrets/bar:

vault.hashicorp.com/agent-inject-secret-foo: database/roles/app
vault.hashicorp.com/agent-inject-secret-bar: consul/creds/app
vault.hashicorp.com/role: 'app'

secret模版格式如下:

vault.hashicorp.com/agent-inject-template-<unique-name>: |
  <
    TEMPLATE
    HERE
  >

举例如下:

vault.hashicorp.com/agent-inject-secret-foo: 'database/creds/db-app'
vault.hashicorp.com/agent-inject-template-foo: |
  {{- with secret "database/creds/db-app" -}}
  postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/mydb?sslmode=disable
  {{- end }}
vault.hashicorp.com/role: 'app'

有用的annotations

  • vault.hashicorp.com/role: role名称,即vault中的auth/<kubernetes>/role/<role_name>role_name

  • vault.hashicorp.com/agent-inject-secret-<unique-name>: /path/to/secret:其中unique-name是渲染的secret的文件名,/path/to/secret指定了vault中用于渲染的secret的数据路径。

  • vault.hashicorp.com/secret-volume-path: /apps/conf:指定渲染文件(即上述的<unique-name>)的挂载路径的方式。使用 vault.hashicorp.com/secret-volume-path-SECRET-NAME可以将文件和路径映射起来,如vault.hashicorp.com/secret-volume-path-foo指定了渲染文件foo的所在路径,如果不指定文件路径映射关系,则表示所有渲染文件的默认路径。

  • vault.hashicorp.com/agent-inject-template-<unique-name>:指定secret的渲染模板

  • vault.hashicorp.com/agent-inject-status: update:在injection结束后设置的状态值,

  • vault.hashicorp.com/agent-pre-populate-only: "true":如果为true,则不会注入sidecar容器。推荐CronJobJob使用

使用Vault agent configuration map

该方式需要通过vault.hashicorp.com/agent-configmap指定Vault Agent configuration 文件,配置文件会被挂载到/vault/configs,配置中需要包含如下文件:

  • config-init.hcl:init容器使用,必须将exit_after_auth设置为 true.
  • config.hcl:sidecar容器使用,必须将exit_after_auth设置为 false.
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-example
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-example-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app-example
  template:
    metadata:
      labels:
        app: app-example
      annotations:
        vault.hashicorp.com/agent-inject: 'true'
        vault.hashicorp.com/agent-configmap: 'my-configmap'
        vault.hashicorp.com/tls-secret: 'vault-tls-client'
    spec:
      containers:
        - name: app
          image: 'app:1.0.0'
      serviceAccountName: app-example
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-configmap
data:
  config.hcl: |
    "auto_auth" = {
      "method" = {
        "config" = {
          "role" = "db-app"
        }
        "type" = "kubernetes"
      }

      "sink" = {
        "config" = {
          "path" = "/home/vault/.token"
        }

        "type" = "file"
      }
    }

    "exit_after_auth" = false
    "pid_file" = "/home/vault/.pid"

    "template" = {
      "contents" = "{{- with secret \"database/creds/db-app\" -}}postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/mydb?sslmode=disable{{- end }}"
      "destination" = "/vault/secrets/db-creds"
    }

    "vault" = {
      "address" = "https://vault.demo.svc.cluster.local:8200"
      "ca_cert" = "/vault/tls/ca.crt"
      "client_cert" = "/vault/tls/client.crt"
      "client_key" = "/vault/tls/client.key"
    }
  config-init.hcl: |
    "auto_auth" = {
      "method" = {
        "config" = {
          "role" = "db-app"
        }
        "type" = "kubernetes"
      }

      "sink" = {
        "config" = {
          "path" = "/home/vault/.token"
        }

        "type" = "file"
      }
    }

    "exit_after_auth" = true
    "pid_file" = "/home/vault/.pid"

    "template" = {
      "contents" = "{{- with secret \"database/creds/db-app\" -}}postgres://{{ .Data.username }}:{{ .Data.password }}@postgres:5432/mydb?sslmode=disable{{- end }}"
      "destination" = "/vault/secrets/db-creds"
    }

    "vault" = {
      "address" = "https://vault.demo.svc.cluster.local:8200"
      "ca_cert" = "/vault/tls/ca.crt"
      "client_cert" = "/vault/tls/client.crt"
      "client_key" = "/vault/tls/client.key"
    }

官方给出了一些渲染deployment,configmap,将渲染内容注入环境变量等例子。

CLI

Token

capabilities

校验一个token访问某个路径的权限,如下面校验hvs.CAESI...WtiSW5mWUYcubbyhole/foo的访问权限,返回deny

$ vault token capabilities hvs.CAESI...WtiSW5mWUY database/creds/readonly
create

将token绑定到多个策略:

$ vault token create -policy=my-policy -policy=other-policy

创建periodic token,renew的时候使用此period:

$ vault token create -period=30m

还可以通过-ttl指定token的ttl,用于创建非periodic token。

renew
$ vault token renew 96ddf4bc-d217-f3ba-f9bd-017055595017

为token指定续订的时间,如果无-increment参数,则使用默认TTL。periodic tokens忽略该参数

$ vault token renew -increment=30m 96ddf4bc-d217-f3ba-f9bd-017055595017
lookup

查看一个token的详细信息:

$ vault token lookup 96ddf4bc-d217-f3ba-f9bd-017055595017

查看一个token绑定的策略:

$ vault token lookup | grep policies
查看所有token

查看所有token的accessors

$ vault list auth/token/accessors

查看特定的accessor:

$ vault token lookup -format json -accessor <accessor>

过滤出root token:

$ vault list -format json auth/token/accessors | jq -r .[] | xargs -I '{}' vault token lookup -format json -accessor '{}' | jq -r 'select(.data.policies | any(. == "root"))'

policy

list
$ vault policy list
read
$ vault policy read my-policy
write

从本地 /tmp/policy.hcl加载策略:

$ vault policy write my-policy /tmp/policy.hcl

标准输入(stdio)加载策略:

$ cat my-policy.hcl | vault policy write my-policy -
delete
$ vault policy delete my-policy

Auth

用于与Vault的auth methods进行交互,如增删改查不同的auth methods。

enable

启用某个auth method

$ vault auth enable userpass
list

查看已启用的auth method

$ vault auth list -detailed

secrets

用于和secrets engines进行交互。

enable

在某个路径下启用一个secrets engine。

$ vault secrets enable aws
Success! Enabled the aws secrets engine at: aws/
$ vault secrets enable -path=ssh-prod ssh
$ vault secrets enable -max-lease-ttl=30m database
list

查看启用的secrets engines

$ vault secrets list -detailed

kv

参见[KV secrets engine](#KV secrets engine)

metadata

添加metadata

$ vault kv metadata put -custom-metadata=hello="hellotest"    

查询metadata

$ vault kv metadata get secret/hello

read

从特定路径读取数据,可以使用-field指定读取的字段。

write

write命令可以向指定路径写入数据(凭据、secrets、配置或任意数据)。使用key=value格式指定数据,如果value前面包含@,则说明要从文件加载数据,如果一个key的value是-,则说明从标准输入读取数据。可以通过-field指定打印的字段。

$ vault write cubbyhole/git-credentials username="student01" password="p@$$w0rd"

-force允许不带数据的write操作:

$ vault write -force transit/keys/my-key
$ echo $MY_TOKEN | vault write consul/config/access token=-

operator

Vault成员操作
$ vault operator raft list-peers
$ vault operator raft join [options] <leader-api-addr>
#如 vault operator raft join "http://127.0.0.2:8200"
$ vault operator raft remove-peer <server_id>
snapshot

备份和恢复

Usage: vault operator raft snapshot <subcommand> [options] [args]

  This command groups subcommands for operators interacting with the snapshot
  functionality of the integrated Raft storage backend.

Subcommands:
    restore    Installs the provided snapshot, returning the cluster to the state defined in it
    save       Saves a snapshot of the current state of the Raft cluster into a file

查看snapshot的信息

如keys数目、大小等

$ vault operator raft snapshot inspect <snapshot_file>

troubleshoot

审计日志

Vault的audit记录了所有客户端请求和服务端响应的详细信息。审计日志中的auth.policy_results字段给出了请求授权结果:

字段名 含义
type 日志类型,这里是 request,表示是客户端发起的请求(另一种常见的是 response)。
time 请求的时间戳,精确到纳秒(RFC3339 格式)
auth 授权信息,表示请求是通过哪个 token、哪些策略授权的。
request 请求的详细信息,包括路径、操作类型、数据等。

如果一个client出现"permission denied"之类的错误,可以通过如下方式查看token对应的策略,找出client操作是否在策略要求之内。同时结合审计日志可以更加确定问题根因:

curl \
    --silent \
    --header "X-Vault-Token: $VAULT_TOKEN" \
    $VAULT_ADDR/v1/auth/token/lookup-self \
    | jq
启用审计设备

⚠️当启用多审计设备时,Vault会尝试将审计日志发送到所有审计设备,且只要有一个审计设备可用,Vault就可以正常处理请求。因此建议为Vault配置多个审计设备

审计设备要求如下ACL策略:

# 'sudo' capability is required to manage audit devices
path "sys/audit/*"
{
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}

# To list enabled audit devices, 'sudo' capability is required
path "sys/audit"
{
  capabilities = ["read", "sudo"]
}

下面将审计信息写入了file类型的设备中:

$ vault audit enable file file_path=/vault/vault-audit.log

审计日志中的token会被hash,如果要查看原始日志,可以启用log_raw=true:

$ vault audit enable -path=file_raw file \
        file_path=/vault/audit-law.log \
        log_raw=true

在debug之后记得关闭原始审计功能:

$ vault audit disable file_raw

查看启用的审计设备:

$ vault audit list -detailed
查询审计日志

查看审计日志中的错误信息:

export AUDIT_LOG_FILE="$PWD/learn-vault-monitoring/vault-audit.log"
jq 'select(.error != null) | [.time,.error]' $AUDIT_LOG_FILE

敏感信息出错时会返回HMAC-SHA256 编码后的错误,使用如下方式查看哈希错误以及对应的时间戳:

jq 'select(.response.data.error != null) | [.time,.response.data.error]' \
  $AUDIT_LOG_FILE

查看请求路径:

jq -n '[inputs | {Path: .request.path} ] | group_by(.Path) | map({Path: .[0].Path, Count: length}) | sort_by(-.Count) | limit(5;.[])' $AUDIT_LOG_FILE

统计错误次数:

jq -n '[inputs | {Errors: .error} ] | group_by(.Errors) | map({Errors: .[0].Errors, Count: length}) | sort_by(-.Count) | .[]' $AUDIT_LOG_FILE

查看client地址以及访问路径:

jq -s 'group_by(.request.remote_address) | map({"remote_address": .[0].request.remote_address,"access": (group_by(.request.path) | map({"key":.[0].request.path,"value":length}) | from_entries)})' $AUDIT_LOG_FILE

审计日志中的敏感信息是被HMAC过的,如root token的名称、accessor等。

可以使用如下方式计算字符串的HMAC值,然后在审计日志中查找:

$ vault write sys/audit-hash/file input="olAP0Oxb0rvUZAWkRRVcMtYl"
Key     Value
---     -----
hash    hmac-sha256:d890b3417cef5aa22ee035a3ed685c78ad34f4939a53738c33f87f490a93838b

校验策略权限

有时候会碰到403 permission denied错误,通常是由于策略权限不足导致的:

$ vault token create -policy=webapp

Key                  Value
---                  -----
token                s.IcTMGNOug5Cx3wBqpGvI5X4e
token_accessor       s2FhMCQssibpiGeBzVWhxJmn
token_duration       768h
token_renewable      true
token_policies       ["default" "webapp"]
identity_policies    []
policies             ["default" "webapp"]

可以使用vault token capabilities命令测试该token是否有访问某个路径的权限:

$ vault token capabilities s.IcTMGNOug5Cx3wBqpGvI5X4e transit/decrypt/phone-number

vault debug

可以生成perf文件。

遥测指标

使用Prometheus metrics查看异常。

CPU指标
  • cpu.usage_user
  • cpu.usage_iowait

cpu.iowait_cpu大于10%时需要注意。

Network指标
  • net.bytes_recv
  • net.bytes_sent
Memory指标
  • mem.total
  • mem.used_percent
文件描述符指标
  • linux_sysctl_fs.file-nr:主机上使用的文件句柄数
  • linux_sysctl_fs.file-max

file-nr超过80%的file-max时需要注意。

从lost quorum中恢复

vault要求有quorum个正常运行的vault server。从lost quorum中恢复的方法是将集群模式转变为单节点模式。

  1. 首先找到vault-config.hcl中定义的 storage

    storage "raft" {
      path    = "/vault/data"
      server_id = "vault_1"
    }
    ....
    
  2. /vault/data/raft目录中创建一个peers.json文件,包含可工作的节点信息:

    • id:server的service_id

    • address:server的地址和端口

    • non_voter:指定该server是否是non-voter角色

    $ cat > /vault/data/raft/peers.json << EOF
    [
      {
        "id": "vault_1",
        "address": "10.0.101.22:8201",
        "non_voter": false
      }
    ]
    EOF
    
  3. 重启vault

  4. Unseal Vault并查看vault状态

    $ vault operator unseal
    $ vault status
    
  5. 校验结果

    $ vault operator raft list-peers
    

如果后续集群恢复,则将其他节点添加到peers.json文件中即可。

Recovery mode(恢复模式)

主要用于解决由于某些新的bug导致Vault无法启动的问题。

规格

性能测试

使用vault-benchmark

Seal/unseal

该功能需要非dev server。vault的配置和启动参见官方文档。在正式环境中,vault启动时会生成5个unseal key和1个root token。在下面最后一行中可以看到如果要unseal的话,需要3个unseal key。

Unseal Key 1: BvaQM7LygUstmn7N34C7KQ4GWKBi/om5v8vVsCTKek/l
Unseal Key 2: Rx3xYfOuLJrRThLTbAauc7gn6E10w3I4BM6WqJ0lKNGO
Unseal Key 3: JBYOsGqAbocP75by6MvE4Zyoodv5p33LzFmBMOSsWslc
Unseal Key 4: f1CmSy/QzzsJWZAcpa+1bIapffldSJhwFVYwXa+KV0uo
Unseal Key 5: L/cXSbU5v4UizDup0GP/EAu8jJ4ZdFfXU15HRssKlHtI

Initial Root Token: hvs.eV6KNABVAdV5uSvsficEJpCx

Vault initialized with 5 key shares and a key threshold of 3...

这种情况下vault的数据是机密的,需要通过unseal过程来获得加密密钥,否则不能操作vault。unseal的过程如下,执行如下命令3次,输入3个不同的unseal key即可:

vault operator unseal

常用命令

vault secrets list
vault kv list apps/

#查看role和policy
vault auth list
vault list auth/dragon_kubernetes_drg1-prd-asf-quarantine/role/dragon-kubernetes-namespace-vault-role
vault policy list 
vault policy read app_ro_dragon_kubernetes_drg1-prd-asf-quarantine

vault read auth/dragon_kubernetes_drg1-dev-central/role/dragon-logging
vault read auth/dragon_kubernetes_drg1-dev-central/config  

修改vault的kv值:

vault kv get -format=json apps/dragon_prime_imageservice/credentials
vault kv put apps/dragon_prime_imageservice/credentials @/tmp/data.json
From:https://www.cnblogs.com/charlieroro/p/18798638
charlieroro
100+评论
captcha