跳至内容
vault-集合

vault-集合

HashiCorp Vault 金库

Vault 是 HashiCorp 推出的企业级秘密管理工具, 专为集中保护, 控制和审计敏感信息访问而设计

源代码开源(source-available) + 商业授权 模式

OpenBao(开源版 Vault)

如果可以, 推荐应用直接通过 SDK 直接访问 Vault 来获取密钥

特性

集中化秘密存储: 安全管理 API 密钥, 数据库密码, SSL 证书等敏感信息 替代分散在代码或配置文件中的明文存储, 从源头降低泄露风险

细粒度访问控制: 基于策略(Policy)实现"最小权限"管理, 支持多身份认证(Token, LDAP, OAuth2, MFA 等), 确保只有授权实体能访问特定秘密;

动态凭证生成: 为数据库, 云服务等自动生成短期有效, 自动过期的临时凭证, 大幅降低长期凭证泄露的安全隐患;

全链路审计追踪: 记录所有秘密访问, 修改, 删除操作, 生成不可篡改的审计日志, 满足合规审计需求(如等保, SOC2);

可以为数据库 /SSH/Consul/AWS/Nomad/PKI/RabbitMQ 等多种平台生成和管理访问密钥 可以与 Cousul, Chef, Ansible 等运维工具集成起来共同使用

密钥:

  1. 静态 Secret: 密钥是固定的
  2. 动态 Secret: 请求时会到后端应用去创建一个临时账号(有效期), 并处理权限; 然后将临时账号给到到前端应用, 应用侧自己需要能处理租约到期

数据存储

  • 内存: 临时存储, 配置内存锁定
  • 文件存储: 开发环境使用
  • 分布式存储: etcd, consul 等
  • aws s3, nosql

应用身份认证方式

应避免硬编码 token 的方式

AppRole: 为应用分配一个 role_id 和 secret_id, 适用于服务器端应用

Kubernetes Auth Method: 针对运行于 Kubernetes 上的服务; SDK 会利用 Pod 的 ServiceAccount 自动与 Vault 完成认证

云平台认证: 对于运行在 AWS, Azure, GCP 上的应用, 可以使用云平台的原生认证 即 IAM 身份进行认证

JWT/OIDC: 利用外部 OAuth2 提供者进行用户认证;

k8s 集成

方式 1: Vault Secrets Operator 同步创建 Secrets 方式 2: Sidecar 模式, pod 附加一个容器, 即 Vault Agent Sidecar 容器负责与 Vault 通信, 并创建文件挂载 方式 3: Vault CSI Driver 卷挂载模式, 挂载为 Pod 的 临时卷

主流方式 1

但方式 2 和 3 无 Secret, 所有密钥不会在命名空间内, 即也不会存储到 etcd 中, 适合安全性非常高的场景

Raft 集群

2/3 可用

Leader 节点宕机, 会进行选举; Follower 节点, 系统无感知

# 查看成员健康度
vault operator raft list-peers

Node     Address       State     Voter
----     -------       -----     -----
node1    vault:8201    leader    true

运行模式

  1. seal 密封模式
  2. Auto-Unseal 自动解封模式

需要注意 seal密封模式 和 Recovery 恢复模式是互斥的;

seal密封模式会生成 5 个解封密钥片段; 密钥有两个用途: 解封 + 授权(重大操作的授权)

自动解封模式时, 解封密钥实际是由 kms/HSM 管理, 此时生成的是5个恢复密钥; 密钥只有 1个用途: 授权(重大操作的授权)

dev 模式

dev 模式下 Vault 会在内存中自动完成初始化和解封 数据没有持久化, 仅用于本地开发调试;

没有解封密钥, 只有一个 rootToken

# 如果指定了这个变量, 会强制运行在开发模式下, 没有持久化
VAULT_DEV_ROOT_TOKEN_ID=

seal 密封模式

需要手工初始化和解封

初始化 集群模式下, 只需要 init 一个节点, 其他节点只需要加入集群;

docker exec -it vault-server sh
export VAULT_ADDR=http://127.0.0.1:8200;

# 会生成 5 个 unseal key 和 1 个 root token, 务必保存
vault operator init

unsealKey: 解封所需要的密钥分片;

当节点重启后, Vault 启动后处于"密封"状态, 需要 5 个 unsealKey 密钥分片 中的至少 3 个才能解封;

# 解封 - 并按提示输入任意 3 个 unsealKey
vault operator unseal

Auto-Unseal 自动解封

初始化时, Vault 会直接把解封密钥存入 KMS; 节点重启时, 会调用 云 kms 接口进行自动解封;

权限控制变成了对"谁能调用 KMS 接口"的阿里云 RAM 权限管理; ECS 实例或运行 Vault 的环境拥有调用 kms:Decrypt 和 kms:Encrypt 的权限

在 Vault 的配置文件中定义 seal 部分

seal "alicloudkms" {
  region     = "cn-hangzhou"
  access_key = "ak"
  secret_key = "sk"
  kms_key_id = "KMS 主密钥 ID"
}

Recovery Keys 恢复密钥

是一个专门用于授权和恢复操作的安全机制, 区别于常规的解封密钥(Unseal Keys);

只有当 Vault 使用了自动解封(Auto Unseal), 例如集成了云厂商的 KMS 或硬件安全模块(HSM)时, 才会用到它;

Vault 的解封过程由 KMS/HSM 自动完成, 不再需要人来输入解封密钥; 因此没有 解封密钥(Unseal Keys);

Recovery Keys 是一个纯人工授权的机制, 它的核心功能是授权, 而不是解密

用于关键操作授权需要人工批准

  • 如生成根令牌 generate-root
  • 执行重新加密 rekey 等
  • 更改迁移 KMS 配置

需要达到设定的阈值(threshold)数量的密钥持有者同意, 操作才能继续

初始化生成: 使用 HSM 或 KMS 初始化 Vault 时, 系统会生成一个内部的 Recovery Key, 并将其拆分成多个密钥碎片(Key Shares), 返回给操作者;

