题目
设计一个多租户 Secrets Rotation Service。租户可以配置数据库密码、第三方 API 密钥或证书的轮换周期;服务负责生成新值、更新外部系统、写入版本库、逐步通知工作负载并停用旧值。请说明故障恢复、并发控制、审计、权限和回滚方案。
面试官考察点
- 能否把轮换拆成可重试的状态机,而不是一个无法恢复的定时脚本。
- 能否处理同一 secret 的并发任务、重复消息和租户之间的隔离。
- 能否设计双版本或灰度发布,避免新值立即导致全量故障。
- 能否说明旧值清理、泄露响应、审计证据与最小权限。
参考答案
核心对象包括 Secret、不可变 Version、RotationPolicy、Job 和 Lease。调度器按下一次轮换时间投递任务;工作队列按 secretid 分区,消费者用带过期时间的租约取得所有权。数据库唯一约束 (secretid, idempotency_key),让重试不会生成两次外部变更。
轮换状态可采用 scheduled → generating → externalupdated → staged → rollingout → verified → retired,每个状态保存外部请求标识、版本号和下一次重试时间。先在外部系统创建新凭据,再写入新版本并标记为 pending;工作负载通过版本引用或动态刷新逐步切换。健康检查、错误率和授权测试通过后,才把新版本提升为 current。
控制面保存元数据,秘密值放在专用加密存储中,应用只获得短时读取权限。每次状态变化写入不可篡改审计日志;日志中不出现秘密明文。回滚只切回仍有效的旧版本,不能把“撤销旧凭据”作为回滚后的默认动作。
架构草图
Scheduler -> Durable Queue -> Rotation Workers
| | |
Policy DB Lease/Idempotency External Provider
| |
Version Store + KMS Rollout Controller -> Workloads
|
Audit Log / Metrics / Alerts每个 worker 以租约续期,租约失效后其他 worker 才能接管。队列消息只携带 secret 标识和 job 标识;worker 从权限受限的版本存储读取值,避免把秘密放进消息、日志或指标标签。
关键流程
- 调度器创建带幂等键的 job,按租户配额排队。
- worker 取得租约,读取当前版本和轮换策略;若 job 已完成则直接返回。
- 生成新值并调用外部 provider,使用 provider 的幂等请求号。
- 写入 pending 版本,执行兼容性检查和小批量 rollout。
- 观察错误率、认证成功率和健康探针;达到阈值后提升 current。
- 等待所有消费者确认,再禁用并销毁旧版本;失败则重试或回滚。
每一步都持久化状态和外部响应。重启时从最后状态继续,而不是重新猜测外部系统是否已经更新。
常见误区
- 只在 cron 中保存下一次时间,进程重启或重复触发后无法恢复。
- 轮换完成就立即撤销旧值,忽略连接池、缓存和长连接的过渡时间。
- 把秘密值放入队列、日志、追踪 span 或错误消息。
- 用全局锁阻塞所有租户,或者没有租户级配额导致单租户耗尽 worker。
- 把回滚理解为再次写入旧值,却不验证旧凭据仍未过期或未被撤销。
一致性与安全取舍
控制面可以使用强一致数据库保存状态机和唯一约束;通知与 rollout 使用至少一次投递,因此消费者必须幂等。版本读取可短暂缓存,但 current 版本变更和撤销事件必须有明确失效路径。租户权限应限制在自己的 secret、job 和审计记录,worker 只获得完成当前步骤所需的 provider 权限。
轮换周期不能只按固定时间决定,还要考虑密钥类型、暴露风险、外部 provider 限制和恢复窗口。NIST 的密钥管理建议强调使用期限与用途、保护等级和撤销流程应共同决定策略。
用故障注入验证:worker 在每个状态前后崩溃、消息重复、租约过期、provider 超时、部分实例 rollout 失败、数据库主从切换。断言同一 job 不会产生重复外部凭据,最终只有一个 current 版本,旧值在确认窗口结束后才撤销。再检查租户越权、审计脱敏和告警延迟。
- AWS Secrets Manager 的
AWSPENDING/AWSCURRENT轮换流程:版本标签和完成步骤。 - Google Cloud Secret Manager 轮换建议:重试、不可并发轮换、渐进 rollout 与旧版本清理。
- NIST SP 800-57 Part 1:密钥用途、保护、使用期限和撤销的管理原则。
追问
如何防止两个 worker 同时轮换同一个 secret?
用数据库租约或带 fencing token 的分布式锁,并把 token 写入每次状态更新。没有当前 token 的 worker 即使恢复,也不能覆盖新状态;租约需要续期和明确的过期时间。
provider 不支持幂等接口怎么办?
先在本地 job 中持久化请求指纹和外部资源标识,重试前查询 provider 的当前状态。若无法查询,就把该步骤标为需要人工确认,避免盲目创建多个凭据。
为什么不能只让应用读取 latest?
latest 可能把未经验证的新值立即推给全部实例。使用不可变版本、分批 rollout 和健康检查,可以把错误值的影响限制在小范围并保留回滚点。
轮换任务积压时如何保护系统?
按租户和 provider 设置并发配额,使用优先级和退避重试,并暴露最老任务年龄、失败率和剩余恢复窗口。接近过期的 secret 可以提升优先级,但不能绕过权限和幂等检查。
泄露事件发生时,轮换服务要做什么?
立即冻结相关 job 的普通调度,创建高优先级紧急轮换,缩短 rollout 观察窗口并保留取证日志。撤销旧值前确认所有关键消费者已切换,同时通知租户和安全响应流程。