跨账号通过Code Pipeline部署时从AWS Secrets Manager获取密钥失败
跨账号CDK传递Secrets Manager密钥到Lambda的解决方案
问题根源
你当前用secretValueFromJson(key).unsafeUnwrap()生成的是CloudFormation的{{resolve:secretsmanager:arn:...}}引用,这种引用仅支持同账号内解析。跨账号场景下,Prod/Dev账号的CloudFormation没有权限直接访问Security账号的Secrets Manager,导致部署时无法解析引用,触发"找不到密钥"的错误。
可行解决方案
方案1:CodePipeline前置动作预取密钥到同账号SSM
在CodePipeline的部署阶段前,添加一个CodeBuild或Lambda动作,先获取Security账号的密钥值,存入Prod/Dev账号的SSM参数中,再让CDK从SSM读取值:
- 配置CodeBuild项目的角色,允许切换到Prod/Dev账号的角色(该角色已拥有Security账号密钥的读取权限)。
- 在CodeBuild脚本中用AWS CLI或SDK调用Secrets Manager获取密钥字段:
# 切换到Prod角色(示例) aws sts assume-role --role-arn arn:aws:iam::PROD_ACCOUNT_ID:role/ProdSecretsReaderRole --role-session-name "fetch-secret" > creds.json export AWS_ACCESS_KEY_ID=$(cat creds.json | jq -r '.Credentials.AccessKeyId') export AWS_SECRET_ACCESS_KEY=$(cat creds.json | jq -r '.Credentials.SecretAccessKey') export AWS_SESSION_TOKEN=$(cat creds.json | jq -r '.Credentials.SessionToken') # 获取密钥字段并存入SSM SECRET_VALUE=$(aws secretsmanager get-secret-value --secret-id arn:aws:secretsmanager:SECURITY_REGION:SECURITY_ACCOUNT_ID:secret:MySecret --query 'SecretString' | jq -r '.myKey') aws ssm put-parameter --name "/prod/my-secret-key" --value "$SECRET_VALUE" --type "SecureString" --overwrite - CDK中从SSM参数读取值传递给Lambda:
const secretValue = ssm.StringParameter.fromSecureStringParameterAttributes(this, 'ProdSecret', { parameterName: '/prod/my-secret-key', version: 1, }).stringValue; new lambda.Function(this, 'MyLambda', { // ...其他配置 environment: { MY_SECRET: secretValue, }, });
方案2:用自定义资源跨账号获取密钥
在Prod/Dev的LambdaStack中创建自定义资源,通过Lambda函数跨账号读取Security账号的密钥,再将值传递给Lambda:
- 创建一个自定义资源提供者Lambda(同账号),该Lambda的角色拥有切换到Security账号密钥读取角色的权限。
- 自定义资源触发时,调用Secrets Manager获取密钥值,返回给CloudFormation。
- CDK中引用自定义资源的输出值作为Lambda的环境变量:
const fetchSecretResource = new customResources.AwsCustomResource(this, 'FetchProdSecret', { onCreate: { service: 'SecretsManager', action: 'getSecretValue', parameters: { SecretId: 'arn:aws:secretsmanager:SECURITY_REGION:SECURITY_ACCOUNT_ID:secret:MySecret', }, physicalResourceId: customResources.PhysicalResourceId.of(Date.now().toString()), assumedRoleArn: 'arn:aws:iam::PROD_ACCOUNT_ID:role/ProdSecretsReaderRole', // 切换到有权限的角色 }, policy: customResources.AwsCustomResourcePolicy.fromSdkCalls({ resources: customResources.AwsCustomResourcePolicy.ANY_RESOURCE, }), }); const secretFieldValue = fetchSecretResource.getResponseField('SecretString').apply(s => JSON.parse(s).myKey); new lambda.Function(this, 'MyLambda', { // ...其他配置 environment: { MY_SECRET: secretFieldValue, }, });
方案3:CDK Synth阶段提前解析密钥(适合允许密钥出现在模板的场景)
直接在CDK的部署脚本中,提前用AWS SDK获取Security账号的密钥值,作为参数传递给LambdaStack,这样synth出来的模板会包含实际密钥值:
- 确保执行CDK synth的角色(Pipeline的执行角色)拥有读取Security账号密钥的权限。
- 在bin目录的CDK入口文件中添加代码:
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager"; const secretsManagerClient = new SecretsManagerClient({ region: 'SECURITY_REGION' }); const getSecretCmd = new GetSecretValueCommand({ SecretId: 'arn:aws:secretsmanager:SECURITY_REGION:SECURITY_ACCOUNT_ID:secret:MySecret', }); const secretResponse = await secretsManagerClient.send(getSecretCmd); const secretObj = JSON.parse(secretResponse.SecretString!); const mySecretValue = secretObj.myKey; // 传递给LambdaStack new LambdaStack(app, 'ProdLambdaStack', { env: { account: 'PROD_ACCOUNT_ID', region: 'PROD_REGION' }, mySecretValue: mySecretValue, }); - LambdaStack中直接使用传入的参数:
interface LambdaStackProps extends cdk.StackProps { mySecretValue: string; } export class LambdaStack extends cdk.Stack { constructor(scope: Construct, id: string, props: LambdaStackProps) { super(scope, id, props); new lambda.Function(this, 'MyLambda', { // ...其他配置 environment: { MY_SECRET: props.mySecretValue, }, }); } }
注意事项
- 权限验证:所有涉及跨账号访问的角色,必须确保信任关系正确(比如Security账号的密钥策略允许Prod/Dev角色读取,Prod/Dev的角色信任CodeBuild或自定义资源的角色)。
- 密钥轮换:如果需要密钥自动轮换,方案1和方案2可搭配EventBridge规则,触发重新获取并更新Lambda环境变量/SSM参数;方案3需要重新执行Pipeline来更新模板。
内容的提问来源于stack exchange,提问作者Nicolas Gorga
相关产品推荐
相关产品推荐