重新生成(Rekey): 在人员变动或合规要求下, 可以使用 rekey 命令来重新生成一套新的 Recovery Keys

PGP 加密: 可以使用 在生成或重新生成 Recovery Keys 时使用 -pgp-keys 参数传入 pgp 公钥;

# 重新生成恢复密钥 - 需要多数旧的恢复密钥, Vault 会输出 5 个全新的 Recovery Keys
vault operator rekey -target=recovery

# 重新生成恢复密钥 - 到达阈值后才会有输出
vault operator rekey -init -key-shares=5 -key-threshold=3

避免单人掌握多数 unsealKey/RecoveryKey

官方方案: 使用 PGP 公钥加密

5 位密钥持有者每人提供一个 PGP 公钥, 初始化时需要传入这 5 个公钥;

vault operator init \
    -key-shares=5 \
    -key-threshold=3 \
    -pgp-keys="user1.asc,user2.asc,user3.asc,user4.asc,user5.asc"

终端会输出 5 段密文, 经 PGP 加密的 Base64 字符串, 只有对私钥持有者能解密出自己的那段密文;

alicloudkms

需要 alicloudkms 插件

在 Auto-Unseal 模式下, Vault 会利用 KMS 的"信封加密"技术, 自动产生一个 Recovery Key(恢复密钥), 并将真正用于加解密数据的 Root Key(根密钥) 存储在 KMS 中

  1. 每次重启 Vault 服务, 它会自动连接阿里云 KMS 完成解密并进入 unsealed(解封)状态
  2. 不在生成 5 个解封的 key, 而是 Recovery Keys

在阿里云控制台:

  1. 开通 KMS(密钥管理服务);
  2. 创建一个 对称密钥(用户主密钥), 记录下它的 KeyId;
  3. 确保你的 ECS 实例或运行 Vault 的环境拥有调用 kms:Decrypt 和 kms:Encrypt 的权限(建议通过 RAM 角色授权);

config.hcl

ui = true

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

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = 1
}

