Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 81 additions & 4 deletions packages/@aws-cdk/aws-ec2-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ const peeringConnection = vpcA.createPeeringConnection('sameAccountCrossRegionPe

For cross-account connections, the acceptor account needs an IAM role that grants the requestor account permission to initiate the connection. Create a new IAM role in the acceptor account using method `createAcceptorVpcRole` to provide the necessary permissions.

Once role is created in account, provide role arn for field `peerRoleArn` under method `createPeeringConnection`
Once the role is created in the acceptor account, provide the role object for the `peerRole` field under method `createPeeringConnection`:

```ts
const stack = new Stack();
Expand All @@ -339,13 +339,15 @@ const acceptorVpc = new VpcV2(this, 'VpcA', {
primaryAddressBlock: IpAddresses.ipv4('10.0.0.0/16'),
});

const acceptorRoleArn = acceptorVpc.createAcceptorVpcRole('000000000000'); // Requestor account ID
const acceptorRole = acceptorVpc.createAcceptorVpcRole('000000000000'); // Requestor account ID
```

After creating an IAM role in the acceptor account, we can initiate the peering connection request from the requestor VPC. Import acceptorVpc to the stack using `fromVpcV2Attributes` method, it is recommended to specify owner account id of the acceptor VPC in case of cross account peering connection, if acceptor VPC is hosted in different region provide region value for import as well.
The following code snippet demonstrates how to set up VPC peering between two VPCs in different AWS accounts using CDK:

```ts
import * as iam from 'aws-cdk-lib/aws-iam';

const stack = new Stack();

const acceptorVpc = VpcV2.fromVpcV2Attributes(this, 'acceptorVpc', {
Expand All @@ -355,16 +357,91 @@ const acceptorVpc = VpcV2.fromVpcV2Attributes(this, 'acceptorVpc', {
ownerAccountId: '111111111111',
});

const acceptorRoleArn = 'arn:aws:iam::111111111111:role/VpcPeeringRole';
// Import the role created in the acceptor account
// Note: The actual role name will be auto-generated by CDK
const acceptorRole = iam.Role.fromRoleArn(this, 'AcceptorRole',
'arn:aws:iam::111111111111:role/acceptor-stack-acceptorVpcVpcPeeringRole12345678-ABCDEFGHIJKL');

const requestorVpc = new VpcV2(this, 'VpcB', {
primaryAddressBlock: IpAddresses.ipv4('10.1.0.0/16'),
});

const peeringConnection = requestorVpc.createPeeringConnection('crossAccountCrossRegionPeering', {
acceptorVpc: acceptorVpc,
peerRoleArn: acceptorRoleArn,
peerRole: acceptorRole,
});
```

### Importing Existing VPC Peering Connections

You can import an existing VPC peering connection using the `fromAttributes` static method. This is useful when you need to reference a peering connection that was created outside of your CDK stack for routing purposes:

```ts
const myVpc = new VpcV2(this, 'MyVpc', {
primaryAddressBlock: IpAddresses.ipv4('10.1.0.0/16'),
});

const existingPeeringConnection = VPCPeeringConnection.fromAttributes(this, 'ExistingPeering', {
vpcPeeringConnectionId: 'pcx-12345678',
});

// Use the imported peering connection as a route target
const routeTable = new RouteTable(this, 'RouteTable', {
vpc: myVpc,
});

routeTable.addRoute('PeeringRoute', '10.0.0.0/16', { gateway: existingPeeringConnection });
```

### Creating Requestor-Side Peering Roles

For cross-account peering scenarios, you can also create a role on the requestor side using the `createRequestorPeerRole` method. This is useful when you need to set up the role infrastructure from the requestor account:

```ts
const requestorVpc = new VpcV2(this, 'RequestorVpc', {
primaryAddressBlock: IpAddresses.ipv4('10.1.0.0/16'),
});

// Create a role that can be assumed by the acceptor account
const requestorRole = requestorVpc.createRequestorPeerRole('111111111111'); // Acceptor account ID
```

### Complete Cross-Account Peering Setup

Here's a complete example showing both sides of a cross-account peering connection:

```ts
// 1. In acceptor account - create acceptor role and VPC
const acceptorVpc = new VpcV2(this, 'AcceptorVpc', {
primaryAddressBlock: IpAddresses.ipv4('10.0.0.0/16'),
});

const acceptorRole = acceptorVpc.createAcceptorVpcRole('123456789012'); // Requestor account ID

// 2. In requestor account - create requestor role and peering connection
const requestorVpc = new VpcV2(this, 'RequestorVpc', {
primaryAddressBlock: IpAddresses.ipv4('10.1.0.0/16'),
});

// Import the acceptor VPC and role
const importedAcceptorVpc = VpcV2.fromVpcV2Attributes(this, 'ImportedAcceptorVpc', {
vpcId: 'vpc-acceptor123',
vpcCidrBlock: '10.0.0.0/16',
region: 'us-east-1',
ownerAccountId: '111111111111',
});

const importedAcceptorRole = iam.Role.fromRoleArn(this, 'ImportedAcceptorRole',
'arn:aws:iam::111111111111:role/VpcPeeringRole');

// Create the peering connection
const peeringConnection = requestorVpc.createPeeringConnection('CrossAccountPeering', {
acceptorVpc: importedAcceptorVpc,
peerRole: importedAcceptorRole,
});

// 3. The acceptor account must then accept the peering connection
// This is done outside of CDK using AWS CLI or console
```

### Route Table Configuration
Expand Down
64 changes: 56 additions & 8 deletions packages/@aws-cdk/aws-ec2-alpha/lib/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CfnEIP, CfnEgressOnlyInternetGateway, CfnInternetGateway, CfnNatGateway, CfnVPCPeeringConnection, CfnRoute, CfnRouteTable, CfnVPCGatewayAttachment, CfnVPNGateway, CfnVPNGatewayRoutePropagation, GatewayVpcEndpoint, IRouteTable, IVpcEndpoint, RouterType } from 'aws-cdk-lib/aws-ec2';
import { IRole } from 'aws-cdk-lib/aws-iam';
import { Construct, IDependable } from 'constructs';
import { Annotations, Duration, FeatureFlags, IResource, Resource, Tags, ValidationError } from 'aws-cdk-lib/core';
import { IVpcV2, VPNGatewayV2Options } from './vpc-v2-base';
Expand Down Expand Up @@ -188,11 +189,11 @@ export interface VPCPeeringConnectionOptions {
readonly acceptorVpc: IVpcV2;

/**
* The role arn created in the acceptor account.
* The role created in the acceptor account for cross-account peering.
*
* @default - no peerRoleArn needed if not cross account connection
* @default - no peerRole needed if not cross account connection
*/
readonly peerRoleArn?: string;
readonly peerRole?: IRole;

/**
* The resource name of the peering connection.
Expand All @@ -202,6 +203,20 @@ export interface VPCPeeringConnectionOptions {
readonly vpcPeeringConnectionName?: string;
}

/**
* Attributes to import an existing VPC peering connection.
*/
export interface VpcPeeringConnectionAttributes {
/**
* The ID of the VPC peering connection.
*
* Must be a valid VPC peering connection ID starting with 'pcx-'.
*
* @example 'pcx-12345678'
*/
readonly vpcPeeringConnectionId: string;
}

/**
* Properties to define a VPC peering connection.
*/
Expand Down Expand Up @@ -509,6 +524,39 @@ export class NatGateway extends Resource implements IRouteTarget {
export class VPCPeeringConnection extends Resource implements IRouteTarget {
/** Uniquely identifies this class. */
public static readonly PROPERTY_INJECTION_ID: string = '@aws-cdk.aws-ec2-alpha.VPCPeeringConnection';

/**
* Import an existing VPC peering connection.
*
* @param scope The scope in which to define this construct
* @param id The scoped construct ID
* @param attrs The attributes of the VPC peering connection to import
* @returns An imported VPC peering connection that can be used as a route target
*/
public static fromAttributes(scope: Construct, id: string, attrs: VpcPeeringConnectionAttributes): IRouteTarget {
/**
* Internal class to allow users to import VPC peering connection
* @internal
*/
class ImportedVPCPeeringConnection extends Resource implements IRouteTarget {
public readonly routerType: RouterType = RouterType.VPC_PEERING_CONNECTION;
Comment on lines +541 to +542
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class ImportedVPCPeeringConnection extends Resource implements IRouteTarget {
public readonly routerType: RouterType = RouterType.VPC_PEERING_CONNECTION;
class ImportedVpcPeeringConnection extends Resource implements IRouteTarget {
public readonly routerType: RouterType = RouterType.VPC_PEERING_CONNECTION;

public readonly routerTargetId: string;

constructor(construct: Construct, constructId: string, vpcPeeringConnectionId: string) {
super(construct, constructId);

// Add validation
if (!vpcPeeringConnectionId || !vpcPeeringConnectionId.startsWith('pcx-')) {
throw new Error('VPC Peering Connection ID must be a valid ID starting with "pcx-"');
}

this.routerTargetId = vpcPeeringConnectionId;
}
}

return new ImportedVPCPeeringConnection(scope, id, attrs.vpcPeeringConnectionId);
}

/**
* The type of router used in the route.
*/
Expand All @@ -533,12 +581,12 @@ export class VPCPeeringConnection extends Resource implements IRouteTarget {

const isCrossAccount = props.requestorVpc.ownerAccountId !== props.acceptorVpc.ownerAccountId;

if (!isCrossAccount && props.peerRoleArn) {
throw new ValidationError('peerRoleArn is not needed for same account peering', this);
if (!isCrossAccount && props.peerRole) {
throw new ValidationError('peerRole is not needed for same account peering', this);
}

if (isCrossAccount && !props.peerRoleArn) {
throw new ValidationError('Cross account VPC peering requires peerRoleArn', this);
if (isCrossAccount && !props.peerRole) {
throw new ValidationError('Cross account VPC peering requires peerRole. Use createAcceptorVpcRole() or createRequestorPeerRole() to create the required role.', this);
}

const overlap = this.validateVpcCidrOverlap(props.requestorVpc, props.acceptorVpc);
Expand All @@ -554,7 +602,7 @@ export class VPCPeeringConnection extends Resource implements IRouteTarget {
peerVpcId: props.acceptorVpc.vpcId,
peerOwnerId: props.acceptorVpc.ownerAccountId,
peerRegion: props.acceptorVpc.region,
peerRoleArn: isCrossAccount ? props.peerRoleArn : undefined,
peerRoleArn: isCrossAccount ? props.peerRole?.roleArn : undefined,
});

this.routerTargetId = this.resource.attrId;
Expand Down
62 changes: 55 additions & 7 deletions packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export interface EgressOnlyInternetGatewayOptions {
/**
* Options to define InternetGateway for VPC
*/
export interface InternetGatewayOptions{
export interface InternetGatewayOptions {

/**
* Destination Ipv6 address for EGW route
Expand Down Expand Up @@ -400,9 +400,9 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
routeTableIds,
vpnGatewayId: this._vpnGatewayId,
});
// The AWS::EC2::VPNGatewayRoutePropagation resource cannot use the VPN gateway
// until it has successfully attached to the VPC.
// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpn-gatewayrouteprop.html
// The AWS::EC2::VPNGatewayRoutePropagation resource cannot use the VPN gateway
// until it has successfully attached to the VPC.
// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpn-gatewayrouteprop.html
routePropagation.node.addDependency(attachment);
}

Expand Down Expand Up @@ -481,7 +481,7 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
let useIpv6;
if (this.secondaryCidrBlock) {
useIpv6 = (this.secondaryCidrBlock.some((secondaryAddress) => secondaryAddress.amazonProvidedIpv6CidrBlock === true ||
secondaryAddress.ipv6IpamPoolId !== undefined || secondaryAddress.ipv6CidrBlock !== undefined));
secondaryAddress.ipv6IpamPoolId !== undefined || secondaryAddress.ipv6CidrBlock !== undefined));
}

if (!useIpv6) {
Expand Down Expand Up @@ -618,12 +618,16 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
}

/**
* Creates peering connection role for acceptor VPC
* Creates peering connection role for acceptor VPC.
*
* The role name will be auto-generated by CDK to ensure uniqueness.
*
* @param requestorAccountId The AWS account ID that will assume this role
* @returns The created IAM role that can be used for cross-account VPC peering
*/
public createAcceptorVpcRole(requestorAccountId: string): Role {
const peeringRole = new Role(this, 'VpcPeeringRole', {
assumedBy: new AccountPrincipal(requestorAccountId),
roleName: 'VpcPeeringRole',
description: 'Restrictive role for VPC peering',
});

Expand All @@ -647,6 +651,50 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
return peeringRole;
}

/**
* Creates peering connection role for requestor VPC.
*
* This role allows the acceptor account to create a VPC peering connection
* with this VPC. The role should be assumed by the acceptor account when
* initiating the peering connection.
*
* The role name will be auto-generated by CDK to ensure uniqueness.
*
* @param acceptorAccountId The AWS account ID that will assume this role
* @returns The created IAM role that can be used for cross-account VPC peering
*
* @example
* ```typescript
* const requestorRole = vpc.createRequestorPeerRole('123456789012');
* // Share this role ARN with the acceptor account
* ```
*/
public createRequestorPeerRole(acceptorAccountId: string): Role {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think requestor needs a role?

My prior suggestion was to create this kind of method to create a role from attributes, since well known role name was hard coded. But since you've removed the hard coded name, I don't think this is relevant.

const peeringRole = new Role(this, 'RequestorVpcPeeringRole', {
assumedBy: new AccountPrincipal(acceptorAccountId),
description: 'Restrictive role for VPC peering from requestor side',
});

peeringRole.addToPolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: ['ec2:CreateVpcPeeringConnection'],
resources: [`arn:${Aws.PARTITION}:ec2:${this.region}:${this.ownerAccountId}:vpc/${this.vpcId}`],
}));

peeringRole.addToPolicy(new PolicyStatement({
actions: ['ec2:CreateVpcPeeringConnection'],
effect: Effect.ALLOW,
resources: [`arn:${Aws.PARTITION}:ec2:${this.region}:${this.ownerAccountId}:vpc-peering-connection/*`],
conditions: {
StringEquals: {
'ec2:RequesterVpc': `arn:${Aws.PARTITION}:ec2:${this.region}:${this.ownerAccountId}:vpc/${this.vpcId}`,
},
},
}));

return peeringRole;
}

/**
* Creates a peering connection
*/
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading