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

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

有时间限制的访问
Vault通过将TTL和相关凭据关联的方式来实现有时间限制的访问。支持在服务层面和插件层面或角色层面配置TTL。
当一个用户使用预先定义的角色以及用户名和密码进行认证时,会接收到一个Vault token和一个附加的TTL值。

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

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

人类和机器认证
auth methods属于Vault的一种插件,负责为一个用户分配认证所需的身份和策略。
- 人类auth methods包括GitHub、LDAP和 userpass。
- 机器auth methods包括AppRole、AWS、Kubernetes 和 TLS。
大部分情况下,Vault会将身份认证管理和决策委托给相关配置好的外部身份auth methods,如Amazon Web Services、GitHub、Kubernetes等。

静态和动态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。

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
本地测试
Shamir
配置文件(Shamir)
-
docker-compose.yml
:services: vault-1: image: hashicorp/vault:latest container_name: vault-1 restart: unless-stopped ports: - "8200:8200" - "8201:8201" volumes: - vault-1-data:/vault/data - ./vault-config-1.hcl:/vault/config/vault.hcl - vault-logs-1:/vault/logs cap_add: - IPC_LOCK environment: - VAULT_ADDR=http://127.0.0.1:8200 - VAULT_API_ADDR=http://vault-1:8200 - VAULT_CLUSTER_ADDR=http://vault-1:8201 command: vault server -config=/vault/config/vault.hcl networks: - vault-network vault-2: image: hashicorp/vault:latest container_name: vault-2 restart: unless-stopped ports: - "8210:8200" - "8211:8201" volumes: - vault-2-data:/vault/file - ./vault-config-2.hcl:/vault/config/vault.hcl - vault-logs-2:/vault/logs cap_add: - IPC_LOCK environment: - VAULT_ADDR=http://127.0.0.1:8200 - VAULT_API_ADDR=http://vault-2:8200 - VAULT_CLUSTER_ADDR=http://vault-2:8201 command: vault server -config=/vault/config/vault.hcl networks: - vault-network vault-3: image: hashicorp/vault:latest container_name: vault-3 restart: unless-stopped ports: - "8220:8200" - "8221:8201" volumes: - vault-3-data:/vault/file - ./vault-config-3.hcl:/vault/config/vault.hcl - vault-logs-3:/vault/logs cap_add: - IPC_LOCK environment: - VAULT_ADDR=http://127.0.0.1:8200 - VAULT_API_ADDR=http://vault-3:8200 - VAULT_CLUSTER_ADDR=http://vault-3:8201 command: vault server -config=/vault/config/vault.hcl networks: - vault-network volumes: vault-1-data: vault-2-data: vault-3-data: vault-logs-1: vault-logs-2: vault-logs-3: networks: vault-network: driver: bridge
-
vault-config-1.hcl
:storage "raft" { path = "/vault/file" node_id = "vault-1" retry_join { leader_api_addr = "http://vault-1:8200" } retry_join { leader_api_addr = "http://vault-2:8200" } retry_join { leader_api_addr = "http://vault-3:8200" } } listener "tcp" { address = "0.0.0.0:8200" tls_disable = true cluster_address = "0.0.0.0:8201" } api_addr = "http://vault-1:8200" cluster_addr = "http://vault-1:8201" ui = true disable_mlock = true log_level = "info"
-
vault-config-2.hcl
:storage "raft" { path = "/vault/file" node_id = "vault-2" retry_join { leader_api_addr = "http://vault-1:8200" } retry_join { leader_api_addr = "http://vault-2:8200" } retry_join { leader_api_addr = "http://vault-3:8200" } } listener "tcp" { address = "0.0.0.0:8200" tls_disable = true cluster_address = "0.0.0.0:8201" } api_addr = "http://vault-2:8200" cluster_addr = "http://vault-2:8201" ui = true disable_mlock = true log_level = "info"
-
vault-config-3.hcl
:storage "raft" { path = "/vault/file" node_id = "vault-3" retry_join { leader_api_addr = "http://vault-1:8200" } retry_join { leader_api_addr = "http://vault-2:8200" } retry_join { leader_api_addr = "http://vault-3:8200" } } listener "tcp" { address = "0.0.0.0:8200" tls_disable = true cluster_address = "0.0.0.0:8201" } api_addr = "http://vault-3:8200" cluster_addr = "http://vault-3:8201" ui = true disable_mlock = true log_level = "info"
启动方式
docker-compose up -d
unseal集群
# 设置第一个节点的地址
export VAULT_ADDR='http://127.0.0.1:8200'
# 初始化第一个Vault节点
echo "初始化Vault集群..."
vault operator init -key-shares=5 -key-threshold=3 -format=json > vault-init.json
# 提取根令牌和解封密钥
export VAULT_TOKEN=$(cat vault-init.json | jq -r '.root_token')
UNSEAL_KEY_1=$(cat vault-init.json | jq -r '.unseal_keys_b64[0]')
UNSEAL_KEY_2=$(cat vault-init.json | jq -r '.unseal_keys_b64[1]')
UNSEAL_KEY_3=$(cat vault-init.json | jq -r '.unseal_keys_b64[2]')
echo "根令牌: $VAULT_TOKEN"
echo "解封密钥已保存到 vault-init.json"
# 解封第一个节点
echo "解封 vault-1..."
vault operator unseal $UNSEAL_KEY_1
vault operator unseal $UNSEAL_KEY_2
vault operator unseal $UNSEAL_KEY_3
# 等待一会儿让第一个节点完全启动
sleep 5
# 解封第二个节点
echo "解封 vault-2..."
export VAULT_ADDR='http://127.0.0.1:8210'
vault operator unseal $UNSEAL_KEY_1
vault operator unseal $UNSEAL_KEY_2
vault operator unseal $UNSEAL_KEY_3
# 解封第三个节点
echo "解封 vault-3..."
export VAULT_ADDR='http://127.0.0.1:8220'
vault operator unseal $UNSEAL_KEY_1
vault operator unseal $UNSEAL_KEY_2
vault operator unseal $UNSEAL_KEY_3
# 切换回第一个节点
export VAULT_ADDR='http://127.0.0.1:8200'
# 检查集群状态
echo "检查Raft集群状态..."
vault operator raft list-peers
echo "Vault集群初始化完成!"
echo "Web UI 访问地址:"
echo " - vault-1: http://localhost:8200"
echo " - vault-2: http://localhost:8210"
echo " - vault-3: http://localhost:8220"
echo ""
echo "使用以下命令设置环境变量:"
echo " export VAULT_ADDR='http://127.0.0.1:8200'"
echo " export VAULT_TOKEN='$VAULT_TOKEN'"
Tips
-
上述配置没有启用tls,如果仍然出现未找到
vault-ca.pem
的错误,则可能是因为本地环境导致的,可以尝试清除本地环境变量:unset VAULT_CACERT unset VAULT_CA_CERT unset VAULT_CLIENT_CERT unset VAULT_CLIENT_KEY
配置文件(Auto-unseal by transit secrets)
可以测试auto seal和recovery keys。
plugin

