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 就够用。
创建 Secrets
创建 Secret 有几种方式。我最常用的是 CLI,因为脚本化方便。但刚开始学习时,用控制台走一遍也挺好。
方法一:AWS 管理控制台
- 打开 Secrets Manager 服务页面,点击「Store a new secret」。
- 选择机密类型。常见的是「Credentials for RDS database」或「Other type of secret」。
- 填写键值对。比如用户名
dbuser,密码P@ssw0rd!。 - 选择加密密钥(默认使用
aws/secretsmanager托管密钥,也可以用客户管理 KMS Key)。 - 配置自动轮换(我们下一节细说)。
- 输入 Secret 名称和描述。比如
prod/mydb。 - 点击 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 集成步骤
- 创建一个 IAM 角色,附加
SecretsManagerReadWrite策略(或者更细粒度的权限,只允许读取特定 Secret)。 - 将角色附加给 EC2 实例。启动实例时选择「Launch with role」,或者之后通过「Modify IAM role」绑定。
- 在 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 的生命周期。这些东西,多用几次自然就熟了。