# 核心配置: 阿里云 KMS 自动解封
seal "alicloudkms" {
  region     = "cn-hangzhou"      # KMS 所在的地域
  access_key = "你的 AccessKeyId"   # 建议使用 RAM 角色, 此处可省略
  secret_key = "你的 AccessKeySecret"

  # 刚才在阿里云创建的密钥 ID
  kms_key_id = "bc4xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

初始化

不再输出 Unseal Key 1...5, 会输出 Recovery Key 1...5, 依然会输出 Initial Root Token

vault operator init

阿里云权限控制

方案1: 细化权限 禁止给非 Vault 服务器授予 Resource: * 的权限

{
  "Action": ["kms:Decrypt"],
  "Resource": ["*"],  // 禁止所有权限, 必须细化
  "Effect": "Allow"
}

每个业务系统细化权限

{
  "Action": ["kms:Decrypt"],
  "Resource": ["acs:kms:cn-hangzhou:12345:key/业务Key_ID_xxxx"], // 明确指定
  "Effect": "Allow"
}

方案2: 提供预设的字符串对进行双保险

seal "alicloudkms" {
  kms_key_id         = "你的ID"
  encryption_context = "{\"AppName\": \"Vault-Production-Cluster\"}"
}

方案3: 限制作用域, 这个 权限 只能在某个范围用

{
  "Action": "kms:Decrypt",
  "Resource": "acs:kms:cn-hangzhou:12345:key/Vault_Key_ID",
  "Effect": "Allow",
  "Condition": {
    "StringEquals": {
      "acs:SourceVpc": ["vpc-t4nxxxxxxxx"] # 只有这个 VPC 内的请求才有效
    }
  }
}

RootToken

初始化后会生成一个最高权限的 rootToken

最佳实践 撤销掉这个初始的 rootToken, 有必要时再进行临时创建(需要 5 个解封密钥)

# 撤销 token
vault token revoke <TOKEN_VALUE>

重新生成 rootToken

# 利用 Recovery Keys 重新生成 root token
# 系统会返回一个 Nonce 值和 OTP (One-Time Password)
vault operator generate-root -init

# 提供 Recovery Keys 签名, 需要多个管理员输入足够数量的 Recovery Keys
vault operator generate-root -recovery-key

# 解码获取新 Token:
# 最后一步会输出一个加密的 Token 字符串, 结合第 1 步的 OTP 即可解密出真实的 hvs.root-xxx

管理cli

export VAULT_ADDR='http://192.168.5.9:8200'
export VAULT_TOKEN='xxxxxxxxxxxxxewofoeiwoiieo'

vault status        # 运行状态

kv

# write
vault kv put secret/myapp/config \
    db_username="admin" \
    db_password="very-secret-password" \
    timeout="5s"

# read
vault kv get secret/myapp/config

# modify
vault kv patch secret/myapp/config timeout="10s"

vault kv metadata get secret/myapp/config       # 查看版本历史
vault kv get -version=1 secret/myapp/config     # 读取旧版本 (版本 1)
# 查看已经开启的认证方法
vault auth list
Path         Type        Accessor                  Description                Version
----         ----        --------                  -----------                -------
approle/     approle     auth_approle_f18cd096     n/a                        n/a
token/       token       auth_token_ecd6971e       token based credentials    n/a
userpass/    userpass    auth_userpass_42108dc7    n/a                        n/a

# token 是已经签发的token, 不能直接列出

vault write auth/userpass/users/user1 password=passw0rd

vault login -method=userpass username=bob password=foobar

vault list auth/userpass/users
vault list auth/approle/role/

业务集成

在 k8s 集群中

  1. 每个 Pod 都有一个挂载在 /var/run/secrets/kubernetes.io/serviceaccount/token 的 JWT 令牌;

  2. 交换 Token: 应用(或 Vault Agent)将这个 JWT 发给 Vault;

  3. 验证: Vault 调用 K8s API 确认: "这个 JWT 确实是 order-service 这个应用的;"

  4. 下发 Token: 验证通过, Vault 返回一个专门给该应用使用的, 权限受限的 VAULT_TOKEN

续签

  1. Vault sdk 有续签机制
  2. Vault Agent - Sidecar 模式

在虚拟机或物理机上

利用 AppRole; 这类似于 OAuth2;

应用持有一个 RoleID(类似用户名)和 SecretID(类似密码);

启动时调用 Vault API 换取 VAULT_TOKEN

最佳实践

  1. 云上优先考虑 Auto-unseal 自动解封, 节点漂移后方便自动解封;

  2. 云下部署考虑硬件密钥

  3. app 通过sdk直接访问 vault 获取秘密信息

  4. k8s集群可选通过 eso(ExternalSecretsOperator) 或者 vault-secrets-operator 将密钥更新到 k8s secrets

  5. vault 最好在k8s集群外独立部署

vault-secrets-operator

将 vault 中的秘密同步到 k8s 集群中

原生支持动态秘密 认证集成: 深度利用 K8s Auth Method, 让 Pod 能够以原生的方式与 Vault 交互

helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
helm install vault-secrets-operator hashicorp/vault-secrets-operator \
  --namespace vault-secrets-operator-system \
  --create-namespace

Vault 配置

  1. 在 Vault 侧配置, 需要启用 Kubernetes 认证, 并创建一个角色(Role), 这个角色会将特定的 Kubernetes ServiceAccount 和命名空间绑定到 Vault 的权限策略上
  2. 在 Kubernetes 侧配置: 通过创建 VaultAuth 资源来告诉 VSO 如何使用这个角色进行认证
# 示例: VaultAuth 资源
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: static-auth
  namespace: default
spec:
  method: kubernetes
  mount: kubernetes
  kubernetes:
    role: database   # 对应在 Vault 中创建的 role 名称
    serviceAccount: my-app-sa # VSO 将使用的 ServiceAccount

通过 VaultStaticSecret 资源从 Vault 拉取 Secret

# 示例: VaultStaticSecret 资源
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: myapp-static-secret
  namespace: default
spec:
  type: kv-v2                     # 使用 KV V2 引擎
  mount: secret                   # Vault 中的挂载路径
  path: myapp/config              # Vault 中的 Secret 路径
  destination:
    name: myapp-k8s-secret        # 生成的 Kubernetes Secret 名称
    create: true                  # 自动创建 Secret
  refreshAfter: 1h                # 定期从 Vault 拉取最新数据, 每小时同步一次

开启 instantUpdates 可以实现基于 Vault 事件(Event)的近实时同步

当 Secret 更新后, 可以通过在 VaultStaticSecret 中配置 rolloutRestartTargets, 让 VSO 自动触发关联的 Deployment 或 StatefulSet 滚动更新

数据转换: 如果 Vault 中的原始数据格式不适合应用, 还可以使用 transformation 字段, 通过 Go 模板对数据进行处理后再存入 Kubernetes Secret

动态密码-数据库

# 启用数据库引擎
vault secrets enable database

PostgreSQL

配置连接

vault write database/config/my-db \
    plugin_name=postgresql-database-plugin \
    allowed_roles="my-role" \
    connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable" \
    username="postgres" \
    password="root-password"

定义角色与权限: 告诉 Vault 每次生成临时用户时执行什么 SQL

vault write database/roles/my-role \
    db_name=my-db \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
    default_ttl="1h" \
    max_ttl="24h"

生成临时密码

vault read database/creds/my-role

得到一个全新的 username (如 v-token-my-role-xxx) 和 password 1小时后, Vault 会自动在数据库里删掉这个用户

java引入配置

引入 spring-cloud-starter-vault-config

bootstrap.yml

spring:
  cloud:
    vault:
      uri: http://localhost:8200
      token: ${VAULT_TOKEN}
      kv:
        enabled: true
        backend: secret
        default-context: myapp/config

API

# 查看状态, 是否封锁和解封
curl https://vault.services.wait/v1/sys/seal-status

# API 可用性验证
curl http://IP:8200/v1/sys/health  # 返回包含 initialized 和 sealed 状态的 JSON

approle

AppRole 代表一组 Vault 策略和登录约束, 必须满足这些策略才能使用这些策略获取令牌

面向应用程序

roleID - 角色ID SecretID - 认证密钥

只产生 Vault Token, 解决你是谁的问题, 并不涉及权限 具体的权限要看绑定的 kv 或者 path 路径策略

# 开启 AppRole 认证
vault auth enable approle

token_type

  1. service 默认类型; 存储在 Vault 的内存和磁盘中, 支持续期(Renew), 吊销(Revoke), 并会在 Vault 界面显示;
  2. batch 类型; 类似 JWT, 所有的权限信息都加密编码在 Token 字符串本身; 极高性能, 减轻 Vault 存储后端的负担, 不可续期, 且一旦发出除非到期否则无法吊销; 适合短暂的自动化任务, 高并发的临时访问

role manager

vault delete auth/approle/role/terraform-role

# 创建角色并绑定策略 - auth/approle/role/terraform-role
vault write auth/approle/role/terraform-role \
    secret_id_ttl=0 \   # SecretID 自身的有效期
    secret_id_num_uses=0 \      # SecretID 使用次数限制
    token_type=service \        # 默认就是 service
    token_num_uses=0 \
    token_ttl=1h \      # Token 产生后的初始有效期
    token_max_ttl=3h \  # Token 允许被续期到的最长总时长, batch 类型的 Token 不可续期
    token_policies="admin-readonly"

# 获取 role-id
vault read auth/approle/role/homelab-app1/role-id

# 创建/获取 Secret ID
vault write -f auth/approle/role/homelab-app1/secret-id

登录并换取 Token


## 登录
vault write auth/approle/login \
    role_id="ae7830a4-ee10-3e84-b186-770b286b51b4" \
    secret_id="f8268b33-6b96-d08f-bcb7-0c959dcd09bd"

# 响应 json 里面有 client_token 字段
# 也可以通过 vault token lookup 查看认证和 token


curl \
    --request POST \
    --data '{"role_id":"ae7830a4-ee10-3e84-b186-770b286b51b4","secret_id":"f8268b33-6b96-d08f-bcb7-0c959dcd09bd"}' \
    https://vault.services.wait/v1/auth/approle/login
wait@ub05:~$ vault read auth/approle/role/homelab-app1/role-id
Key        Value
---        -----
role_id    3211340e-fe90-3236-36fe-ff7100763cf2
wait@ub05:~$
wait@ub05:~$ vault write -f auth/approle/role/homelab-app1/secret-id
Key                   Value
---                   -----
secret_id             b4d8c662-0042-040a-0527-dbbbe3acec90  # 密码
secret_id_accessor    b86c5e6d-880f-32e4-279b-eaf19151bc5b  # 访问器/工号
secret_id_num_uses    0
secret_id_ttl         0s

secret_id_accessor 即密钥的编号, 便于通过 secret_id_accessor 管理 secret, 例如删除

vault write auth/approle/role/homelab-app1/secret-id-accessor/destroy accessor=b86c5e6dxxx

CIDR绑定

在 Vault 的 AppRole 机制中, CIDR 绑定分为两个层级, 建议同时使用:

  • secret_id_bound_cidrs 限制 谁能拿 SecretID 去登录
  • token_bound_cidrs 限制 登录后拿到的 Token 只能在哪些 IP 使用
vault write auth/approle/role/homelab-node \
    secret_id_num_uses=0 \
    secret_id_ttl="0" \

    # 核心限制:只允许来自 指定 的 IP 请求登录, 多ip使用逗号分隔
    secret_id_bound_cidrs="192.168.1.5/32" \

    # 核心限制: 生成的 Token 也只能在 这个 ip 上使用
    token_bound_cidrs="192.168.1.5/32" \
    token_ttl="1h" \
    token_max_ttl="3h" \
    policies="pki-policy"           # 绑定某个策略

userpass

vault 内部的账号体系

可以通过 Vault 的 API, CLI 或 UI 来登录这个账号 Vault 验证密码正确后, 会现场生成一个 Token

vault auth enable userpass      # 开启 userpass 认证
vault auth list                 # 查看已经开启的认证方法
vault list auth/userpass/users  # 查看全部用户

# 新建一个用户, 增加策略也是这个命令
vault write auth/userpass/users/alice \
    password="foo" \
    policies="admin"            # 关联 admin 这个策略

# 登录时会自动获取 token
vault login -method=userpass username=admin

curl --request POST \
    --data '{"password": "user_password"}' \
    ${VAULT_ADDR}/v1/auth/userpass/login/admin


# 设置环境变量
export VAULT_TOKEN="hvs.xxx..."

vault login -method=userpass username=admin
Key                    Value
---                    -----
token                  hvs.CAESIKXoJd6k37JUxGEZZ5tHOdBydB4Y07Zfl0B3z8FrmpY-Gh4KHGh2cy5YT3FLaGNEbEJSeDJPYUdRcTFZN3R4aTg
token_accessor         jfy69OCJVuZXn6gjKfdWbsmg
token_duration         768h
token_renewable        true
token_policies         ["admin" "default"]
identity_policies      []
policies               ["admin" "default"]
token_meta_username    admin

# 查看 Token 详情, 验证权限
vault token lookup

Key                 Value
---                 -----
accessor            jfy69OCJVuZXn6gjKfdWbsmg
creation_time       1775833166
creation_ttl        768h
display_name        userpass-admin
entity_id           bebb8efc-3615-7fff-219d-78598cf87bb3
expire_time         2026-05-12T22:59:26.642334339+08:00
explicit_max_ttl    0s
id                  hvs.CAESIKXoJd6k37JUxGEZZ5tHOdBydB4Y07Zfl0B3z8FrmpY-Gh4KHGh2cy5YT3FLaGNEbEJSeDJPYUdRcTFZN3R4aTg
issue_time          2026-04-10T22:59:26.642342468+08:00
meta                map[username:admin]
num_uses            0
orphan              true
path                auth/userpass/login/admin
policies            [admin default]
renewable           true
ttl                 767h59m35s
type                service


# 用户登录后修改密码
vault write auth/userpass/users/alice/password password="NewSecurePassword"

engine

secret engine

Generic: KV, Kubernetes, LDAP, PKI Certificates, SSH, TOTP, Transit

Cloud: AliCloud AWS Azure Google Cloud Google Cloud KMS

Infra: Consul Databases Nomad RabbitMQ

Generic (通用类): 核心功能与加解密

引擎 作用
KV Key-Value 存放静态密钥, api token 等
Transit 加密服务
PKI Certificates: CA (证书颁发机构) 自动为内网服务签发 TLS 证书
SSH 生成临时的 SSH 登录凭证
TOTP 生成临时的 动态两步验证码;
LDAP
Kubernetes 通常指机密引擎(为 K8s 生成 Service Account Token), 而不是登录认证;

Cloud: AliCloud / AWS / Azure / GCP 场景: 动态生成 IAM 角色账号 程序向 Vault 申请, Vault 调用云 API 临时创建一个只拥有特定权限的 AK

Google Cloud KMS / AWS KMS 密钥桥接; 让 Vault 利用云端的硬件安全模块(HSM)来保护自己的主密钥

Infra (基础设施类): 中间件动态授权

Databases: 支持 MySQL, Postgres, MongoDB, Redis 等 应用请求角色, Vault 会去数据库创建账号并赋权, 消除共享的 DB 账号

Consul / Nomad: 动态生成 Consul 的 ACL Token 或 Nomad 的调度权限

RabbitMQ: 动态管理消息队列的用户和权限(VHosts, Permissions)

cubbyhole 每个 token 都有的临时存储空间 仅当前 Token 可见

即使是 Root Token, 也无法看到其他 Token 的 cubbyhole
随 Token 销毁的, 一旦 Token 到期, 被撤销或注销, 该 Token 在 cubbyhole/ 下存储的所有数据会立即被永久物理删除

aws


vault secrets enable aws

# 写入一个高级权限的aws账号
vault write aws/config/root \
    access_key=AKIAZXN6JNH5D6ZSHS5V \
    secret_key=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 \
    region=us-east-1

# 创建一个角色(vpc),让它可以操作 VPC
vault write aws/roles/vpc \
    credential_type=iam_user \
    policy_document=-<<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "ec2:*",
      "Resource": "*"
    }
  ]
}
EOF