Vault支持3种插件:auth methods, secret engines和 database plugins,这些插件分为内置插件和外部插件两种。可以使用plugin_directory
选项加载外部插件。
Vault Integrations给出了支持的插件列表。
Token
在客户端认证成功之后,vault会颁发一个token,用于校验客户端的访问以及客户端可以执行的操作。
使用vault cli执行命令前,需要设置通过 vault login
登陆或设置VAULT_TOKEN
环境变量。

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
路径下执行create
和update
操作,但由于第二条策略更具体,其优先级更高,因此,在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
策略且路径相同,则合并策略,下面允许在路径下执行list
, read
, create
和 update
操作:
# 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
, read
和 update
操作:
# 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所需的policies
,ttl
和explicit-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/
开头。
- auth方法
- secrets engine
AppRole
用于给机器或apps提供认证。

关键参数
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模式。
使用方式
创建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_name
和contact_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_time
非n/a
,但destroyed
为false
,表示此时为软删除
$ 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
不推荐使用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
使用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_certificates
、ocsp_servers
和crl_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
证书:

生成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_domains 和 allow-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
-
-
$ vault read pki/config/issuers Key Value --- ----- default 8ceb6b59-7042-25e3-da6f-f06b745773ab default_follows_latest_issuer false
可以通过
vault read pki/cert/ca_chain
或vault read pki/issuer/default
查看默认issuer的证书链。 -
查看证书内容:
首先获取证书的序列号(注意:vault list pki/issuers
获取到的并不是证书中的序列号,而是issuer的UUID),注意,
pki/certs
endpoint不包含下面序列号:但包含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
-
$ 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

