AWS Secrets Manager:创建、读取、轮换 Secrets,IAM 权限配置

说实话,在我刚接触云安全那会儿,管理敏感信息是个挺头疼的事。代码里硬编码密钥?那是新手才干的。环境变量?稍微好点,但轮换起来麻烦得要命。直到 AWS Secrets Manager 出现,我才觉得这事儿终于有了正解。

这章咱们就聊聊怎么创建、读取和轮换 Secrets,以及怎么配好 IAM 权限。嗯,这些都是你日常用得最多的操作。

创建 Secret:从控制台到 CLI

创建 Secret 其实很简单。我个人习惯先用控制台走一遍,然后再用 CLI 或 SDK 自动化。控制台的向导很友好,你只需要选好密钥类型(明文还是 JSON),设置好自动轮换周期就行。

看看 CLI 怎么做:

# 创建一个明文 Secret
aws secretsmanager create-secret \
    --name MyDatabaseSecret \
    --secret-string '{"username":"admin","password":"P@ssw0rd!"}' \
    --region us-east-1

# 创建一个 JSON 格式的 Secret(更推荐)
aws secretsmanager create-secret \
    --name MyApiKeySecret \
    --secret-string '{"":"abc123xyz789"}' \
    --kms-key-id alias/my-custom-key \
    --description " for external service"

我在项目中遇到过一个问题:创建 Secret 时没指定 KMS 密钥。结果默认用了 AWS 托管密钥,后来审计发现不符合合规要求。所以记住,如果公司有加密策略,最好用自定义 KMS 密钥。

小建议:Secret 名称建议用有意义的命名规则,比如 prod/app1/db/password。这样后续管理和权限控制都方便。

读取 Secret:应用程序的正确姿势

读 Secret 的核心操作就一个 API:GetSecretValue。但你想想看,如果每次请求都去调用 AWS API,延迟和成本都受不了。所以最佳实践是缓存。

这是我在生产环境用的代码模式:

import boto3
from botocore.exceptions import ClientError

def get_secret(secret_name, region="us-east-1"):
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region
    )

    try:
        response = client.get_secret_value(SecretId=secret_name)
        return response['SecretString']
    except ClientError as e:
        if e.response['Error']['Code'] == 'ResourceNotFoundException':
            print(f"Secret {secret_name} not found")
        elif e.response['Error']['Code'] == 'AccessDeniedException':
            print(f"Access denied to secret {secret_name}")
        else:
            raise e

这里有个坑:我见过不少团队直接在生产环境每次请求都读取 Secret。其实你应该缓存到本地内存,并设置一个合理的过期时间(比如轮换间隔的一半)。

避坑指南:千万不要把 Secret 打印到日志里!我曾经在排查 bug 时不小心把 Secret 打到了 CloudWatch,结果 Security Team 连夜找我谈话。敏感信息永远不要输出到日志。

自动轮换 Secrets:安全基石

为什么要轮换?说白了,如果密钥泄露了,轮换能把损失降到最低。AWS Secrets Manager 支持自动轮换,但需要你配置一个 Lambda 函数。

轮换流程大概是这样的:

  • 创建新版本:Lambda 生成新密码并标记为 AWSPENDING
  • 测试新版本:验证新密码能正常工作(比如连数据库)
  • 切换:把新密码标记为 AWSCURRENT,旧密码变成 AWSPREVIOUS
  • 清理:把 AWSPENDING 状态移除

我自己写了一个轮换 Lambda,用 RDS 作为示例:

import boto3
import json
import logging

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    secret_id = event['SecretId']
    client_ = event['ClientRequestToken']
    step = event['Step']

    secrets_client = boto3.client('secretsmanager')

    try:
        metadata = secrets_client.describe_secret(SecretId=secret_id)
        if 'RotationEnabled' not in metadata:
            raise ValueError("Secret not configured for rotation")

        versions = metadata['VersionIdsToStages']
        if client_ not in versions:
            raise ValueError("Invalid client ")

        if metadata['RotationRules']['AutomaticallyAfterDays'] is None:
            raise ValueError("Rotation interval not set")

        if step == 'createSecret':
            create_secret(secrets_client, secret_id, client_)
        elif step == 'setSecret':
            set_secret(secrets_client, secret_id, client_)
        elif step == 'testSecret':
            test_secret(secrets_client, secret_id, client_)
        elif step == 'finishSecret':
            finish_secret(secrets_client, secret_id, client_)
        else:
            raise ValueError(f"Unknown step: {step}")
    except Exception as e:
        logger.error(f"Rotation failed: {str(e)}")
        raise e

我建议轮换周期不要设太长。30天是比较平衡的选择。我见过有人设90天,结果密钥泄露了还不知道。另外,如果数据库压力大,可以安排在凌晨执行轮换。

IAM 权限配置:最小权限原则

权限配置是 Secrets Manager 安全的核心。你看,如果每个开发者都能访问所有 Secret,那跟没加密差不多。

先看看基础策略:

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

这个策略只允许读取 prod/app1/ 路径下的 Secret。我建议你按环境(dev/staging/prod)和应用维度来做资源隔离。

还有一个容易被忽略的点:条件键。比如你可以限制只能从特定的 VPC 访问:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "aws:SourceVpc": "vpc-0abc123def456"
                }
            }
        }
    ]
}

为什么这样做?如果你的应用跑在 EC2 上,这样就算有人拿到了访问密钥,也必须通过你的 VPC 才能读 Secret。安全又加了一层。

权限场景 建议最小操作 资源范围
应用读取 GetSecretValue 特定路径下的 Secret
管理员创建/更新 CreateSecret、PutSecretValue、UpdateSecret 按应用隔离
轮换 Lambda GetSecretValue、PutSecretValue、UpdateSecretVersionStage 特定 Secret ARN
审计 DescribeSecret、ListSecrets 全局,但只读

实战建议

给你分享几个我在项目中积累的经验:

  • Secret 版本管理:轮换时不要手动删除旧版本,AWS 会自动保留最近的 100 个版本。万一新密钥出问题,还能回滚。
  • 监控告警:用 CloudWatch 监控 GetSecretValue 调用的失败次数,超出阈值就告警。这能帮你发现权限问题或攻击行为。
  • 标签:给每个 Secret 打上标签,比如 Environment=production、Owner=platform-team。这样后期查询和成本分析都方便。
核心原则:Secrets Manager 只是一个工具,真正的安全取决于你怎么用。最小权限、自动轮换、定期审计——这三件事做到位了,密钥泄露的风险至少降低80%。

好了,这一章的内容就到这。记住,Secrets Manager 不是银弹,但它能帮你把秘密管理这件事变得可控、可审计、可自动化。下一章咱们聊聊怎么把 Secrets 和 CI/CD 集成,让密钥在流水线里安全流转。

AWS Secrets Manager 核心操作流程 应用服务 IAM 权限策略 Secrets Manager 创建 Secret define-secret-string 读取 Secret get-secret-value 轮换 Secret Lambda 四步流程 createSecret setSecret testSecret finishSecret 最佳实践说明 • 创建时指定自定义 KMS 密钥,满足合规要求 • 读取时缓存到本地内存,避免频繁调用 API • 轮换周期建议 30 天,配合 Lambda 自动完成