# 获取 access_key 和 secret_key, 这里获取到的是 aws 的;
# Vault 中把动态生成的机密信息关联了一个 lease 租约, 默认 768h 可用
vault read aws/creds/vpc
# 手动吊销租约, 会删除 aws iam 账号
vault lease revoke aws/creds/vpc/w8OTDxIri80TCkGtoZPkc6Qc

# 设置生成的 Key 的有效时间
vault secrets tune -default-lease-ttl=30m aws/

alicloud

# 开启阿里云认证插件
vault auth enable alicloud

# 配置 Vault 访问阿里云的凭证(建议给这个 RAM 用户最小权限: Action: "sts:GetCallerIdentity")
vault write auth/alicloud/config \
    access_key="YOUR_ACCESS_KEY" \
    secret_key="YOUR_SECRET_KEY"

# 创建 Vault 角色并绑定 RAM 角色
# 这一步是建立 RAM 角色 (ARN) 与 Vault 策略 之间的映射关系

# 创建一个 Vault 角色, 绑定到特定的阿里云 RAM 角色 ARN
vault write auth/alicloud/role/homelab-gpu-node \
    arn="acs:ram::1234567890:role/my-gpu-instance-role" \
    policies="pki-policy,kv-readonly" \
    ttl="1h"

# 只有拥有 my-gpu-instance-role 这个 RAM 角色的实例, 才能登录这个 Vault 角色

