使用AWS CDK配置跨账户VPC对等连接路由表遇阻求助
AWS CDK(TypeScript)多账户VPC对等连接路由配置解决方案
问题背景
使用AWS CDK(TypeScript)搭建多账户VPC对等环境,运维账户A已通过CDK Bootstrap获得目标账户B的信任(支持部署和查询),但配置跨账户路由表时遇到阻碍,尝试两种方案均失败:
失败方案一:跨账户栈引用
- 账户A栈:创建VPC A,创建含
ec2:AcceptVpcPeeringConnection、ec2:ModifyVpcPeeringConnectionOptions、ec2:CreateRoute权限的角色,允许账户B扮演。 - 账户B栈:创建VPC B,用上述角色接受对等连接,为VPC B子网添加指向VPC A的路由。
- 账户A栈:为VPC A子网添加指向VPC B的路由。
失败原因:CDK不支持跨账户跨栈资源引用,栈3需要栈2的VPC B CIDR,触发错误:Cross stack references are only supported for stacks deployed to the same account or between nested stacks and their parent stack。
失败方案二:CustomResource跨账户调用
取消账户A的栈3,改为在账户B栈中用Lambda自定义资源为账户A创建路由:
失败原因:
- 扮演角色时提示
Missing credentials in config:CustomResource自身IAM策略未授权sts:AssumeRole操作,无法切换到账户A的角色。 - 不扮演角色时提示权限不足:账户B主体无权限修改账户A侧的路由和对等连接配置。
正确实现方案
核心思路:通过跨账户角色信任+自定义资源完成跨账户操作,避免CDK跨栈引用限制,同时确保权限链完整。
步骤1:账户A部署栈
- 创建VPC A,记录其CIDR、VPC ID、私有路由表ID(可通过CfnOutput输出)。
- 创建跨账户角色,配置:
- 信任策略:允许账户B的CDK自定义资源执行角色(或账户B根ID)扮演。
- 权限策略:授予
ec2:AcceptVpcPeeringConnection、ec2:ModifyVpcPeeringConnectionOptions、ec2:CreateRoute、ec2:DeleteRoute权限(建议限定资源范围,如指定VPC A的路由表ARN)。
- 输出角色ARN、VPC A的CIDR、VPC ID、路由表ID,供账户B栈使用(可通过SSM参数存储或手动传递)。
步骤2:账户B部署栈
- 定义参数,传入账户A的角色ARN、VPC A的CIDR、VPC ID、路由表ID。
- 创建VPC B,发起VPC对等连接请求(指定账户A的VPC ID和账户ID)。
- 为VPC B的所有子网路由表添加指向VPC A CIDR的路由(本地操作,无需跨账户)。
- 创建自定义资源(AwsCustomResource),完成两个跨账户操作:
- 扮演账户A的角色,接受VPC对等连接请求。
- 扮演账户A的角色,为VPC A的路由表添加指向VPC B CIDR的路由。
- 自定义资源的IAM策略必须包含
sts:AssumeRole权限,允许切换到账户A的跨账户角色。
代码示例
账户A栈代码
import * as cdk from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as iam from 'aws-cdk-lib/aws-iam'; const ACCOUNT_B_ID = '123456789012'; // 替换为实际账户B ID const REGION = 'us-east-1'; export class AccountAStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // 创建VPC A const vpcA = new ec2.Vpc(this, 'VpcA', { cidr: '10.0.0.0/16', maxAzs: 2, subnetConfiguration: [ { name: 'private', subnetType: ec2.SubnetType.PRIVATE_ISOLATED } ] }); // 创建跨账户角色,允许账户B的自定义资源执行角色扮演 const crossAccountRole = new iam.Role(this, 'CrossAccountPeeringRole', { assumedBy: new iam.AccountPrincipal(ACCOUNT_B_ID), // 更严格的信任策略:指定账户B的CDK自定义资源执行角色ARN // assumedBy: new iam.ArnPrincipal(`arn:aws:iam::${ACCOUNT_B_ID}:role/cdk-hnb659fds-custom-resource-exec-role-${REGION}`), inlinePolicies: { PeeringPermissions: new iam.PolicyDocument({ statements: [ new iam.PolicyStatement({ actions: [ 'ec2:AcceptVpcPeeringConnection', 'ec2:ModifyVpcPeeringConnectionOptions', 'ec2:CreateRoute', 'ec2:DeleteRoute' ], // 限定资源范围,提升安全性 resources: [ vpcA.vpcArn, ...vpcA.privateSubnets.map(s => s.routeTable.routeTableArn) ] }) ] }) } }); // 输出关键信息,供账户B栈使用 new cdk.CfnOutput(this, 'CrossAccountRoleArn', { value: crossAccountRole.roleArn }); new cdk.CfnOutput(this, 'VpcACidr', { value: vpcA.vpcCidrBlock }); new cdk.CfnOutput(this, 'VpcAId', { value: vpcA.vpcId }); new cdk.CfnOutput(this, 'VpcAPrivateRouteTableIds', { value: vpcA.privateSubnets.map(s => s.routeTable.routeTableId).join(',') }); } }
账户B栈代码
import * as cdk from 'aws-cdk-lib'; import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as custom_resources from 'aws-cdk-lib/custom-resources'; import * as logs from 'aws-cdk-lib/aws-logs'; const ACCOUNT_A_ID = '098765432109'; // 替换为实际账户A ID const REGION = 'us-east-1'; export class AccountBStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // 传入账户A的资源参数 const accountARoleArn = new cdk.CfnParameter(this, 'AccountARoleArn', { type: 'String' }); const vpcACidr = new cdk.CfnParameter(this, 'VpcACidr', { type: 'String' }); const vpcAId = new cdk.CfnParameter(this, 'VpcAId', { type: 'String' }); const vpcAPrivateRouteTableIds = new cdk.CfnParameter(this, 'VpcAPrivateRouteTableIds', { type: 'String' }); // 创建VPC B const vpcB = new ec2.Vpc(this, 'VpcB', { cidr: '10.1.0.0/16', maxAzs: 2, subnetConfiguration: [ { name: 'private', subnetType: ec2.SubnetType.PRIVATE_ISOLATED } ] }); // 创建VPC对等连接请求(从B到A) const peering = new ec2.CfnVpcPeeringConnection(this, 'VpcPeering', { peerAccountId: ACCOUNT_A_ID, peerVpcId: vpcAId.valueAsString, vpcId: vpcB.vpcId }); // 为VPC B的私有子网路由表添加指向VPC A的路由 vpcB.privateSubnets.forEach((subnet, index) => { new ec2.CfnRoute(this, `RouteToA-${index}`, { routeTableId: subnet.routeTable.routeTableId, destinationCidrBlock: vpcACidr.valueAsString, vpcPeeringConnectionId: peering.ref }); }); // 自定义资源:接受对等连接+为VPC A添加路由 const routeTableIds = vpcAPrivateRouteTableIds.valueAsString.split(','); routeTableIds.forEach((routeTableId, index) => { new custom_resources.AwsCustomResource(this, `CrossAccountRoute-${index}`, { policy: custom_resources.AwsCustomResourcePolicy.fromStatements([ new iam.PolicyStatement({ actions: ['sts:AssumeRole'], resources: [accountARoleArn.valueAsString] }) ]), logRetention: logs.RetentionDays.ONE_DAY, onCreate: { service: 'EC2', action: 'createRoute', assumedRoleArn: accountARoleArn.valueAsString, parameters: { RouteTableId: routeTableId, DestinationCidrBlock: vpcB.vpcCidrBlock, VpcPeeringConnectionId: peering.ref }, physicalResourceId: custom_resources.PhysicalResourceId.of(`create-route-${peering.ref}-${routeTableId}`) }, onDelete: { service: 'EC2', action: 'deleteRoute', assumedRoleArn: accountARoleArn.valueAsString, parameters: { RouteTableId: routeTableId, DestinationCidrBlock: vpcB.vpcCidrBlock } }, installLatestAwsSdk: true // 避免SDK版本兼容问题 }); }); // 接受对等连接的自定义资源(需在路由创建前执行) const acceptPeering = new custom_resources.AwsCustomResource(this, 'AcceptPeering', { policy: custom_resources.AwsCustomResourcePolicy.fromStatements([ new iam.PolicyStatement({ actions: ['sts:AssumeRole'], resources: [accountARoleArn.valueAsString] }) ]), logRetention: logs.RetentionDays.ONE_DAY, onCreate: { service: 'EC2', action: 'acceptVpcPeeringConnection', assumedRoleArn: accountARoleArn.valueAsString, parameters: { VpcPeeringConnectionId: peering.ref }, physicalResourceId: custom_resources.PhysicalResourceId.of(`accept-peering-${peering.ref}`) }, installLatestAwsSdk: true }); // 确保路由创建在对等连接接受之后 routeTableIds.forEach((_, index) => { this.node.tryFindChild(`CrossAccountRoute-${index}`)?.node.addDependency(acceptPeering); }); } }
关键注意事项
- 权限最小化:跨账户角色的权限应限定在具体资源(如VPC A的路由表),避免使用
*。 - 信任策略严格性:优先指定账户B的自定义资源执行角色ARN,而非账户根ID,降低权限泄露风险。
- SDK版本:设置
installLatestAwsSdk: true,避免旧版SDK的凭证获取问题。 - 资源依赖:确保路由创建操作在对等连接接受之后执行,避免因对等连接未生效导致路由创建失败。
内容的提问来源于stack exchange,提问作者qwertasek
相关产品推荐
相关产品推荐

