动态凭据能缩短泄露后的可用时间,却不会自动解决连接池、续租失败和任务重试。把密码从配置文件挪到Vault,只完成了凭据生命周期的一部分。真正需要设计的是:凭据什么时候取得,什么时候停止接新任务,什么时候让旧连接退出。
先区分 Token 与秘密租约
Vault 文档说明,动态秘密通常伴随租约,租约带有有效时间和续租信息;到期或撤销后,Vault会使关联秘密失效。普通KV值并不因此自动获得动态数据库凭据的撤销能力。登录Token的续期,也不等于它曾经获取的每份秘密都被无限续期。租约机制
图中的续期是请求租约继续有效,不是绕过最大TTL。实现时应检查响应里的实际租期和可续租状态,不能只按自己请求的increment安排下一次工作。秘密引擎与角色配置可能限制续期,后端撤销失败也需要告警和补偿。
连接池需要两道门
第一道门控制新任务。剩余寿命不足以覆盖预估执行时间和安全余量时,换取新凭据或暂缓任务。第二道门控制旧连接。新池健康后再切换流量,让旧池停止接入并在期限内关闭;不要先撤销旧凭据再祈祷所有事务都已结束。
以下离线例子只验证接单条件。时间以整数秒表示,固定教学数据避免等待;它没有访问Vault,也没有证明数据库服务端会中断已有会话。
def may_start(now, expires_at, expected_seconds, margin): if expected_seconds < 0 or margin < 0: raise ValueError("duration must be nonnegative") return expires_at - now > expected_seconds + margin assert may_start(100, 200, 40, 10) assert not may_start(150, 200, 40, 10) assert not may_start(201, 200, 1, 1) try: may_start(0, 100, -1, 5) except ValueError: pass else: raise AssertionError("negative duration accepted") print("lease admission boundaries passed")一个请求失败后,先判断发生在哪一步
| 现象 | 首先检查 | 处理方向 |
|---|---|---|
| 无法取密 | 身份登录、角色路径、策略权限 | 让任务失败可见,避免回退硬编码密码 |
| 续租拒绝 | renewable、最大TTL、服务端响应 | 获取新租约并换池 |
| 撤销异常 | 秘密后端连通性与失败队列 | 重试与人工处理,不伪报已失效 |
| 执行中认证失败 | 连接寿命、事务与重试边界 | 只重试可安全重复的动作 |
Go客户端的Sys.Revoke方法返回一个error,不能用两个变量接收;撤销后也应验证业务侧效果。Go API 生产代码还要传递超时、隐藏敏感响应、限制日志字段。本文没有运行Go SDK、真实Vault或数据库。
建议先在隔离环境演练三件事:任务跨过租约到期、续租持续失败、撤销请求失败。记录新请求是否被拦截、旧事务怎样结束,以及后台是否能发现未完成撤销。动态凭据是一种把风险时间窗缩小的机制,不能用“零泄露”替代这些证据。