kv

v1 版本比较简单, 没有多版本 v2 支持多版本

# 启用 v1
vault secrets enable -version=1 kv
vault kv put kv/my-secret my-value=s3cr3t

vault kv get kv/my-secret

vault kv delete kv/my-secret

vault kv list homelab/infra/
vault kv get homelab/infra/aliyun
vault kv list homelab/infra/ssh-keys/


vault kv put secret/test db_password=123456   # 创建秘密
vault kv get secret/test                      # 读取秘密(应显示 db_password:123456)

Transit 加密服务

  1. 自身不存储数据
  2. 只负责提供加解密数据的api, 即只执行不存储;

支持的加密方式 (算法)

类型 算法
对称加密 AES-GCM (默认), AES-CBC, ChaCha20-Poly1305
非对称加密 RSA (2048/4096), ECDSA (P-256/384/521), Ed25519
哈希 SHA-2 (224/256/384/512)
HMAC SHA-2 (256/384/512)

性能: 标准4核主机, AES-GCM 这种硬件加速算法每秒可以处理约万次请求

关键特性: 收敛加密 (Convergent Encryption) Vault 支持一种模式, 使得相同的明文和上下文在加密后产生相同的密文, 这在数据库脱敏后的"等值查询"中非常有用

信封加密

  1. Vault 端: 仅生成一个 数据密钥 (Data Encryption Key, DEK);
  2. 应用端使用这个 DEK 对数据进行加密和解密
  3. 应用向 Vault 请求用"主密钥"加密这个 DEK, 并将加密后的 DEK 连同密文一起存入数据库
  4. 应用重启后需要先向将 DEK 的密文给 vault 解密, 然后用 DEK 去解密业务数据;

优点: 只有获取 / 加密 DEK 时调用一次 Vault, 实际大数据量的加解密在本地 CPU 完成

ssh

原理: ssh 服务器通过信任CA根证书的方式允许可信客户端登录

  1. vault 是 ca 签署者
  2. 签署用户的 ssh 公钥后 生成 证书文件
  3. 用户使用 ssh 私钥 + 证书文件 登录服务器
# 开启 SSH 秘密引擎
vault secrets enable -path=ssh-client-signer ssh

# 配置 Vault 生成自己的 CA 私钥和公钥
# 注意: Vault 会安全地保存私钥, 只把公钥给你
vault write ssh-client-signer/config/ca generate_signing_key=true

需要获取 Vault 的 CA 公钥, 并把它放到你所有服务器 /etc/ssh/trusted-user-ca-keys.pem
并在 sshd_config 里引用它

# 获取公钥并保存
curl -o /etc/ssh/ssh_ca.pub $VAULT_ADDR/v1/ssh-client-signer/public_key

示例

创建 Vault 角色 (Role) 创建一个名为 ops-role 的角色, 它强制要求证书中必须包含 ops 这个勋章, 并且限制有效期为 1 小时

vault write ssh-client-signer/roles/ops-role \
    key_type=ca \
    allow_user_certificates=true \
    allowed_users="ops" \           # 为证书里的角色名
    default_extensions="permit-pty,permit-port-forwarding" \
    allow_user_key_ids=true \
    key_id_format="{{token_display_name}}" \
    max_ttl="1h" \
    ttl="1h"


# key_id_format: 这会自动把登录 Vault 的用户名(如 alice)填入证书的 Key ID (-I)

用户申请签名

用户生成自己的ssh公私钥

ssh-keygen -t ed25519 -f ~/.ssh/vault_temp -N ""

# 提交自己的公钥进行签名 - 获取到证书文件
vault write -field=signed_key ssh-client-signer/sign/ops-role \
    public_key=@$HOME/.ssh/vault_temp.pub > ~/.ssh/vault_temp-cert.pub

# 登录目标系统的公共账号 ops-runner, 会自动引用 vault_temp-cert.pub 文件
ssh -i ~/.ssh/vault_temp ops-runner@192.168.5.x

PKI

tls 证书管理

是 CA, 还负责身份校验, 权限控制和短期证书颁发 Vault 维护动态的 CRL(证书撤销列表)

组合

  1. Root CA: 可以在 Vault 内部生成
  2. Intermediate CA: 由根 CA 签发, 专门用于处理日常请求
  3. Role(角色): 你可以定义一个角色 homelab-dot-com, 限制它只能签发 *.homelab.com, 有效期最长 24h