Auto-auth
自动认证包含两部分:
当一个工具(vault agent或vault proxy)启用自动认证后,工具会使用配置的认证方式请求一个vault token。如果请求成功,自动认证会将token写入合适的sink中。
Templates
简介
template_config
用于配置template引擎的默认行为。template
用于配置Vault Agent使用Consul Template语言将secrets渲染到文件。可以配置多个template
。template
中可以通过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_auth
的sink
。
更新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的CREATE
和UPDATE
事件,如果检测到此类事件,且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容器。推荐CronJob
或Job
使用
使用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...WtiSW5mWUY
对 cubbyhole/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。
revoke
$ vault token revoke hvs.mY48tW5VVMB8UpxO4tPXuF50
Success! Revoked token (if it existed)
create root token
初始化revoke root token操作,记住生成的OTP
:
$ vault operator generate-root -init
Nonce 0754414b-f868-a14f-b3b1-91d2a1f3a511
Started true
Progress 0/3
Complete false
OTP gUZhMmgA4gXJSJtHZlx7mcoNEWqL
OTP Length 28
多次执行如下命令生成新的root token(过程需要unseal keys或recovery keys):
$ vault operator generate-root
最终生成加密的root token:
$ vault operator generate-root
Operation nonce: 0754414b-f868-a14f-b3b1-91d2a1f3a511
Unseal Key (will be hidden):
Nonce 0754414b-f868-a14f-b3b1-91d2a1f3a511
Started true
Progress 3/3
Complete true
Encoded Token DyMpRj0PHSduEykBJBoeMA0ZQWc9Gho4cgVJeQ
使用如下方式解密root token:
$ vault operator generate-root -decode=DyMpRj0PHSduEykBJBoeMA0ZQWc9Gho4cgVJeQ -otp=gUZhMmgA4gXJSJtHZlx7mcoNEWqL
hvs.pbzfZtqKwPjxWu9PPyuv7R85
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中恢复的方法是将集群模式转变为单节点模式。
-
首先找到
vault-config.hcl
中定义的storage
:storage "raft" { path = "/vault/data" server_id = "vault_1" } ....
-
在
/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
-
-
重启vault
-
Unseal Vault并查看vault状态
$ vault operator unseal $ vault status
-
校验结果
$ vault operator raft list-peers
如果后续集群恢复,则将其他节点添加到peers.json
文件中即可。
Recovery mode(恢复模式)
主要用于解决由于某些新的bug导致Vault无法启动的问题。
规格
性能测试
Seal/unseal
参考:
unseal
vault默认是sealed状态,该状态下,vault可以访问物理存储,但无法解密任何数据。为了解密数据,vault需要获取root key,因此unseal对过程是获取root key的过程。vault使用unseal keys加密了root key。
vault默认会使用Shamir's Secret Sharing 算法将root key切为多份。要求一定的份数(-key-threshold
)才能重建root key,通过vault operator unseal <key>
命令执行unseal过程(需要执行多次(-key-threshold
))。
初始化方式如下:
$ vault operator init \
-key-shares=3 \
-key-threshold=2 \
-pgp-keys="keybase:hashicorp,keybase:jefferai,keybase:sethvargo"
默认方式下,vault启动时需要一个节点一个节点去unseal。在vault重启之后会变回sealed状态,需要重新unseal。
auto unseal
Vault 启动后,无需手工unseal,而是自动向一个可信的云服务(如 AWS KMS、Azure Key Vault、GCP CKMS 等)请求解密自身的数据加密密钥。使用Shamir seal时,unseal需要提供unseal keys,使用auto unseal则需要提供recovery keys。
初始化方式如下:
$ vault operator init \
-recovery-shares=3 \
-recovery-threshold=2 \
-recovery-pgp-keys="keybase:hashicorp,keybase:jefferai,keybase:sethvargo"
注意这里生成的是recovery keys:
Recovery Key 1: wcFMA7l3edsU86d4ARAAgOUbfhQmfDN9HS/rolMfYzi5rWRh4sPZMZ47aNGA8A5jjElyIBVCxvnOxxxxxxxxx
Recovery Key 2: wcFMA/WshhkMOj35AQ/9G3Fm2xwJvOCyDAFGYthfP6axpPv1oDnPSH8DIuU0bnGOizj479wrqhzDxxxxxxxxx
以Azure Key Vault为例,初始化过程会生成主密钥 (Master Key),这个密钥用于加密和解密 Vault 中所有的数据。
初始化
1. Vault 初始化
├─ vault operator init
├─ 生成 Recovery Keys(而非 Unseal Keys)
├─ 生成 Root Token
└─ Master Key
2. Azure Key Vault 交互
├─ Vault 连接到 Azure KV
├─ 使用指定的密钥加密 Master Key
└─ 加密后的 Master Key 存储在 Vault 存储后端
自动解封
1. Vault 服务启动
├─ 读取配置文件
├─ 连接到 Azure Key Vault
└─ 验证身份认证
2. 自动解封流程
├─ 从存储后端读取加密的 Master Key
├─ 向 Azure KV 发送解密请求
├─ Azure KV 返回解密后的 Master Key
├─ Vault 使用 Master Key 解锁数据加密密钥
└─ Vault 状态变为 "unsealed"
与Shamir unseal不同的是Recovery keys 并不会解密root key,其运行依赖auto unseal 机制的稳定性。可以使用vault operator generate-root
命令配合Recovery keys重新生成root key
Recovery keys cannot decrypt the root key and therefore are not sufficient to unseal Vault if the auto unseal mechanism isn't working.
If the seal mechanism or its keys are permanently deleted, then the Vault cluster cannot be recovered, even from backups.
seal
该命令会删除内存中的root key,需要通过unseal才能恢复root key。使用命令vault operator seal
可以手动seal vault。
Rekey
Rekey unseal key
使用vault operator rekey
可以rekey unseal keys:
-
创建gpg Keys
-
创建gpg keys:
gpg --full-generate-key
- Key Type: RSA and RSA
- Key Size: 4096
- Key Validity: 0 (does not expire)
-
列出存在的gpg keys:
gpg --list-secret-keys --keyid-format=long
gpg: checking the trustdb gpg: marginals needed: 3 completes needed: 1 trust model: pgp gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u [keyboxd] --------- sec rsa4096/CBBD350A01D3E854 2025-07-22 [SC] 1794F41C93736FCEABCA2F54CBBD350A01D3E854 uid [ultimate] Eduardo Mota <eduardo.mota@farfetch.com> ssb rsa4096/156C54AB294220D2 2025-07-22 [E]
-
导出public GPG key:
gpg --armor --export <ID> # Example gpg --armor --export CBBD350A01D3E854 > my.asc
-
-
使用如上方式创建2个GPG keys:
test1.asc
和test2.asc
-
启动rekey:
vault operator rekey -init -key-shares=2 -key-threshold=2 -pgp-keys="/vault/docker/test1.asc,/vault/docker/test2.asc"
-
init
:初始化rekey操作,可以重新设置key-shares
和key-threshold
-
key-shares
:指定要生成的keys的数目 -
key-threshold
:指定需要多少个keys才能重建root key,即需要提供key-threshold
个keys才能访问vault。 -
pgp-keys
:包含用于加密key-shares的 PGP 公钥。列表中的公钥数量必须与-key-shares
参数指定的数量完全一致。每个新生成的key会分别使用列表中对应位置的公钥进行加密。
-
-
启动rekey需要输入unseal key,多次执行
vault operator rekey
并输入正确的unseal keys(需要达到上一次初始化时指定的-key-threshold
数目)输入足够的unseal keys之后,会生成新的keys,如下生成了2个keys(
-key-shares=2
)Key 1 fingerprint: 436578ae2740acf7f728e430b8b9205505336caa; value: wcFMA7l3edsU86d4AQ//c255uELAcWj8aHDtgD845OIp+DC4oW/2TRjjvD/CzKfQnMfDsYI0zr+cDi2ewvxxxxxx Key 2 fingerprint: f91a16e501f5ce0b6a66c88a3473c995e110912d; value: wcFMA/WshhkMOj35AQ//Sp5MLWn4H+l/3U6EfjCLavcnqxNVT3K6fZcURtFFtnlM8TOviVOI+Y6flGN/GNxxxxxx
-
保存被GPG加密的key:
echo "wcFMA7l3edsU86d4AQ//c255uELAcWj8aHDtgD845OIp+DC4oW/2TRjjvD/CzKfQnMfDsYI0zr+cDi2ewvxxxxxx" | base64 -d > key1.gpg echo "wcFMA/WshhkMOj35AQ//Sp5MLWn4H+l/3U6EfjCLavcnqxNVT3K6fZcURtFFtnlM8TOviVOI+Y6flGN/GNxxxxxx" | base64 -d > key2.gpg
-
解密key,后续可以通过这些keys unseal vault:
# 解密 Key 1(需要对应的私钥) gpg --decrypt key1.gpg # 5b873863bedc8bfb99a571d7cc212dd0e6a4e8a25cba4d6171ea5e66c1c244e38f # 解密 Key 2(需要对应的私钥) gpg --decrypt key2.gpg #62b11b46a10c36fd7f6713ac930656dd743bd8db0845229efd4e9ae8878d3e2e22
Tips
- 使用-cancel可以取消rekey流程:
vault operator rekey -target=recovery -init -key-shares=2 -key-threshold=2 -pgp-keys="/vault/docker/test1.asc,/vault/docker/test2.asc" -cancel
Rekey recovery key
要求auto seal模式。
rekey recovery key需要指定target:vault operator rekey --target=recovery
。使用如下命令rekey recovery key:
-
按照[Rekey unseal key](#Rekey unseal key)的第1步,生成2个GPG keys:
test1.asc
和test2.asc
-
启动rekey流程:
vault operator rekey -target=recovery -init -key-shares=2 -key-threshold=2 -pgp-keys="/vault/docker/test1.asc,/vault/docker/test2.asc"
target
:默认是barrier
,即unseal key。这里为recovery
,表示recovery keys。
-
多次执行如下命令(需要输入老的recovery keys)来生成新的recovery keys
vault operator rekey -target=recovery -key-shares=2 -key-threshold=2 -pgp-keys="/vault/docker/test1.asc,/vault/docker/test2.asc"
最终生成2个(
-key-shares
)recovery keys:Key 1 fingerprint: 436578ae2740acf7f728e430b8b9205505336caa; value: wcFMA7l3edsU86d4ARAApOqalZXU+s5JcbiXJIfVjZovmvNVuVF0aVv0L52qEkKmF5Rt9FFE8xxxxxx Key 2 fingerprint: f91a16e501f5ce0b6a66c88a3473c995e110912d; value: wcFMA/WshhkMOj35ARAAgmGOOJ+5ILralqlzHRKc8f8Nj0S9WPD0AATQ+IJ+E29Lvlq5nKmc9xxxxxx
-
按照[Rekey unseal key](#Rekey unseal key)的第5,6步可以解密新的recovery keys
升级
参考官方文档,查看:
- important change
- change tracker
- 完整的release notes,注意breaking change
- 查看plugin是否是内置(builtin)的:
vault plugin list
常用命令
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
本文来自博客园,作者:charlieroro,转载请注明原文链接:https://www.cnblogs.com/charlieroro/p/18798638