密钥生命周期管理:创建、存储、轮换、吊销、过期处理

密钥生命周期管理,说白了就是管好密钥从生到死的每一步。很多团队只关心「怎么创建」和「放哪儿」,却忽略了轮换、吊销和过期处理。我在项目里见过一个悲剧:某团队把数据库密码直接写死在代码里,发现泄露后只是改了新密码,但旧密码没吊销,结果攻击者用旧凭证继续访问了两个月。

下面我按五个阶段拆开讲,每个阶段都有血泪教训。

1. 创建:从源头保证强度

密钥创建是第一步,也是最容易被糊弄的一步。我个人习惯用至少 256 位随机数生成,绝不手动敲键盘。

  • 生成算法:优先用 openssl rand -base64 32 或 uuidgen 配合哈希。别用 date +%s | md5sum,那玩意儿可预测。
  • 责任分离:创建密钥的人不能同时拥有该密钥的使用权限。我曾在某个金融项目里强制要求「创建者只读初始值,运维人员只负责存储,应用只负责调用」。
  • 初始版本记录:创建时立即打上版本标签,方便后续轮换和回滚。
⚠️ 我踩过的坑:有次看见同事用「admin123」当 ,理由是「反正之后会改」。结果忘了改,上线第二天就被爬虫扫到了。记住:密钥创建时就该是生产级强度,没有「临时密钥」这回事。

2. 存储:别放仓库,别写环境变量死值

存储环节是 CI/CD 管道出错的重灾区。很多团队把密钥塞进 .env 文件提交到 Git,或者用 GitLab CI/CD 的 variables 明文存储。说白了,这些都是裸奔。

我个人推荐这几种方式,按安全性排序:

存储方式 安全性 审计能力 动态轮换 适用场景
环境变量(明文) 低 无 需手动 开发/测试环境,不推荐生产
GitLab CI/CD 变量(Masked) 中 有操作日志 需手动 小团队,管道简单
HashiCorp Vault 高 完整审计 自动 生产环境,多服务
AWS Secrets Manager 高 CloudTrail 自动轮换 AWS 原生用户
Kubernetes Secrets 中 需配合 RBAC 需工具 K8s 集群内部

我在项目中用过一段时间的 Vault,它的动态密钥(Dynamic Secret)机制很香:每次请求生成一个临时凭证,用完即废,连轮换都省了。

# 创建静态密钥并存储到 Vault
vault kv put secret/myapp/db \
  username="db_user" \
  password="$(openssl rand -base64 32)" \
  ttl="7d"
💡 小技巧:在 CI/CD 管道中,用 Vault Agent 或 Sidecar 模式自动注入密钥,别让管道 job 直接持有永久凭证。我通常让 Vault 根据 pipeline ID 生成短期 ,用完自动吊销。

3. 轮换:别等到泄露才换

轮换不是应急措施,而是常规操作。我个人习惯设定强制轮换周期,比如:

  • :30 天轮换一次
  • 数据库密码:90 天轮换一次
  • TLS 证书:按证书有效期,提前 30 天触发轮换

你可能会问:「轮换的时候服务会不会断?」嗯,这正是关键。要保证轮换是零停机操作,我常用的套路是「蓝绿版本」:

  1. 创建新密钥版本(版本 v2)
  2. 将新密钥写入 Vault,标记为 active
  3. 应用逐步切换到新密钥,同时保留旧密钥一段时间
  4. 确认所有实例使用新密钥后,吊销旧密钥

我曾经的救火经历:有次轮换脚本没加失败回退,导致所有服务同时拿到旧密钥失效版本,数据库连接全部断开。还好当时手动回滚了 Vault 的版本。现在我的轮换脚本里永远保留「回滚第一步:恢复上一版本」的逻辑。

在 GitLab CI 里实现自动轮换,可以这样写:

rotate-secret:
  stage: deploy
  script:
    - # 检查密钥剩余有效期
    - expiry=$(vault read -field=expiry secret/myapp/)
    - if [ "$expiry" -lt "$(( $(date +%s) + 7*86400 ))" ]; then
    -   # 生成新密钥并更新 Vault
    -   new_key=$(openssl rand -base64 32)
    -   vault kv put secret/myapp/ value="$new_key"
    -   # 通知下游服务
    -   curl -X POST https://hooks.example.com/secret-rotated
    - fi
  only:
    - schedules  # 定时触发

4. 吊销:检测到泄露立即行动

吊销不是轮换的附属操作,而是一条独立的安全红线。只要发现密钥有泄露嫌疑,立刻废除,别等什么轮换周期。

吊销步骤我总结为「三步走」:

  • 立即禁用:在密钥管理系统中打上 revoked 标签,阻止所有新请求使用该密钥。
  • 清理所有引用:用脚本扫描 CI/CD 变量、代码仓库、日志中的密钥指纹,确保不会残留旧值。
  • 发出告警:通知相关团队该密钥已吊销,并触发备用密钥的自动启用。
⚠️ 我见过的惨案:某团队在 GitLab 里直接删除了泄露的 CI/CD 变量,但没吊销 Vault 里的原密钥。攻击者拿到的旧密钥仍然有效,因为 Vault 里的版本没标记为「已吊销」。从那以后,我强制要求所有密钥管理系统必须支持吊销列表(CRL),并且管道每次调用前先检查密钥状态。

5. 过期处理:自动化和告警两手抓

密钥过期本身不是问题,问题是过期后没人管。我建议给每个密钥加上 TTL(Time To Live),到期前自动触发轮换或告警。

密钥类型 推荐过期策略 告警阈值
静态 30 天过期 到期前 7 天告警
数据库凭证 90 天过期 到期前 14 天告警
TLS 证书 按 CA 规定(通常 1 年) 到期前 30 天告警,14 天自动续签

在 CI/CD 管道里,我会添加一个独立的审计 job,每天检查所有密钥的剩余有效期,如果某个密钥快要过期又没有轮换计划,就自动阻断发布管道。

# 在 GitLab CI 中检查密钥过期状态
check-secret-expiry:
  stage: audit
  script:
    - |
      for secret in $(vault list -format=json secret/myapp/ | jq -r '.[]'); do
        expiry=$(vault read -field=expiry "secret/myapp/$secret")
        remaining=$(( expiry - $(date +%s) ))
        if [ "$remaining" -lt "$(( 7*86400 ))" ]; then
          echo "❌ $secret will expire in less than 7 days!"
          exit 1
        fi
      done

嗯,注意这里如果密钥已经过期,管道应该强制失败,而不是悄悄忽略。我见过太多次「告警发到没人看的邮件」的案例。

一句话总结:密钥生命周期不是一次性的配置任务,而是一个持续运行的自动化流程。创建、存储、轮换、吊销、过期处理,每个环节都要在 CI/CD 管道里落地成可审计的 job。

密钥生命周期管理流程图 密钥生命周期管理流程图 ① 创建 ② 存储 ③ 使用 ④ 轮换 更新版本 ⑤ 吊销 ⑥ 过期 自动重新创建 说明 ①→②→③:正向流程 ④→②:轮换更新版本 ⑤:泄露时立即吊销 ⑥→①:过期后自动重建 轮换→吊销:违反策略时触发