与 cert-manager 组合 cert-manager 只是证书的搬运工, 自身不生成证书

安装 cert-manager, 配置一个 Issuer 类型为 vault

  1. cert-manager 自动向 Vault 申请证书
  2. Vault 验证 K8s 的 ServiceAccount
  3. Vault 返回证书, cert-manager 将其存入 Secret 供 app 使用
# 配置 PKI 角色, 限制只能申请特定的域名, 有效期 24 小时
vault write pki_int/roles/homelab-node \
    allowed_domains="homelab.local" \
    allow_subdomains=true \
    max_ttl="24h" \
    generate_lease_with_token=true

客户端脚本

#!/bin/bash
set -e

# --- 配置区 ---
VAULT_ADDR="https://vault.services.wait"
ROLE_NAME="homelab-node"
COMMON_NAME="ub05.homelab.local"   # 当前机器的域名
CERT_DIR="/etc/ssl/vault"
RESTART_COMMAND="systemctl reload nginx" # 或者 traefik

# AppRole 凭证 (建议通过环境变量或受保护的文件读取)
ROLE_ID="你的 -role-id"
SECRET_ID="你的 -secret-id"

# --- 1. 登录 Vault 获取临时 Token ---
echo "Logging into Vault..."
LOGIN_TOKEN=$(vault write -field=token auth/approle/login \
    role_id="$ROLE_ID" secret_id="$SECRET_ID")

export VAULT_TOKEN=$LOGIN_TOKEN

# --- 2. 申请新证书 ---
echo "Requesting new certificate for $COMMON_NAME..."
CERT_DATA=$(vault write -format=json pki_int/issue/$ROLE_NAME \
    common_name="$COMMON_NAME" ttl="24h")

# --- 3. 解析并保存证书, 私钥和 CA 链 ---
mkdir -p $CERT_DIR
chmod 700 $CERT_DIR

echo "$CERT_DATA" | jq -r '.data.certificate' > $CERT_DIR/cert.pem
echo "$CERT_DATA" | jq -r '.data.private_key' > $CERT_DIR/key.pem
echo "$CERT_DATA" | jq -r '.data.issuing_ca' > $CERT_DIR/ca.pem

# 合并证书链 (很多 Web 服务需要这个)
cat $CERT_DIR/cert.pem $CERT_DIR/ca.pem > $CERT_DIR/fullchain.pem

echo "Certificates updated in $CERT_DIR"

# --- 4. 重载服务 ---
echo "Reloading service..."
$RESTART_COMMAND

echo "Done!"

安全

# 强制启用 TLS 加密
listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = 0
  tls_cert_file = "/vault/certs/cert.pem"  # 挂载证书目录
  tls_key_file  = "/vault/certs/key.pem"
}

# 使用分布式存储(替代文件存储)
storage "consul" {  # 或 etcd/raft
  address = "consul:8500"
  path    = "vault/"
}

# 启用审计日志
vault audit enable file file_path=/vault/logs/audit.log  # 登录后执行
创建子 token 和限制策略
vault policy write mysql-readonly readonly-policy.hcl

# 创建带限制的子 Token
# 创建一个有效期 24 小时, 只能用 10 次, 绑定该策略的子 Token
# 得到一个以 hvs. 开头的 Token, 它只能执行策略定义的读取操作
vault token create -policy="mysql-readonly" -ttl="24h" -use-limit=10

轮换 KMS 主密钥 (底层加密)

在阿里云 KMS 侧, 可以手动触发主密钥(CMK)的轮换, 或者开启"自动轮换"功能(如每 365 天一次);

效果: KMS 会生成一个新的密钥版本(Key Version);

对 Vault 的影响: Vault 无需重启; 它下次加密数据时会使用新版本, 解密旧数据时 KMS 会自动识别并使用旧版本; 这是对业务无感的最佳方案;

policy策略

权限 / 策略

  1. 允许访问 Vault 中哪些路径;
  2. 策略可以附加到 token 或 角色

Vault 采用一组具有优先级的判定规则来决定最为具体的路径匹配; 如果一个匹配模式被多个策略使用并能匹配上给定路径, Vault 会取其能力的并集

如果一个路径能被多个策略定义的不同的匹配模式所匹配, 那么只有最高优先级的匹配会被采用

如果 P1 中第一个 + 或是 * 出现的位置早于 P2, 那么采用 P2 如果 P1 以 * 结尾, 而 P2 不是, 那么采用 P2 如果 P1 包含更多的 + 段, 那么采用 P2 如果 P1 更短, 那么采用 P2 如果 P1 得到的字典序更小, 那么采用 P2 仅允许 * 出现在模式的末尾

一旦令牌被签发, 其关联的策略无法再被修改; 必须吊销旧令牌并申请新令牌才能得到更新后的关联策略

令牌关联的策略内容是实时解析的, 也就是说, 如果更新了策略内容, 附加此策略的令牌下次的请求就会按照新策略内容进行权限检查

path "secret/foo" {
  capabilities = ["read"]
  required_parameters = ["bar", "baz"]  # 必须指定的参数

  # 针对指定路径允许操作的键值对的白名单, 非空时只能使用列表中限定的值
  allowed_parameters = {
    "bar" = []
  }

  # 键值对的黑名单,优先级高于 allowed_parameters
  denied_parameters = {
    "bar" = []
  }


  min_wrapping_ttl = "1s"       # 客户端可以指定的响应封装有效期的最小值
  max_wrapping_ttl = "90s"      # 允许设置的响应封装有效期的最大值

}

path

五个核心板块: 身份与认证(Auth), 系统管理(Sys), 身份实体(Identity), 秘密引擎(KV & PKI)

认证相关路径 (Auth & AppRole)

控制用户如何进入 Vault 以及如何管理登录方式

