You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用AWS CDK配置跨账户VPC对等连接路由表遇阻求助

AWS CDK(TypeScript)多账户VPC对等连接路由配置解决方案

问题背景

使用AWS CDK(TypeScript)搭建多账户VPC对等环境,运维账户A已通过CDK Bootstrap获得目标账户B的信任(支持部署和查询),但配置跨账户路由表时遇到阻碍,尝试两种方案均失败:

失败方案一:跨账户栈引用

  1. 账户A栈:创建VPC A,创建含ec2:AcceptVpcPeeringConnection、ec2:ModifyVpcPeeringConnectionOptions、ec2:CreateRoute权限的角色,允许账户B扮演。
  2. 账户B栈:创建VPC B,用上述角色接受对等连接,为VPC B子网添加指向VPC A的路由。
  3. 账户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部署栈

  1. 创建VPC A,记录其CIDR、VPC ID、私有路由表ID(可通过CfnOutput输出)。
  2. 创建跨账户角色,配置:
    • 信任策略:允许账户B的CDK自定义资源执行角色(或账户B根ID)扮演。
    • 权限策略:授予ec2:AcceptVpcPeeringConnection、ec2:ModifyVpcPeeringConnectionOptions、ec2:CreateRoute、ec2:DeleteRoute权限(建议限定资源范围,如指定VPC A的路由表ARN)。
  3. 输出角色ARN、VPC A的CIDR、VPC ID、路由表ID,供账户B栈使用(可通过SSM参数存储或手动传递)。

步骤2:账户B部署栈

  1. 定义参数,传入账户A的角色ARN、VPC A的CIDR、VPC ID、路由表ID。
  2. 创建VPC B,发起VPC对等连接请求(指定账户A的VPC ID和账户ID)。
  3. 为VPC B的所有子网路由表添加指向VPC A CIDR的路由(本地操作,无需跨账户)。
  4. 创建自定义资源(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);
    });
  }
}

关键注意事项

  1. 权限最小化:跨账户角色的权限应限定在具体资源(如VPC A的路由表),避免使用*。
  2. 信任策略严格性:优先指定账户B的自定义资源执行角色ARN,而非账户根ID,降低权限泄露风险。
  3. SDK版本:设置installLatestAwsSdk: true,避免旧版SDK的凭证获取问题。
  4. 资源依赖:确保路由创建操作在对等连接接受之后执行,避免因对等连接未生效导致路由创建失败。

内容的提问来源于stack exchange,提问作者qwertasek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 04:07:16