| name | implementing-hashicorp-vault-dynamic-secrets |
| description | 为数据库凭据、AWS IAM 密钥和 PKI 证书实施 HashiCorp Vault 动态密钥引擎, 支持自动生成、租约管理和凭据轮换, 以消除应用程序配置中的静态密钥。 适用于 Vault 密钥引擎配置、动态数据库凭据、 短暂云凭据或自动密钥轮换相关请求。
|
| domain | cybersecurity |
| subdomain | identity-access-management |
| tags | ["HashiCorp-Vault","dynamic-secrets","secrets-management","database-credentials","AWS-secrets","PKI"] |
| version | 1.0 |
| author | mahipal |
| license | Apache-2.0 |
实施 HashiCorp Vault 动态密钥
适用场景
- 应用程序使用存储在配置文件或环境变量中的静态数据库凭据
- AWS IAM 访问密钥是长期有效的并在服务间共享
- 需要通过生成短期按需密钥来消除凭据蔓延
- 合规要求强制凭据轮换(PCI-DSS 要求 8、NIST 800-53 IA-5)
- 实施零信任密钥管理,凭据不会静态存储
- 从手动凭据管理迁移到自动化密钥生命周期
不适用于存储无法动态生成的静态密钥(改用 Vault 的 KV 密钥引擎);动态密钥适用于可以在目标系统上以编程方式创建和撤销的凭据。
前置条件
- HashiCorp Vault 1.15+(社区版或企业版)
- Vault 服务器已初始化并解封,并配置了自动解封(AWS KMS、Azure Key Vault 或 Transit)
- 目标数据库系统具有管理员凭据,供 Vault 创建/撤销动态账户
- AWS IAM 账户具有创建/删除 IAM 用户和访问密钥的权限
- 从 Vault 到所有目标系统的网络连接
- 已为使用应用程序配置了 Vault 策略和认证方法
工作流程
步骤 1:部署和配置 Vault 服务器
使用生产级配置初始化 Vault:
# vault-config.hcl - 生产 Vault 服务器配置
storage "raft" {
path = "/opt/vault/data"
node_id = "vault-1"
retry_join {
leader_api_addr = "https://vault-2.corp.local:8200"
}
retry_join {
leader_api_addr = "https://vault-3.corp.local:8200"
}
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/opt/vault/tls/vault-cert.pem"
tls_key_file = "/opt/vault/tls/vault-key.pem"
}
seal "awskms" {
region = "us-east-1"
kms_key_id = "alias/vault-unseal-key"
}
api_addr = "https://vault-1.corp.local:8200"
cluster_addr = "https://vault-1.corp.local:8201"
telemetry {
prometheus_retention_time = "24h"
disable_hostname = true
}
ui = true
vault operator init -key-shares=5 -key-threshold=3
vault audit enable file file_path=/var/log/vault/audit.log
vault auth enable approle
vault policy write db-consumer - <<EOF
# 允许读取动态数据库凭据
path "database/creds/app-readonly" {
capabilities = ["read"]
}
path "database/creds/app-readwrite" {
capabilities = ["read"]
}
# 允许续约和撤销自身的租约
path "sys/leases/renew" {
capabilities = ["update"]
}
path "sys/leases/revoke" {
capabilities = ["update"]
}
# 允许读取自身的令牌信息
path "auth/token/lookup-self" {
capabilities = ["read"]
}
EOF
vault write auth/approle/role/webapp \
token_policies="db-consumer" \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=720h \
secret_id_num_uses=0
步骤 2:配置数据库密钥引擎
为 PostgreSQL 和 MySQL 设置动态凭据生成:
vault secrets enable database
vault write database/config/production-postgres \
plugin_name=postgresql-database-plugin \
allowed_roles="app-readonly,app-readwrite,app-admin" \
connection_url="postgresql://{{username}}:{{password}}@db-primary.corp.local:5432/appdb?sslmode=verify-full" \
username="vault_admin" \
password="$VAULT_DB_PASSWORD" \
password_authentication="scram-sha-256"
vault write -force database/rotate-root/production-postgres
vault write database/roles/app-readonly \
db_name=production-postgres \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\"; \
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO \"{{name}}\";" \
revocation_statements="REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM \"{{name}}\"; \
DROP ROLE IF EXISTS \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
vault write database/roles/app-readwrite \
db_name=production-postgres \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\"; \
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO \"{{name}}\";" \
revocation_statements="REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM \"{{name}}\"; \
DROP ROLE IF EXISTS \"{{name}}\";" \
default_ttl="30m" \
max_ttl="8h"
vault write database/config/production-mysql \
plugin_name=mysql-database-plugin \
allowed_roles="mysql-readonly,mysql-readwrite" \
connection_url="{{username}}:{{password}}@tcp(mysql-primary.corp.local:3306)/" \
username="vault_admin" \
password=""
vault write database/roles/mysql-readonly \
db_name=production-mysql \
creation_statements= \
revocation_statements= \
default_ttl= \
max_ttl=
vault database/creds/app-readonly
步骤 3:配置 AWS 密钥引擎
生成短暂的 AWS IAM 凭据:
vault secrets enable aws
vault write aws/config/root \
access_key="$AWS_ACCESS_KEY_ID" \
secret_key="$AWS_SECRET_ACCESS_KEY" \
region="us-east-1"
vault write aws/config/lease \
lease="30m" \
lease_max="1h"
vault write aws/roles/s3-readonly \
credential_type=iam_user \
policy_document=-<<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::app-data-bucket",
"arn:aws:s3:::app-data-bucket/*"
]
}
]
}
EOF
vault write aws/roles/ec2-admin \
credential_type=assumed_role \
role_arns="arn:aws:iam::123456789012:role/VaultEC2AdminRole" \
default_sts_ttl="30m" \
max_sts_ttl="1h"
vault write aws/roles/cross-account-readonly \
credential_type=federation_token \
policy_document=-<<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Resource": "arn:aws:iam::987654321098:role/CrossAccountReadOnly"
}
]
}
EOF
echo "测试 AWS STS 凭据:"
vault read aws/creds/ec2-admin
步骤 4:配置 PKI 密钥引擎以动态颁发证书
按需生成短期 TLS 证书:
vault secrets enable -path=pki pki
vault secrets tune -max-lease-ttl=87600h pki
vault write pki/root/generate/internal \
common_name="Corp Internal Root CA" \
ttl=87600h \
key_type=ec \
key_bits=384
vault secrets enable -path=pki_int pki
vault secrets tune -max-lease-ttl=43800h pki_int
vault write pki_int/intermediate/generate/internal \
common_name="Corp Intermediate CA" \
key_type=ec \
key_bits=256
vault write pki/root/sign-intermediate \
csr=@intermediate.csr \
format=pem_bundle \
ttl=43800h
vault write pki_int/config/urls \
issuing_certificates="https://vault.corp.local:8200/v1/pki_int/ca" \
crl_distribution_points="https://vault.corp.local:8200/v1/pki_int/crl"
vault write pki_int/roles/web-server \
allowed_domains="corp.local,internal.corp.com" \
allow_subdomains=true \
max_ttl=720h \
key_type=ec \
key_bits=256 \
require_cn=true \
enforce_hostnames=true
vault write pki_int/roles/service-mesh \
allowed_domains="service.consul" \
allow_subdomains=true \
max_ttl=24h \
key_type=ec \
key_bits=256 \
allow_ip_sans=true \
server_flag=true \
client_flag=true
vault write pki_int/issue/web-server \
common_name="api.corp.local" \
alt_names="api.internal.corp.com" \
ttl=720h
步骤 5:将应用程序与 Vault 集成
配置应用程序使用动态密钥:
"""
与 HashiCorp Vault 集成用于动态数据库凭据的应用程序示例。
使用带有自动租约续约的 hvac Python 客户端。
"""
import hvac
import threading
import time
import logging
class VaultDynamicCredentialManager:
def __init__(self, vault_addr, role_id, secret_id):
self.client = hvac.Client(url=vault_addr)
self.role_id = role_id
self.secret_id = secret_id
self.logger = logging.getLogger("vault_credentials")
self._current_creds = None
self._lease_id = None
self._renewal_thread = None
self._stop_event = threading.Event()
def authenticate(self):
"""使用 AppRole 向 Vault 进行认证。"""
response = self.client.auth.approle.login(
role_id=self.role_id,
secret_id=self.secret_id
)
self.client.token = response["auth"]["client_token"]
self.logger.info("已通过 AppRole 向 Vault 认证")
def get_database_credentials(self, role="app-readonly"):
"""从 Vault 请求动态数据库凭据。"""
self.authenticate()
response = self.client.secrets.database.generate_credentials(
name=role
)
._current_creds = {
: response[][],
: response[][],
}
._lease_id = response[]
lease_duration = response[]
.logger.info(
)
._start_renewal(lease_duration)
._current_creds
():
._renewal_thread ._renewal_thread.is_alive():
._stop_event.()
._renewal_thread.join()
._stop_event.clear()
renewal_interval = lease_duration *
():
._stop_event.wait(renewal_interval):
:
.client.sys.renew_lease(
lease_id=._lease_id,
increment=lease_duration
)
.logger.info()
hvac.exceptions.InvalidRequest:
.logger.warning()
.get_database_credentials()
Exception e:
.logger.error()
._renewal_thread = threading.Thread(target=renew_loop, daemon=)
._renewal_thread.start()
():
._lease_id:
._stop_event.()
.client.sys.revoke_lease(._lease_id)
.logger.info()
._current_creds =
._lease_id =
():
.authenticate()
response = .client.secrets.aws.generate_credentials(
name=role
)
{
: response[][],
: response[][],
: response[].get(),
: response[],
: response[]
}
vault_mgr = VaultDynamicCredentialManager(
vault_addr=,
role_id=,
secret_id=
)
db_creds = vault_mgr.get_database_credentials()
步骤 6:监控 Vault 操作和租约管理
跟踪动态密钥使用情况和租约生命周期:
vault list sys/leases/lookup/database/creds/app-readonly
vault list sys/leases/lookup/aws/creds/s3-readonly
vault write sys/leases/lookup lease_id="database/creds/app-readonly/abcd1234"
vault lease revoke -prefix database/creds/app-readonly
cat > vault_audit_monitor.sh << 'SCRIPT'
AUDIT_LOG="/var/log/vault/audit.log"
echo "=== 动态凭据请求(最近 1 小时)==="
jq -r 'select(.type == "response" and .request.path | startswith("database/creds/")) |
"\(.time) \(.request.path) \(.auth.display_name)"' \
"$AUDIT_LOG" | tail -100
echo ""
echo "=== 高频凭据使用者 ==="
jq -r 'select(.type == "request" and .request.path | startswith("database/creds/")) |
.auth.display_name' \
"$AUDIT_LOG" | sort | uniq -c | sort -rn | head -10
echo ""
echo "=== 认证失败尝试 ==="
jq -r 'select(.type == "response" and .error != null and
.request.path | startswith("auth/")) |
"\(.time) \(.request.path) \(.error)"' \
| -20
SCRIPT
+x vault_audit_monitor.sh
核心概念
| 术语 | 定义 |
|---|
| 动态密钥(Dynamic Secrets) | Vault 按需生成的带有自动过期功能的凭据,确保每个消费者获得唯一的短期凭据 |
| 租约(Lease) | Vault 保证凭据有效的时限协议;消费者必须在到期前续约或请求新凭据 |
| 密钥引擎(Secrets Engine) | 生成、存储或加密数据的 Vault 插件;数据库、AWS、PKI 和 KV 是常见的引擎 |
| AppRole | 为机器间认证设计的 Vault 认证方法,使用角色 ID 和密钥 ID 对 |
| 根凭据轮换(Root Credential Rotation) | 让 Vault 独家接管用于创建动态密钥的管理员凭据的过程,消除人对根密码的知晓 |
| 租约撤销(Lease Revocation) | 立即使动态凭据失效,在事件响应期间用于撤销被入侵路径的所有凭据 |
工具与系统
- HashiCorp Vault:提供动态密钥、加密即服务和基于身份的访问控制的密钥管理平台
- Vault Agent:处理 Vault 认证、令牌续约和应用程序密钥缓存的 Sidecar 进程
- Vault Secrets Operator:将 Vault 密钥同步到 Kubernetes Secrets 以供 Pod 使用的 Kubernetes Operator
- hvac:用于 HashiCorp Vault API 操作的 Python 客户端库
常见场景
场景:消除微服务中的静态数据库凭据
背景:50 个微服务共享 3 个静态 PostgreSQL 凭据,这些凭据存储在 Kubernetes 部署的环境变量中。凭据泄露需要同时轮换所有 50 个服务。
处理方式:
- 在 Kubernetes 中使用 Raft 存储部署 3 节点 HA 集群的 Vault
- 使用管理员凭据配置数据库密钥引擎的 PostgreSQL 连接
- 使用最小权限 SQL 授权为每个服务创建 Vault 角色
- 部署 Vault Secrets Operator 将动态凭据注入 Pod 环境变量
- 更新应用程序连接逻辑以通过租约续约处理凭据轮换
- 轮换 Vault 根凭据以删除人对管理员密码的知晓
- 监控租约生命周期并为续约失败设置告警
注意事项:
- 不在应用程序连接池中处理凭据轮换(使用过期凭据的连接会失败)
- 将 TTL 设置得太短会对数据库造成过多的凭据生成负载
- 不配置适当的撤销语句会导致租约过期后遗留孤儿数据库用户
- 在没有 HA 的情况下运行 Vault 会为所有应用程序认证创建单点故障
输出格式
HASHICORP VAULT 动态密钥报告
=========================================
Vault 版本: 1.16.2 Enterprise
集群状态: HA 活跃(3 个节点)
封印类型: AWS KMS(自动解封)
密钥引擎
database/: PostgreSQL, MySQL(2 个连接)
aws/: IAM 用户、承担角色、联合令牌
pki_int/: 内部 CA(EC P-256)
动态凭据指标(最近 24 小时)
生成的总凭据: 4,287
数据库(PostgreSQL):2,891
数据库(MySQL): 543
AWS STS: 612
PKI 证书: 241
活跃租约
总活跃: 387
database/creds/app-readonly:198
database/creds/app-readwrite:89
aws/creds/s3-readonly: 67
pki_int/issue/web-server: 33
租约生命周期
平均 TTL: 45 分钟
续约(24 小时): 12,847
撤销(24 小时): 3,901
过期(未续约): 12
安全
认证失败尝试(24 小时):3
根凭据已轮换: 是(所有数据库)
审计日志记录: 已启用(文件 + syslog)
策略违规(24 小时): 7(权限拒绝)