path 用途
auth/approle/login 这是 AppRole 登录的端点
auth/* 所有认证方法(如 Userpass, GitHub, LDAP)的配置接口
sys/auth/* 用于管理"挂载点"

如果需要程序通过 RoleID 和 SecretID 获取 Token, 必须拥有 auth/approle/login 路径的 create 或 update 权限;

开启一个新的认证方式或关闭已有的, 就需要访问 sys/auth/

系统管理与审计 (Sys)

是 Vault 服务器级别的底层控制路径

path 用途
sys/health 返回 Vault 节点状态(是否初始化, 解封, 主节点)
sys/audit 审计日志管理
sys/leases/* 租约管理, 可以续期和撤销
sys/mounts/* 秘密引擎管理

例如手动执行的 vault secrets enable, 本质上就是在操作sys/mounts路径, 决定在哪个路径挂载哪种秘密存储

策略管理 (Policies)

path 用途
sys/policies/acl/* 管理所有的 ACL 策略, 即 hcl 策略文件
sys/policies/acl/admin 专门指代名为 admin 的策略;

通常在 CI/CD 或权限分配时, 会限制用户只能读取而不能修改这个特定的admin管理员策略

身份模型 (Identity)

是 Vault 的核心, 它把不同的登录方式(Alias)统一到一个"人"(Entity)身上 每个 Entity 代表一个真实的用户或机器

alias 别名: 它负责把 userpass 里的 admin 和 github 里的 wait 映射到同一个 Identity Entity 上;

path 用途
identity/entity/* 管理"身份实体"
identity/entity-alias/* 管理"别名"
identity/entity/id 通过 ID 精确查询某个身份的信息
identity/entity/name 通过 名称精确查询某个身份的信息

秘密引擎路径 (KV & PKI)

KV-V2 (Key-Value)

kvv2/data/ KV-V2 的路径中必须包含 data/ 才能读写实际数据 kvv2/data/prod/* kvv2/data/dev/*

PKI (证书颁发机构)

path 用途
pki_int/issue/* 签发证书; 通过请求此路径并指定 Role, Vault 会动态生成私钥和证书;
pki_int/revoke 吊销证书; 将证书序列号加入吊销列表(CRL);
pki_int/tidy 清理操作; 删除过期的证书以释放存储空间, 保持 Vault 性能;
pki/config/urls 配置证书的下载地址和 CRL 分发点;

Capabilites 动作

create(POST/PUT)        # 允许在指定路径创建数据
read(GET)               # 允许读取指定路径的数据
delete(DELETE)          # 允许删除指定路径的数据

# 允许修改指定路径的数据; 对多数 Vault 部件来说, 这隐含了在指定位置创建初始值的能力
update(POST/PUT)

# 允许列出指定路径的所有键;
# 要注意的是, 经由 list 操作返回的键是未经策略过滤的;
# 请勿在键名中编码敏感信息; 不是所有后端都支持 list 操作
list(LIST)

# 允许访问需要根权限保护的路径;
# 除非拥有 sudo 能力, 否则令牌被禁止与这些路径交互(对这种路径的操作可能同时需要其他能力, 例如 read 或 delete)
# 例如, 修改审计日志后端配置就需要令牌具有 sudo 特权;
sudo

# 不允许访问;
# 该定义总是优先于其他能力定义, 包括 sudo, 只要能力中存在 deny, 就不允许执行任何操作
deny

管理cli

vault policy list                   # 列出策略
vault policy read <policy-name>     # read
vault read sys/policy               # 列出所有已注册的策略

# create
vault policy write policy-name policy-file.hcl

# update
vault write my-existing-policy updated-policy.json

vault delete sys/policy/policy-name


# 关联策略

## 创建用户时关联策略
vault write auth/userpass/users/user1 \
    password="s3cr3t!" \
    policies="dev-readonly,logs"

vault login -method="userpass" username="user1"

## 创建令牌时附加策略
vault token create -policy=dev-readonly -policy=logs

示例

# 允许对 secret/foo 路径进行读
path "secret/foo" {
  capabilities = ["read"]
}

# 允许访问
path "secret/*" {
  capabilities = ["create", "read", "update", "delete", "list"]
}


# 拒绝访问, 拒绝的优先级最高
path "secret/super-secret" {
  capabilities = ["deny"]
}

# 允许创建, 但是只能是限定值
path "secret/restricted" {
  capabilities = ["create"]
  allowed_parameters = {
    "foo" = []
    "bar" = ["zip", "zap"]
  }
}

# 模糊匹配
path "secret/zip-*" {
  capabilities = ["read"]
}

# + 代表路径中一个段内任意长度的字符
path "secret/+/teamb" {
  capabilities = ["read"]
}

# 除 bar 外可以对任意 key 设置任意值
path "secret/foo" {
  capabilities = ["create"]
  allowed_parameters = {
    "bar" = ["zip", "zap"]
    "*"   = []
  }
}

path "secret/foo" {
  capabilities = ["create"]
  denied_parameters = {
    "bar" = ["zip", "zap"]
  }
}

path "secret/foo" {
  capabilities = ["create"]
  denied_parameters = {
    "*" = []
  }
}
# 允许读取特定路径下的秘密
path "secret/data/mysql/*" {
  capabilities = ["read"]
}

# 允许列出秘密列表
path "secret/metadata/mysql/*" {
  capabilities = ["list"]
}

内置策略

Vault 有两个内建策略: default 和 root

default

默认情况下, 它被附加到所有令牌上, 但可以通过使用身份验证方式创建令牌时显式排除

该策略包含了基础的功能, 例如准许令牌查询有关自身的数据以及使用 Cubbyhole 数据

# 创建令牌时排除 default 策略
vault token create -no-default-policy

curl \
  --request POST \
  --header "X-Vault-Token: ..." \
  --data '{"no_default_policy": "true"}' \
  https://vault.hashicorp.rocks/v1/auth/token/create

root

任何关联了该策略的用户都将是根用户; 强烈建议在生产环境中使用 Vault 前首先吊销所有的根令牌

# 吊销根令牌
vault token revoke "<token>"

curl \
  --request POST \
  --header "X-Vault-Token: ..." \
  --data '{"token": "<token>"}' \
  https://vault.hashicorp.rocks/v1/auth/token/revoke

插件

Ethereum Secrets Engine 插件

Vault 变成一个 远程签名机(Remote Signer): 私钥永远不出 Vault 内存, 你只需发送交易摘要给 Vault, 它返回签名后的结果

存储: 私钥经过 Vault 的主密钥加密后存入 Raft 计算: 签名操作在 Vault 内部完成

访问: 你的 Sepolia 节点或应用通过 AppRole 获取一个短期的 Token, 仅能调用 /sign 接口

# 第一步: 启用插, 进入容器执行, 或配置好本地 VAULT_ADDR 后执行
vault secrets enable ethereum

# 第二步: 创建钱包(私钥)

# 让 Vault 生成新钱包, 这会返回一个以太坊地址(如 0xabc...), 但不会返回私钥
vault write ethereum/accounts/my-sepolia-wallet

# 方法 2: 导入现有私钥
vault write ethereum/accounts/import-wallet \
    private_key="你的十六进制私钥"


## 第三步: 请求签名(交易)
# 当你的 Sepolia 节点需要发送一笔交易时, 应用构造好交易 RLP 编码或交易 JSON, 发送给 Vault:
# Vault 会返回一个 signed_transaction, 直接将其广播到以太坊网络即可
```bash
vault write ethereum/accounts/my-sepolia-wallet/sign \
    transaction='{"to":"0x...","value":"1000000000000000000","gas":21000,"gas_price":"20000000000"}'

分层确定性钱包 (HD Wallet)

Vault 支持 BIP-32/BIP-44

在 Vault 中存储一个 助记词 (Mnemonic) 通过不同的路径衍生出无数个地址(如 m/44'/60'/0'/0/1) 只需要备份一次 Vault, 就能管理整个钱包矩阵

deploy

准备镜像

docker pull hashicorp/vault:1.21

https://hub.docker.com/r/hashicorp/vault

dev 模式

不持久化

注意: 添加 VAULT_DEV_ROOT_TOKEN_ID 后会强制为开发模式

services:
  vault:
    image: hashicorp/vault:1.21
    container_name: vault
    restart: always
    environment:
      VAULT_DEV_ROOT_TOKEN_ID: xxxxxxxxxxxxxewofoeiwoiieo
      VAULT_ADDR: "=http://0.0.0.0:8200"
      TZ: Asia/Shanghai
      LANG: en_US.UTF-8
    cap_add:
      - IPC_LOCK
    ports:
      - 8200:8200/tcp
docker exec -it vault-dev sh vault status  # 输出 Sealed: false 即正常

单节点-持久化

mkdir -p /opt/vault/{config,data,logs}
chown -R 100:100 /opt/vault/    # Vault 默认用户 ID 是 100

docker run -d --name vault-dev \
  -p 8200:8200 \
  -v /opt/vault/config:/vault/config \
  -v /opt/vault/data:/vault/data \
  --cap-add=IPC_LOCK \
  hashicorp/vault:latest \
  server

vault.hcl

api_addr = "http://127.0.0.1:8200"
cluster_addr = "http://127.0.0.1:8201"
ui = true

storage "raft" {
  path    = "/vault/data"
  node_id = "node1"
}

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = 1       # 开发环境禁用 TLS
}

单节点-持久化-compose

volumes:
  vault_volume:
    name: vault_volume

services:
  vault:
    image: hashicorp/vault:1.21
    container_name: vault
    restart: always
    environment:
      VAULT_ADDR: "=http://0.0.0.0:8200"
      TZ: Asia/Shanghai
      LANG: en_US.UTF-8
    cap_add:
      - IPC_LOCK

    networks:
      - core
    ports:
      - 8200:8200/tcp
    volumes:
      - vault_volume:/vault
      - ./vault.hcl:/config/vault.hcl:ro
    command:
      - "server"
      - "-config=/config/vault.hcl"
    labels:
      - "service_name=vault"
    logging:
      options:
        labels: "service_name"

vault.hcl

ui = true                       # 启用 Web UI
default_lease_ttl = "168h"      # 默认租约 7 天
max_lease_ttl     = "720h"      # 最大租约 30 天

listener "tcp" {
  address     = "0.0.0.0:8200"
  tls_disable = 1
}

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

三节点模式

Storage: 使用 Raft;

Networking: 节点间通过 8201 端口同步数据;

Auto-Unseal: 建议配合云厂商的 KMS 使用(如阿里云 KMS, AWS KMS)

初始化 会生成 5 个 Unseal Keys

docker exec -it vault-1 vault operator init

解封 每个节点都需要, 输入3/5个分片密钥

vault operator unseal

docker-compose.yml

services:
  vault-1:
    image: hashicorp/vault:latest
    container_name: vault-1
    volumes:
      - ./vault-1/config:/vault/config
      - ./vault-1/data:/vault/data
    cap_add:
      - IPC_LOCK
    command: server
    ports:
      - "8200:8200"
    environment:
      VAULT_ADDR: 'http://127.0.0.1:8200'

  vault-2:
    image: hashicorp/vault:latest
    container_name: vault-2
    volumes:
      - ./vault-2/config:/vault/config
      - ./vault-2/data:/vault/data
    cap_add:
      - IPC_LOCK
    command: server
    ports:
      - "8202:8200"

  vault-3:
    image: hashicorp/vault:latest
    container_name: vault-3
    volumes:
      - ./vault-3/config:/vault/config
      - ./vault-3/data:/vault/data
    cap_add:
      - IPC_LOCK
    command: server
    ports:
      - "8203:8200"

vault-1.hcl

api_addr = "http://vault-1:8200"        # 业务可用地址
cluster_addr = "http://vault-1:8201"    # 节点通信地址
ui = true

listener "tcp" {
  address     = "0.0.0.0:8200"
  cluster_address = "0.0.0.0:8201"
  tls_disable = 1
}

storage "raft" {
  path    = "/vault/data"
  node_id = "vault-1"

  # 初始启动时指定其他节点地址, 或者通过 retry_join 自动发现
  retry_join {
    leader_api_addr = "http://vault-2:8200"
  }
  retry_join {
    leader_api_addr = "http://vault-3:8200"
  }
}
最后更新于