2. AWS Secrets Manager 入门

第一次接触 AWS Secrets Manager 的时候,我还在处理一个挺头疼的问题——数据库密码硬编码在代码里,每次换密码都得重新部署。后来发现 Secrets Manager 其实就干一件事:把敏感信息(密码、、证书)统一存起来,再用的时候安全取出来。说白了,它就是个带自动轮换功能的保险柜。

基本概念

在讲操作之前,先理清几个核心概念。你想想看,下面这些你肯定都会用到:

  • Secret(机密):一个包含敏感数据的逻辑实体。可以存数据库凭证、OAuth 、SSH 密钥等。每个 Secret 由 SecretId(通常是 ARN)唯一标识。
  • Secret Value(机密值):实际存储的二进制数据,最大 64KB。通常以 JSON 格式组织,比如 {"username":"admin","password":"xxx"}。
  • ARN(资源标识符):每个 Secret 的全局唯一地址,比如 arn:aws:secretsmanager:us-east-1:123456789012:secret:MyDbSecret-xxxx。
  • 轮换(Rotation):定期自动更新 Secret 的机制。需要搭配一个 Lambda 函数来执行轮换逻辑。
  • 标签(Tags):通过键值对管理元数据,方便做成本分析和权限控制。

我个人的经验:刚接触时容易把 Secret 和 Parameter Store(SSM)搞混。Secrets Manager 比 Parameter Store 贵一些,但它自带轮换、跨账号借用、自动修改数据库密码等能力。选谁?很简单:需要自动轮换或超长密钥(超过 8KB)就用 Secrets Manager,否则 Parameter Store 就够用。

AWS Secrets Manager 核心概念与流程 Secrets Manager Secret(机密对象) ARN + 标签 自动轮换策略 轮换 Lambda 函数 EC2 / Lambda / RDS / 应用 每个 Secret 都有一个唯一 ARN

创建 Secrets

创建 Secret 有几种方式。我最常用的是 CLI,因为脚本化方便。但刚开始学习时,用控制台走一遍也挺好。

方法一:AWS 管理控制台

  1. 打开 Secrets Manager 服务页面,点击「Store a new secret」。
  2. 选择机密类型。常见的是「Credentials for RDS database」或「Other type of secret」。
  3. 填写键值对。比如用户名 dbuser,密码 P@ssw0rd!。
  4. 选择加密密钥(默认使用 aws/secretsmanager 托管密钥,也可以用客户管理 KMS Key)。
  5. 配置自动轮换(我们下一节细说)。
  6. 输入 Secret 名称和描述。比如 prod/mydb。
  7. 点击 Store。

小技巧:Secret 名称建议用「环境/应用/用途」的层级命名,比如 dev/user-api/db-password。我在项目中这么命名后,资源管理器里一眼就能看出归属,配合标签也省事。

方法二:AWS CLI

命令其实很简洁。下面是我自己常用的模板:

# 创建一个简单 Secret
aws secretsmanager create-secret \
    --name prod/myapp/db \
    --description "Database credentials for myapp" \
    --secret-string '{"username":"admin","password":"SuperSecure123!"}' \
    --region us-east-1

# 创建一个带 KMS 加密的 Secret
aws secretsmanager create-secret \
    --name dev/crm/ \
    --kms-key-id alias/my-custom-key \
    --secret-string '{"":"sk-xxxxxxxx"}'

注意 --secret-string 支持 JSON 或纯文本。但建议都用 JSON,方便应用解析。

方法三:Python SDK (boto3)

有一次我要批量从旧系统迁移几百个密码,写了个小脚本。核心代码就几行:

import boto3
import json

client = boto3.client('secretsmanager', region_name='us-west-2')

response = client.create_secret(
    Name='staging/billing/db',
    Description='Billing DB credentials',
    SecretString=json.dumps({
        'host': 'billing-db.example.com',
        'port': 3306,
        'user': 'billing_app',
        'password': 'CorrectHorseBatteryStaple'
    }),
    Tags=[{'Key': 'Environment', 'Value': 'staging'}]
)
print(f"Created secret ARN: {response['ARN']}")

注意 SecretString 必须序列化成字符串。如果存二进制文件要用 SecretBinary,但是我很少用。

轮换 Secrets

轮换是我觉得 Secrets Manager 最值钱的功能。以前手动更新密码,总有人忘记改代码里的配置,导致半夜告警。自动轮换配上 Lambda,基本可以做到零停机。

轮换策略

AWS 提供了两种轮换策略:

策略 描述 场景
单用户轮换 用一个 Lambda 函数生成新密码,同时更新 Secret 和目标资源(如 RDS)。应用再用旧密码连接时会失败,直到代码取到新版本。 开发环境、可接受短暂中断的场景
双用户轮换 创建两个用户(master / rotation)。轮换时先改 master 密码,再逐步切换应用使用的新凭证。整个过程应用一直能用其中一个有效密码。 生产环境、要求高可用

