多云Secrets工具差异解析教程 - 第2节

AWS Secrets Manager 基础:核心概念与架构

说实话,刚接触 AWS Secrets Manager 那会儿,我第一反应是:这不就是个加密的键值对存储吗?用 DynamoDB 加个 KMS 也能凑合。但真的在生产环境跑起来,才发现它藏了不少细节。这一章我们先扎扎实实把基础打牢。

核心概念

先过一遍主要术语,后面操作才不会晕头转向。

概念说明
Secret一个机密数据对象,可以存数据库密码、、OAuth 等。最大 65,536 字节。
Secret Value某个版本下的实际机密内容,可以是纯文本或 JSON 结构。
Version / Stage每个 Secret 可以有多个版本,版本通过「Stage」标签管理(如 AWSCURRENT、AWSPREVIOUS)。
Rotation自动轮换机制,Secrets Manager 可以按周期调 Lambda 函数更新密码。
KMS Key加密用的对称密钥,默认使用 aws/secretsmanager 服务自带 CMK,也可以自己指定。
Resource Policy类似 S3 Bucket Policy,直接挂到 Secret 上的权限策略,支持跨账号访问。

为啥要有「Stage」这层? 我曾经遇到一个场景:轮换期间新密码还在测试,旧密码需要继续可用。Secrets Manager 允许同时保留 AWSCURRENT 和 AWSPREVIOUS 两个版本,应用端在读取时直接指定 Stage,完全不用改代码。

架构拆解

来看一张简图,理解 Secrets Manager 在 AWS 环境里的位置和调用链。

AWS Secrets Manager 核心架构 应用程序 / Lambda SDK / CLI / 控制台 Secrets Manager 控制平面 + 数据平面 KMS 加密 后端存储 调用链路示意: 1. 应用通过 SDK 发起 GetSecretValue 请求(指定 SecretId + Stage) 2. Secrets Manager 解析权限 → 从后端存储获取密文 → KMS 解密 → 返回明文 3. 所有操作都会记录 CloudTrail 审计日志

嗯,这里有个点容易忽略:Secrets Manager 的读操作是有缓存的。SDK 默认会缓存 1 小时,如果你改了密码应用没读到,先查缓存时间,别急着怀疑代码。

我的习惯:生产环境用 secretsmanager:GetSecretValue 配合 IAM 条件键 secretsmanager:VersionStage 做精细控制,只允许取 AWSCURRENT 版本,避免应用意外读到旧密码。

基础操作:创建、读取、更新、删除

创建 Secret

我个人喜欢用 AWS CLI 做快速验证,控制台步骤多但更直观。先看 CLI 的创建方式:

# 创建简单的明文 Secret
aws secretsmanager create-secret \
  --name dev/wordpress/db \
  --description "WordPress 数据库密码" \
  --secret-string "MyP@ssw0rd!2024" \
  --kms-key-id alias/aws/secretsmanager

# 创建 JSON 结构的 Secret(推荐)
aws secretsmanager create-secret \
  --name prod/ \
  --secret-string '{"":"sk-xxxx","endpoint":"https://api.example.com"}' \
  --tags Key=Environment,Value=prod

注意:不指定 --kms-key-id 的话,会用默认的 aws/secretsmanager 密钥,加密成本为零(不额外收费)。

我曾经踩过的坑:创建时 secret-string 超过 65KB 直接报错。如果你的凭证是几百 KB 的 JKS 或 PEM 文件,先压缩成 base64 再放到 secret-string 里,或者改用 --secret-binary 参数。

读取 Secret

读取是最频繁的操作。接口有 GetSecretValue 和 BatchGetSecretValue(批量获取,减少 API 调用次数)。

# 读取当前版本
aws secretsmanager get-secret-value \
  --secret-id prod/ \
  --version-stage AWSCURRENT

# 读取历史版本(回滚场景用)
aws secretsmanager get-secret-value \
  --secret-id prod/ \
  --version-id "a1b2c3d4-..."

返回的 JSON 里你会看到四个重要字段:SecretString(明文)、SecretBinary(二进制流)、VersionId(UUID)、VersionStages(当前 stage 列表)。

更新 Secret

更新不是直接在原值上改,而是 创建新版本。旧版本会打上 AWSPREVIOUS 标签保留。

# 更新 secret-string 内容
aws secretsmanager put-secret-value \
  --secret-id prod/ \
  --secret-string '{"":"sk-new","endpoint":"https://api.example.com"}' \
  --version-stages AWSCURRENT

# 如果想强制覆盖某个 stage 的版本(比如把某个旧版本提升为当前版本)
aws secretsmanager update-secret-version-stage \
  --secret-id prod/ \
  --version-stage AWSCURRENT \
  --remove-from-version-id "old-id" \
  --move-to-version-id "new-id"

关于轮换的思考:手动 put-secret-value 不会触发轮换函数。如果你需要自动轮换数据库密码,得配置 Rotation Lambda。轮换策略有单用户和双用户两种,我遇到的大部分场景用单用户就够了——在 AWSCURRENT 更新前先创建临时凭证,应用无感知切换。

删除 Secret

删除分两步:先 计划删除(设置恢复窗口),超过窗口期才会真正删除。窗口期默认 30 天,最小 7 天。

# 计划删除,设置 7 天恢复窗口
aws secretsmanager delete-secret \
  --secret-id dev/testing-key \
  --recovery-window-in-days 7

# 如果你的 Secret 已经没用了,也不想留恢复机会,用 --force-delete-without-recovery
aws secretsmanager delete-secret \
  --secret-id dev/testing-key \
  --force-delete-without-recovery

千万别手抖用 force 模式! 我有个同事在测试环境误删了生产 Secret,还好 30 天窗口期救了一命。从那以后我养成了习惯:只在里程碑备份后才能用 force。

权限与审计

操作 Secrets Manager 必须要有 IAM 策略。举个例子,应用只需要读取,不要给它更新的权限:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/*",
      "Condition": {
        "StringEquals": {
          "secretsmanager:VersionStage": "AWSCURRENT"
        }
      }
    }
  ]
}

审计日志全部落在 CloudTrail 里,你可以通过 eventName 筛选:GetSecretValue、PutSecretValue、DeleteSecret 等。说实话,我每次排查问题第一件事就是查 CloudTrail,看看是谁在什么时间改了哪个 Secret。

总结一下操作清单

操作对应 API注意事项
创建CreateSecret限定 KMS 权限,用 JSON 结构更易维护
读取GetSecretValue默认缓存 1 小时,可以指定版本 Stage
更新PutSecretValue创建新版本,旧版本保留为 AWSPREVIOUS
删除DeleteSecret必须设置恢复窗口,force 模式慎用
恢复RestoreSecret仅在计划删除窗口期内有效

一个小技巧:如果你的应用使用 Java 或 Python SDK,建议启用 Secrets Manager 缓存插件(比如 aws-secretsmanager-caching-java)。它内部做 LRU + 过期自动刷新,能把你 API 调用次数降到原来的十分之一,而且不会因为缓存读到脏数据。