Vault 使用
Vault 配置模块为 mcube 应用提供了与 HashiCorp Vault 集成的能力,支持从 Vault 读取秘密、管理凭证和加密数据。
什么是 Vault?
HashiCorp Vault 是一个统一的秘密管理平台,用于安全地存储和访问敏感数据。
为什么需要 Vault?
在传统开发中,我们经常遇到这些问题:
❌ 配置文件中硬编码密码
database:
password: "mypassword123" # 不安全!
❌ 环境变量泄露风险
export DB_PASSWORD="secret" # 进程列表可见
❌ 静态凭证难以轮换
// 密码写死在代码中,更换困难
conn := db.Connect("postgres://admin:oldpassword@localhost")
✅ 使用 Vault 后
// 从 Vault 动态获取最新凭证
resp, _ := client.Secrets.KvV2Read(ctx, "database/config")
password := resp.Data.Data["password"].(string)
Vault 能做什么?
| 功能 | 场景 | 价值 |
|---|
| 秘密存储 | 存储 API 密钥、数据库密码、证书等敏感信息 | 集中管理,加密存储,访问控制 |
| 动态凭证 | 为数据库、云平台生成临时凭证 | 凭证自动过期,减少泄露风险 |
| 数据加密 | 加密敏感数据(信用卡号、身份证号) | 无需管理加密密钥,Vault 负责加密逻辑 |
| 证书管理 | 自动签发和续期 TLS 证书 | 简化 PKI 管理,自动化证书生命周期 |
核心概念
1. 秘密(Secret)
秘密是指任何需要严格控制访问的敏感数据,例如:
- API 密钥:第三方服务的认证令牌
- 数据库密码:MySQL、PostgreSQL 连接密码
- 加密密钥:用于数据加密的密钥材料
- TLS 证书:HTTPS 通信所需的证书和私钥
2. 认证(Authentication)
应用程序需要向 Vault 证明身份后才能访问秘密:
- Token:最简单,适合开发测试
- AppRole:基于角色的认证,适合服务器应用
- Kubernetes:利用 K8s ServiceAccount,适合容器环境
3. 策略(Policy)
定义谁可以访问哪些秘密,实现细粒度权限控制:
path "secret/data/myapp/*" {
capabilities = ["read"] # 只能读取
}
path "database/creds/readonly" {
capabilities = ["read"] # 只能读取数据库凭证
}
4. 挂载点(Mount Path)
挂载点是 Vault 中秘密引擎的安装位置。Vault 支持在不同路径挂载多个秘密引擎实例。
Vault 路径结构 = 挂载点 + 秘密路径
├─────┘ └─────┘
mount secret
path path
示例:secret/myapp/config
- 挂载点: secret (KV v2 引擎挂载位置)
- 秘密路径: myapp/config (实际存储路径)
常见挂载点:
| 引擎类型 | 默认挂载点 | 用途 |
|---|
| KV v2 | secret | 键值对存储 |
| Transit | transit | 数据加密/解密 |
| Database | database | 动态凭证生成 |
| PKI | pki | 证书签发 |
CLI vs API 使用对比:
# Vault CLI 自动识别挂载点
vault kv get secret/myapp/config
└──┬──┘└────┬────┘
挂载点 秘密路径
// vault-client-go 需要分离指定
client.Secrets.KvV2Read(
ctx,
"myapp/config", // 秘密路径(不含挂载点)
vault.WithMountPath("secret"), // 通过参数指定挂载点
)
// mcube 辅助方法自动使用配置
// 配置文件: kv_mount_path = "secret"
vault.ReadSecret(ctx, "myapp/config") // 自动使用配置的挂载点
查看 Vault 挂载点:
vault secrets list
# 输出:
# Path Type
# ---- ----
# secret/ kv # KV v2 引擎
# transit/ transit # 加密引擎
# database/ database # 数据库引擎
常见错误:
// ❌ 错误:路径中包含挂载点
client.Secrets.KvV2Read(ctx, "secret/myapp/config")
// 实际访问:kv-v2/data/secret/myapp/config (404错误)
// ✅ 正确:分离挂载点和秘密路径
client.Secrets.KvV2Read(
ctx,
"myapp/config", // 秘密路径
vault.WithMountPath("secret"), // 挂载点参数
)
特性
- ✅ 多种认证方式: Token、AppRole、Kubernetes 认证
- ✅ 自动 Token 续期: 支持自动续期,避免 token 过期
- ✅ TLS 支持: 支持 TLS 客户端证书认证
- ✅ Namespace支持: 支持企业版 Namespace 功能
- ✅ 灵活的客户端: 直接返回 vault-client-go 客户端,支持所有 Vault 功能
典型使用场景
场景 1:统一管理应用配置
问题: 多个微服务共享数据库密码,密码散落在各个配置文件中,更换密码需要重启所有服务。
方案: 将密码存储在 Vault,应用启动时从 Vault 读取最新密码。
// 应用启动时读取数据库配置
resp, err := client.Secrets.KvV2Read(
ctx,
"myapp/database",
vault.WithMountPath("secret"), // 指定 KV 引擎挂载点
)
dbConfig := map[string]string{
"host": resp.Data.Data["host"].(string),
"port": resp.Data.Data["port"].(string),
"username": resp.Data.Data["username"].(string),
"password": resp.Data.Data["password"].(string),
}
// 连接数据库
db := connectDatabase(dbConfig)
收益:
- ✅ 集中管理:更新密码只需更新 Vault,无需修改每个服务的配置
- ✅ 访问审计:可追踪哪个服务在什么时候访问了密码
- ✅ 版本控制:Vault KV v2 支持秘密版本,可回滚到旧密码
场景 2:保护敏感数据
问题: 需要存储用户的信用卡号、身份证号等敏感信息,但不想自己管理加密密钥。
方案: 使用 Vault 的 Transit 加密引擎,在保存前加密,读取时解密。
import "github.com/hashicorp/vault-client-go/schema"
// 加密敏感数据
plaintext := base64.StdEncoding.EncodeToString([]byte("6222021234567890"))
encResp, _ := client.Secrets.TransitEncrypt(
ctx,
"credit-cards",
schema.TransitEncryptRequest{Plaintext: plaintext},
vault.WithMountPath("transit"), // Transit 引擎挂载点
)
// 存储到数据库
db.Save(encResp.Data.Ciphertext) // 存储: vault:v1:abcd1234...
// 从数据库读取并解密
ciphertext := db.Query(...)
decResp, _ := client.Secrets.TransitDecrypt(
ctx,
"credit-cards",
schema.TransitDecryptRequest{Ciphertext: ciphertext},
vault.WithMountPath("transit"),
)
original, _ := base64.StdEncoding.DecodeString(decResp.Data.Plaintext)
收益:
- ✅ 密钥集中管理:加密密钥存储在 Vault,应用无需管理密钥
- ✅ 密钥轮换:Vault 支持密钥轮换,旧数据仍可解密
- ✅ 合规要求:符合 PCI-DSS、GDPR 等数据保护规范
场景 3:动态数据库凭证
问题: 所有应用共享一个 MySQL 账号,一旦泄露影响范围大,且无法追踪是谁泄露的。
方案: 使用 Vault 动态生成临时数据库账号,每个应用实例使用独立凭证,凭证自动过期。
// 请求临时数据库凭证(有效期 1 小时)
resp, _ := client.Secrets.DatabaseGenerateCredentials(
ctx,
"readonly-role",
vault.WithMountPath("database"), // Database 引擎挂载点
)
username := resp.Data.Data["username"].(string) // vault-token-abc123
password := resp.Data.Data["password"].(string) // 随机生成
// 使用临时凭证连接数据库
dsn := fmt.Sprintf("%s:%s@tcp(localhost:3306)/mydb", username, password)
db, _ := sql.Open("mysql", dsn)
// 1 小时后凭证自动失效,数据库账号被删除
收益:
- ✅ 最小权限:每个应用只获得所需的最小权限(如只读访问)
- ✅ 自动过期:凭证有生命周期,即使泄露影响也可控
- ✅ 审计追踪:可追踪每个凭证是谁在何时申请的
场景 4:自动化证书管理
问题: 微服务网格需要大量 TLS 证书,手动签发和续期证书繁琐且容易出错。
方案: 使用 Vault PKI 引擎自动签发和续期证书。
import "github.com/hashicorp/vault-client-go/schema"
// 申请证书(有效期 30 天)
resp, _ := client.Secrets.PkiIssue(
ctx,
"myapp-role",
schema.PkiIssueRequest{
CommonName: "service1.example.com",
Ttl: "720h", // 30 days
},
vault.WithMountPath("pki"), // PKI 引擎挂载点
)
certificate := resp.Data.Certificate
privateKey := resp.Data.PrivateKey
// 配置 TLS 服务器
cert, _ := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
收益:
- ✅ 自动化:无需手动生成 CSR、签名、分发证书
- ✅ 短周期:证书短期有效(如 30 天),减少泄露风险
- ✅ 零信任:结合服务网格实现服务间零信任通信
快速开始
1. 引入模块
import (
_ "github.com/infraboard/mcube/v2/ioc/config/vault"
)
2. 配置
在 application.toml 中添加 Vault 配置:
[vault]
# 基础配置
address = "http://127.0.0.1:8200"
auth_method = "token"
token = "myroot"
auto_renew = true
# 挂载点配置(可选,使用默认值即可)
# 这些配置被辅助方法自动使用,无需在代码中手动指定
kv_mount_path = "secret" # KV v2 引擎挂载点,默认 "secret"
transit_mount_path = "transit" # Transit 引擎挂载点,默认 "transit"
database_mount_path = "database" # Database 引擎挂载点,默认 "database"
pki_mount_path = "pki" # PKI 引擎挂载点,默认 "pki"
挂载点配置说明:
- 开发环境:使用默认值即可(与 Vault CLI 保持一致)
- 生产环境:可根据实际挂载点配置调整,如
kv_mount_path = "prod-kv"
3. 使用辅助方法(推荐)
辅助方法自动使用配置文件中的挂载点,代码更简洁:
import (
"context"
"github.com/infraboard/mcube/v2/ioc/config/vault"
)
ctx := context.Background()
// 读取秘密 - 自动使用配置的 kv_mount_path
// 等价于 CLI: vault kv get secret/myapp/config
resp, err := vault.ReadSecret(ctx, "myapp/config")
if err != nil {
panic(err)
}
// 访问秘密数据
for key, value := range resp.Data.Data {
fmt.Printf("%s: %v\n", key, value)
}
// 写入秘密 - 自动使用配置的 kv_mount_path
data := map[string]interface{}{
"database_url": "postgres://localhost/db",
"api_key": "secret-key",
}
_, err = vault.WriteSecret(ctx, "myapp/config", data)
3.2 使用原生客户端(灵活)
原生客户端每次调用都需要通过函数参数指定挂载点:
import (
"github.com/hashicorp/vault-client-go"
"github.com/infraboard/mcube/v2/ioc/config/vault"
)
client := vault.Client()
ctx := context.Background()
// 读取秘密 - 通过 WithMountPath 参数指定挂载点
resp, err := client.Secrets.KvV2Read(
ctx,
"myapp/config", // 秘密路径
vault.WithMountPath("secret"), // 参数指定挂载点
)
if err != nil {
panic(err)
}
// 访问秘密数据
for key, value := range resp.Data.Data {
fmt.Printf("%s: %v\n", key, value)
}
两种方式对比:
| 特性 | 辅助方法 | 原生客户端 |
|---|
| 挂载点指定 | 配置文件 kv_mount_path = "secret" | 函数参数 WithMountPath("secret") |
| 代码简洁性 | ✅ 简洁 | 详细 |
| 灵活性 | 适合标准场景 | ✅ 高度灵活 |
| 适用场景 | 单一挂载点 | 多挂载点、高级用法 |
配置说明
基础配置
| 配置项 | 类型 | 默认值 | 说明 |
|---|
address | string | http://127.0.0.1:8200 | Vault 服务器地址 |
auth_method | string | token | 认证方式: token, approle, kubernetes |
auto_renew | bool | true | 是否自动续期 token |
namespace | string | - | Vault Namespace(企业版) |
挂载点配置
挂载点配置用于辅助方法自动定位秘密引擎位置:
| 配置项 | 类型 | 默认值 | 说明 | 对应辅助方法 |
|---|
kv_mount_path | string | secret | KV v2 引擎挂载点 | ReadSecret(), WriteSecret() |
transit_mount_path | string | transit | Transit 加密引擎挂载点 | Encrypt(), Decrypt() |
database_mount_path | string | database | Database 引擎挂载点 | GenerateDatabaseCredentials() |
pki_mount_path | string | pki | PKI 证书引擎挂载点 | IssueCertificate() |
说明:
- 辅助方法会自动读取这些配置,无需在代码中手动指定
- 原生客户端调用时仍需通过
WithMountPath() 参数指定
- 如未配置,使用默认值(与 Vault CLI 开发模式一致)
Token 认证
最简单的认证方式,适合开发和测试环境:
[vault]
auth_method = "token"
token = "hvs.xxxxxxxxxxxxx"
AppRole 认证
推荐用于生产环境的应用程序:
[vault]
auth_method = "approle"
role_id = "your-role-id"
secret_id = "your-secret-id"
设置 AppRole:
# 启用 AppRole 认证
vault auth enable approle
# 创建角色和策略
vault write auth/approle/role/myapp \
secret_id_ttl=10m \
token_ttl=20m \
token_max_ttl=30m \
policies="myapp-policy"
# 获取凭证
vault read auth/approle/role/myapp/role-id
vault write -f auth/approle/role/myapp/secret-id
Kubernetes 认证
适合在 Kubernetes 环境中运行的应用:
[vault]
auth_method = "kubernetes"
k8s_role = "myapp"
k8s_token_path = "/var/run/secrets/kubernetes.io/serviceaccount/token"
配置 Kubernetes 认证:
# 启用 Kubernetes 认证
vault auth enable kubernetes
# 配置 Kubernetes 认证后端
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc:443" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token
# 创建角色绑定到 ServiceAccount
vault write auth/kubernetes/role/myapp \
bound_service_account_names=myapp \
bound_service_account_namespaces=default \
policies=myapp-policy \
ttl=24h
TLS 配置
生产环境强烈建议启用 TLS:
[vault]
address = "https://vault.example.com:8200"
tls_enable = true
tls_ca = "/path/to/ca.crt"
# 可选:客户端证书认证
tls_cert = "/path/to/client.crt"
tls_key = "/path/to/client.key"
# 仅在开发环境使用
# tls_skip_verify = true
前置准备
为了方便功能验证,本示例将使用 Docker 启动一个 Vault 服务器。
1. 启动 Vault 服务
使用 Docker 启动一个开发模式的 Vault 服务器:
docker run -itd --name=vault \
--cap-add=IPC_LOCK \
-p 8200:8200 \
-e 'VAULT_DEV_ROOT_TOKEN_ID=myroot' \
-e 'VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200' \
hashicorp/vault:latest
2. 创建测试秘密
在容器内执行命令(本地无需安装 Vault CLI):
# 写入测试秘密
docker exec \
-e VAULT_ADDR='http://127.0.0.1:8200' \
-e VAULT_TOKEN='myroot' \
vault vault kv put secret/myapp/config \
database_url="postgres://localhost:5432/mydb" \
api_key="my-secret-api-key" \
debug_mode="true"
# 验证秘密
docker exec \
-e VAULT_ADDR='http://127.0.0.1:8200' \
-e VAULT_TOKEN='myroot' \
vault vault kv get secret/myapp/config
# 输出
====== Secret Path ======
secret/data/myapp/config
======= Metadata =======
Key Value
--- -----
created_time 2026-02-20T01:46:00.710838087Z
custom_metadata <nil>
deletion_time n/a
destroyed false
version 1
======== Data ========
Key Value
--- -----
api_key my-secret-api-key
database_url postgres://localhost:5432/mydb
debug_mode true
辅助方法 API(推荐)
辅助方法封装了常用操作,自动使用配置文件中的挂载点,代码更简洁。
KV v2 秘密管理
自动使用配置: kv_mount_path = "secret"
import "github.com/infraboard/mcube/v2/ioc/config/vault"
ctx := context.Background()
// 读取秘密 - 自动使用 kv_mount_path
resp, err := vault.ReadSecret(ctx, "myapp/config")
if err != nil {
return err
}
password := resp.Data.Data["password"].(string)
// 写入秘密 - 自动使用 kv_mount_path
data := map[string]interface{}{
"username": "admin",
"password": "secret123",
}
_, err = vault.WriteSecret(ctx, "myapp/config", data)
// 列出秘密 - 自动使用 kv_mount_path
list, err := vault.ListSecrets(ctx, "myapp/")
for _, key := range list.Data.Keys {
fmt.Println(key) // config, database, ...
}
// 删除秘密 - 自动使用 kv_mount_path
err = vault.DeleteSecret(ctx, "myapp/old-config")
Transit 加密
自动使用配置: transit_mount_path = "transit"
import "encoding/base64"
// 加密 - 自动使用 transit_mount_path
plaintext := base64.StdEncoding.EncodeToString([]byte("sensitive data"))
encResp, err := vault.Encrypt(ctx, "my-key", plaintext)
ciphertext := encResp.Data.Ciphertext // vault:v1:abc123...
// 解密 - 自动使用 transit_mount_path
decResp, err := vault.Decrypt(ctx, "my-key", ciphertext)
original, _ := base64.StdEncoding.DecodeString(decResp.Data.Plaintext)
fmt.Println(string(original)) // sensitive data
动态数据库凭证
自动使用配置: database_mount_path = "database"
// 获取临时数据库凭证 - 自动使用 database_mount_path
resp, err := vault.GenerateDatabaseCredentials(ctx, "readonly-role")
if err != nil {
return err
}
username := resp.Data["username"].(string)
password := resp.Data["password"].(string)
// 使用凭证连接数据库
dsn := fmt.Sprintf("%s:%s@tcp(localhost:3306)/mydb", username, password)
db, _ := sql.Open("mysql", dsn)
临时使用不同挂载点
如需临时使用不同挂载点,可以传递 WithMountPath 参数:
import "github.com/hashicorp/vault-client-go"
// 临时覆盖配置的挂载点
resp, err := vault.ReadSecret(
ctx,
"prod/config",
vault.WithMountPath("prod-kv"), // 覆盖 kv_mount_path 配置
)
原生客户端 API(高级用法)
原生客户端提供了完全的灵活性,每次调用都需要通过 WithMountPath() 参数指定挂载点。
读取秘密
// 通过参数指定挂载点
resp, err := client.Secrets.KvV2Read(
ctx,
"path/to/secret", // 秘密路径
vault.WithMountPath("secret"), // 参数指定挂载点
)
if err != nil {
return err
}
// 访问秘密数据
password := resp.Data.Data["password"].(string)
写入秘密
import "github.com/hashicorp/vault-client-go/schema"
// 通过参数指定挂载点
_, err := client.Secrets.KvV2Write(
ctx,
"path/to/secret", // 秘密路径
schema.KvV2WriteRequest{
Data: map[string]interface{}{
"username": "admin",
"password": "secret123",
},
},
vault.WithMountPath("secret"), // 参数指定挂载点
)
列出秘密路径
// 通过参数指定挂载点
resp, err := client.Secrets.KvV2List(
ctx,
"path/to/", // 秘密路径前缀
vault.WithMountPath("secret"), // 参数指定挂载点
)
if err != nil {
return err
}
for _, key := range resp.Data.Keys {
fmt.Println(key)
}
删除秘密
// 删除最新版本 - 通过参数指定挂载点
_, err := client.Secrets.KvV2Delete(
ctx,
"path/to/secret",
vault.WithMountPath("secret"), // 参数指定挂载点
)
// 删除特定版本
_, err := client.Secrets.KvV2DeleteVersions(
ctx,
"path/to/secret",
schema.KvV2DeleteVersionsRequest{
Versions: []int64{1, 2},
},
vault.WithMountPath("secret"), // 参数指定挂载点
)
使用 Transit 加密
import "github.com/hashicorp/vault-client-go/schema"
// 加密数据 - 通过参数指定挂载点
encResp, err := client.Secrets.TransitEncrypt(
ctx,
"my-key",
schema.TransitEncryptRequest{
Plaintext: base64.StdEncoding.EncodeToString([]byte("secret data")),
},
vault.WithMountPath("transit"), // 参数指定挂载点
)
ciphertext := encResp.Data.Ciphertext
// 解密数据 - 通过参数指定挂载点
decResp, err := client.Secrets.TransitDecrypt(
ctx,
"my-key",
schema.TransitDecryptRequest{
Ciphertext: ciphertext,
},
vault.WithMountPath("transit"), // 参数指定挂载点
)
plaintext, _ := base64.StdEncoding.DecodeString(decResp.Data.Plaintext)
动态数据库凭证
// 获取数据库凭证 - 通过参数指定挂载点
resp, err := client.Secrets.DatabaseGenerateCredentials(
ctx,
"my-role",
vault.WithMountPath("database"), // 参数指定挂载点
)
if err != nil {
return err
}
username := resp.Data.Data["username"].(string)
password := resp.Data.Data["password"].(string)
// 使用凭证连接数据库...
// 凭证会在 lease_duration 后自动过期
环境变量
支持通过环境变量覆盖配置:
VAULT_ADDR: Vault 服务器地址
VAULT_TOKEN: Token 认证的 token
VAULT_NAMESPACE: Vault namespace(企业版)
VAULT_CACERT: CA 证书路径
VAULT_CLIENT_CERT: 客户端证书路径
VAULT_CLIENT_KEY: 客户端私钥路径
VAULT_SKIP_VERIFY: 跳过 TLS 验证(值为 "true")
最佳实践
- 生产环境认证: 使用 AppRole 或 Kubernetes 认证,避免使用 token 认证
- 最小权限原则: 为每个应用创建独立的策略,只授予必要的权限
- 启用 TLS: 生产环境必须启用 TLS 加密通信
- 自动续期: 开启
auto_renew 避免 token 过期
- 动态秘密: 优先使用动态秘密(如数据库凭证),而不是静态秘密
- 秘密轮换: 定期轮换秘密,使用 Vault 的版本控制功能
- 审计日志: 启用 Vault 审计日志,追踪秘密访问
- 密钥管理: 使用 Transit 引擎进行加密操作,而不是在应用中硬编码密钥
故障处理
Token 过期
启用自动续期:
[vault]
auto_renew = true
模块会自动在 token 生命周期的 80% 时尝试续期。
连接失败
检查网络连接和 Vault 服务状态:
验证 TLS 配置是否正确。
权限不足
检查策略配置:
# 查看当前 token 的策略
vault token lookup
# 查看策略内容
vault policy read myapp-policy
确保策略包含必要的权限:
# 读取 KV 秘密
path "secret/data/myapp/*" {
capabilities = ["read", "list"]
}
# 写入 KV 秘密
path "secret/data/myapp/*" {
capabilities = ["create", "update"]
}
相关资源
License
MIT