我个人习惯在测试环境先用单用户轮换,确认 Lambda 没问题再切双用户。毕竟双用户实现更复杂,Lambda 里要处理两套凭证的切换逻辑。

配置自动轮换

创建 Secret 时可以直接勾选「Enable automatic rotation」,并选择轮换间隔(如 30 天)。AWS 会要求指定一个 Lambda 函数来做实际轮换。如果你选 RDS 数据库类型,可以选 AWS 预置的轮换模板,省心不少。

下面是 CLI 方式启用轮换(假设已经有一个轮换 Lambda 的 ARN):

aws secretsmanager rotate-secret \
    --secret-id prod/mydb \
    --rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:my-rotation-lambda \
    --rotation-rules '{"AutomaticallyRotateAfterDays": 30}'

轮换 Lambda 的避坑指南

我曾经踩过一个坑:第一次写轮换 Lambda 时,没处理好不同版本的 Secret 读取。Lambda 函数有多个阶段——createSecret、setSecret、testSecret、finishSecret。每个阶段必须严格按顺序执行,而且不能假设秘钥在哪个阶段已经生效。另外,Lambda 的 IAM 角色至少要给 secretsmanager:GetSecretValue、UpdateSecret 和目标数据库的读写权限。不然轮换到一半卡住,Secret 状态就变成 RotatingFailed,还得手动恢复。

与 EC2 / Lambda 集成

集成说白了就是:应用运行时从 Secrets Manager 拉取敏感信息,而不是写在代码或环境变量里。怎么做?核心是两点:合适的 IAM 权限 + 正确的 SDK 调用。

EC2 集成步骤

  1. 创建一个 IAM 角色,附加 SecretsManagerReadWrite 策略(或者更细粒度的权限,只允许读取特定 Secret)。
  2. 将角色附加给 EC2 实例。启动实例时选择「Launch with role」,或者之后通过「Modify IAM role」绑定。
  3. 在 EC2 上安装 AWS CLI 或 SDK。应用启动时调用 get-secret-value 命令读取 Secret。

示例:在 EC2 上读取 Secret

# 直接从命令行读取(用于测试)
aws secretsmanager get-secret-value --secret-id dev/app/db --query SecretString --output text

# Python 代码里读取(生产用)
import boto3, json
client = boto3.client('secretsmanager')
resp = client.get_secret_value(SecretId='dev/app/db')
creds = json.loads(resp['SecretString'])
print(f"Connecting to {creds['host']} as {creds['user']}")

这里有个坑:第一次取到的 Secret 可能是旧版本。如果你启用了轮换,代码里要处理版本号 VersionId。大多数情况下用 AWSCURRENT 版本就够了,也就是不指定版本 ID 时默认返回的那个。

Lambda 集成

Lambda 集成更简单,因为 Lambda 天然带 IAM 执行角色。你只需要给角色添加 secretsmanager:GetSecretValue 权限。我在项目中一般把权限限制到具体 Secret 的 ARN:

{
    "Effect": "Allow",
    "Action": "secretsmanager:GetSecretValue",
    "Resource": "arn:aws:secretsmanager:us-east-1:*:secret:prod/*"
}

这样避免权限过大。注意 GetSecretValue 的 API 调用会留下 CloudTrail 日志,方便审计谁在什么时候拿走了密码。

下面是一个完整的 Lambda 函数示例(Node.js 版本),我在处理支付回调时用过类似的:

const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager();
exports.handler = async (event) => {
    const params = { SecretId: 'prod/payment/' };
    try {
        const data = await secretsManager.getSecretValue(params).promise();
        const secret = JSON.parse(data.SecretString);
        // 用 secret. 去调用支付服务
        return { statusCode: 200, body: 'Success' };
    } catch (err) {
        console.error('Failed to fetch secret', err);
        throw err;
    }
};

最佳实践汇总

  • 最小权限:每条规则只给 GetSecretValue,不要开放 PutSecretValue。
  • 缓存凭证:别每次请求都调 API,用内存或本地缓存(比如缓存 5 分钟),避免被限流。
  • 使用 VPC Endpoint:如果 EC2/Lambda 在 VPC 内且没有 NAT,配置 Secrets Manager 的 VPC Endpoint(com.amazonaws.region.secretsmanager),不走公网。
  • 监控轮换:通过 CloudWatch 设置告警,如果 RotationFailed 事件发生马上通知。
  • 不要硬编码 Secret ARN:把 ARN 放在环境变量或参数配置中,方便切换环境。

顺手提一句:如果你在用 ECS 或 EKS,Secrets Manager 还能通过 secretsmanager 卷挂载或环境变量注入(需要 ECS task role 或 Secrets Store CSI Driver)。这些内容后面会单独讲,这里先不展开。

其实学到这里,你已经能处理大部分日常场景了。Secrets Manager 入门不难,难点在于把轮换 Lambda 写好、做好权限控制、以及理解不同版本 Secret 的生命周期。这些东西,多用几次自然就熟了。