Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vpcv2): vpc peering connection construct #31645

Merged
merged 46 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
af43218
initial skeleton code
Oct 2, 2024
16c8c3e
improve readability and update missing params
Oct 3, 2024
7ebbfe5
add test cases
Oct 3, 2024
9f60103
add integration test
Oct 3, 2024
7222bf4
additional test coverage
Oct 3, 2024
cc7c813
Merge branch 'main' into vpc_peering_connection_construct
1kaileychen Oct 3, 2024
d920b57
add vpc peering connection into readme
Oct 3, 2024
49780bf
Merge branch 'vpc_peering_connection_construct' of https://github.com…
Oct 3, 2024
65a0846
Merge branch 'main' into vpc_peering_connection_construct
1kaileychen Oct 3, 2024
b21736e
fix rosetta readme errors
Oct 4, 2024
41f0fb7
Merge branch 'vpc_peering_connection_construct' of https://github.com…
Oct 4, 2024
3baf408
Merge branch 'main' into vpc_peering_connection_construct
paulhcsun Oct 4, 2024
39f7072
move validateVpcCidrOverlap to utils file
Oct 7, 2024
2b4c5c6
initial design change implementation
Oct 10, 2024
0c3bc8b
add region & ownerAccountId to VpcV2Props
Oct 10, 2024
ac63088
remove old tests
Oct 10, 2024
9f96a92
error msg modifications & use ownerAccountId
Oct 10, 2024
57df8ed
update policies and warning
Oct 11, 2024
5299751
add tests and integration tests
Oct 11, 2024
0143dbc
add readme
Oct 11, 2024
1c0f389
Merge remote-tracking branch 'origin/main' into vpc_peering_connectio…
Oct 11, 2024
d31b6ca
fix lint fail due to ordering in readme
Oct 11, 2024
751530c
update readme errors
Oct 11, 2024
98d39d2
update readme peering connection class to function
Oct 11, 2024
49e2e4f
add createPeeringConnection test
Oct 15, 2024
b8fc4a0
change warning to error when peerRoleArn is defined for same account
Oct 16, 2024
e810bd7
update description for peerRoleArn
Oct 16, 2024
b926d86
add clarity in readme different types of peering
Oct 16, 2024
9ff9da6
return integ.route-v2 to original test case
Oct 16, 2024
b13d363
integration test for peering connection
Oct 16, 2024
6607192
Apply suggestions from code review
shikha372 Oct 16, 2024
2f78a5a
Update wording suggestion
1kaileychen Oct 17, 2024
5773a08
remove region and ownerAccountId from VpcV2Props
Oct 17, 2024
8e3ff5f
update test cases and readme
Oct 17, 2024
9022843
Merge branch 'vpc_peering_connection_construct' of https://github.com…
Oct 17, 2024
951abf0
update account ids that are allowed in readme and tests
Oct 17, 2024
999f4de
Merge branch 'main' into vpc_peering_connection_construct
shikha372 Nov 12, 2024
7cd8009
fixing integ test for cross account
shikha372 Nov 14, 2024
9ceb78d
fixing account id in test snapshot
shikha372 Nov 15, 2024
3b0f7d5
Merge branch 'main' into vpc_peering_connection_construct
shikha372 Nov 15, 2024
c0949b5
adding unit test fixes
shikha372 Nov 18, 2024
ade37c0
Merge branch 'main' into vpc_peering_connection_construct
shikha372 Nov 18, 2024
882a0b5
fixing Readme
shikha372 Nov 19, 2024
cc723b9
Merge branch 'main' into vpc_peering_connection_construct
shikha372 Nov 19, 2024
2a8f8b6
modifying ReadMe
shikha372 Nov 19, 2024
b892886
Merge branch 'main' into vpc_peering_connection_construct
shikha372 Nov 21, 2024
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
36 changes: 36 additions & 0 deletions packages/@aws-cdk/aws-ec2-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,42 @@ new Route(this, 'DynamoDBRoute', {
});
```

## VPC Peering Connection

VPC peering connection allows you to connect two VPCs and route traffic between them using private IP addresses. The VpcV2 construct supports creating VPC peering connections through the `VPCPeeringConnection` construct from the `route` module.
shikha372 marked this conversation as resolved.
Show resolved Hide resolved

1kaileychen marked this conversation as resolved.
Show resolved Hide resolved
Here's an example of how to create a VPC peering connection between two VPCs:
<!--
```ts
const stack = new Stack();

const vpcA = new VpcV2(this, 'VpcA', {
primaryAddressBlock: IpAddresses.ipv4('10.0.0.0/16'),
});

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

const peeringConnection = new VPCPeeringConnection(this, 'crossAccountCrossRegionPeering', {
isCrossAccount: true,
requestorVpc: vpcA,
acceptorVpc: vpcB,
acceptorAccountId: '123456789012',
acceptorRegion: 'us-west-2',
});
1kaileychen marked this conversation as resolved.
Show resolved Hide resolved

const routeTable = new RouteTable(this, 'RouteTable', {
vpc: vpcA,
});

routeTable.addRoute('vpcPeeringRoute', '10.0.0.0/16', { gateway: peeringConnection });
``` -->

Note that for cross-account peering, you'll need to ensure that the peering request is accepted in the peer account. For more information see [Accept or reject a VPC peering connection](https://docs.aws.amazon.com/vpc/latest/peering/accept-vpc-peering-connection.html).

To add routes for the peering connection to specific subnets, you can use the addRoute method of the RouteTable construct.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this section is not required .. or the question is, if the customer removed the peering connection from the CDK application, I am assuming that the peer connection will be deleted is it correct?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it was added to address this comment #31645 (comment), but I agree we can remove it as peering connection gets deleted if a corresponding CFN stack is deleted.

## Adding Egress-Only Internet Gateway to VPC

An egress-only internet gateway is a horizontally scaled, redundant, and highly available VPC component that allows outbound communication over IPv6 from instances in your VPC to the internet, and prevents the internet from initiating an IPv6 connection with your instances.
Expand Down
142 changes: 138 additions & 4 deletions packages/@aws-cdk/aws-ec2-alpha/lib/route.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CfnEIP, CfnEgressOnlyInternetGateway, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnRouteTable, CfnVPCGatewayAttachment, CfnVPNGateway, CfnVPNGatewayRoutePropagation, GatewayVpcEndpoint, IRouteTable, IVpcEndpoint, RouterType } from 'aws-cdk-lib/aws-ec2';
import { CfnEIP, CfnEgressOnlyInternetGateway, CfnInternetGateway, CfnNatGateway, CfnVPCPeeringConnection, CfnRoute, CfnRouteTable, CfnVPCGatewayAttachment, CfnVPNGateway, CfnVPNGatewayRoutePropagation, GatewayVpcEndpoint, IRouteTable, IVpcEndpoint, RouterType } from 'aws-cdk-lib/aws-ec2';
import { Construct, IDependable } from 'constructs';
import { Annotations, Duration, IResource, Resource } from 'aws-cdk-lib/core';
import { Annotations, Duration, IResource, Resource, Stack } from 'aws-cdk-lib/core';
import { IVpcV2, VPNGatewayV2Options } from './vpc-v2-base';
import { NetworkUtils, allRouteTableIds } from './util';
import { NetworkUtils, allRouteTableIds, CidrBlock } from './util';
import { ISubnetV2 } from './subnet-v2';

/**
Expand Down Expand Up @@ -175,6 +175,40 @@ export interface NatGatewayProps extends NatGatewayOptions {
readonly vpc?: IVpcV2;
}

/**
* Options to define a VPC peering connection.
*/
export interface VPCPeeringConnectionOptions {
/**
* The VPC that is accepting the peering connection.
*/
readonly acceptorVpc: IVpcV2;

/**
* The AWS account ID of the acceptor VPC owner.
*
1kaileychen marked this conversation as resolved.
Show resolved Hide resolved
* @default - no roleArn needed if not cross account connection
*/
readonly peerRoleArn?: string;

/**
* The resource name of the peering connection.
*
* @default - peering connection provisioned without any name
*/
readonly vpcPeeringConnectionName?: string;
}

/**
* Properties to define a VPC peering connection.
*/
export interface VPCPeeringConnectionProps extends VPCPeeringConnectionOptions {
/**
* The VPC that is requesting the peering connection.
*/
readonly requestorVpc: IVpcV2;
}

/**
* Creates an egress-only internet gateway
* @resource AWS::EC2::EgressOnlyInternetGateway
Expand Down Expand Up @@ -402,6 +436,106 @@ export class NatGateway extends Resource implements IRouteTarget {
}
}

/**
* Creates a peering connection between two VPCs
* @resource AWS::EC2::VPCPeeringConnection
*/
export class VPCPeeringConnection extends Resource implements IRouteTarget {

/**
* The type of router used in the route.
*/
readonly routerType: RouterType;

1kaileychen marked this conversation as resolved.
Show resolved Hide resolved
/**
* The ID of the route target.
*/
readonly routerTargetId: string;

1kaileychen marked this conversation as resolved.
Show resolved Hide resolved
/**
* The VPC peering connection CFN resource.
*/
public readonly resource: CfnVPCPeeringConnection;

constructor(scope: Construct, id: string, props: VPCPeeringConnectionProps) {
super(scope, id);

this.routerType = RouterType.VPC_PEERING_CONNECTION;

const isCrossAccount = Stack.of(props.requestorVpc).account !== Stack.of(props.acceptorVpc).account;

1kaileychen marked this conversation as resolved.
Show resolved Hide resolved
if (!isCrossAccount && props.peerRoleArn) {
Annotations.of(this).addWarning(
'This is a same account peering, peerRoleArn is not needed and will be ignored',
);
}

if (isCrossAccount && !props.peerRoleArn) {
throw new Error('Cross account VPC peering requires a peerArnId');
}
shikha372 marked this conversation as resolved.
Show resolved Hide resolved

// TODO: do we still want to validate? already have this check in the vpc peering
const overlap = this.validateVpcCidrOverlap(props.requestorVpc, props.acceptorVpc);
if (overlap) {
throw new Error('CIDR block should not overlap with existing subnet blocks');
}
shikha372 marked this conversation as resolved.
Show resolved Hide resolved

this.resource = new CfnVPCPeeringConnection(this, 'VPCPeeringConnection', {
vpcId: props.requestorVpc.vpcId,
peerVpcId: props.acceptorVpc.vpcId,
peerOwnerId: props.acceptorVpc.ownerAccountId,
peerRegion: props.acceptorVpc.region,
peerRoleArn: isCrossAccount ? props.peerRoleArn : undefined,
});

this.routerTargetId = this.resource.attrId;
this.node.defaultChild = this.resource;
}

/**
* Validates if the provided IPv4 CIDR block overlaps with existing subnet CIDR blocks within the given VPC.
*
* @param requestorVpc The VPC of the requestor.
* @param acceptorVpc The VPC of the acceptor.
* @returns True if the IPv4 CIDR block overlaps with existing subnet CIDR blocks, false otherwise.
* @internal
1kaileychen marked this conversation as resolved.
Show resolved Hide resolved
*/
private validateVpcCidrOverlap(requestorVpc: IVpcV2, acceptorVpc: IVpcV2): boolean {

const requestorCidrs = [requestorVpc.ipv4CidrBlock];
const acceptorCidrs = [acceptorVpc.ipv4CidrBlock];

if (requestorVpc.secondaryCidrBlock) {
requestorCidrs.push(...requestorVpc.secondaryCidrBlock
.map(block => block.cidrBlock)
.filter((cidr): cidr is string => cidr !== undefined));
}

if (acceptorVpc.secondaryCidrBlock) {
acceptorCidrs.push(...acceptorVpc.secondaryCidrBlock
.map(block => block.cidrBlock)
.filter((cidr): cidr is string => cidr !== undefined));
}

for (const requestorCidr of requestorCidrs) {
const requestorRange = new CidrBlock(requestorCidr);
const requestorIpRange: [string, string] = [requestorRange.minIp(), requestorRange.maxIp()];

for (const acceptorCidr of acceptorCidrs) {
const acceptorRange = new CidrBlock(acceptorCidr);
const acceptorIpRange: [string, string] = [acceptorRange.minIp(), acceptorRange.maxIp()];

if (requestorRange.rangesOverlap(acceptorIpRange, requestorIpRange)) {
return true;
}
}
}

return false;
}

}

/**
* The type of endpoint or gateway being targeted by the route.
*/
Expand Down Expand Up @@ -534,7 +668,7 @@ export class Route extends Resource implements IRouteV2 {
/**
* The type of router the route is targetting
*/
public readonly targetRouterType: RouterType
public readonly targetRouterType: RouterType;

/**
* The route CFN resource.
Expand Down
3 changes: 1 addition & 2 deletions packages/@aws-cdk/aws-ec2-alpha/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,5 +378,4 @@ export class CidrBlockIpv6 {
}
return ipv6Number;
}
}

}
81 changes: 79 additions & 2 deletions packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2-base.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Resource, Annotations } from 'aws-cdk-lib';
import { Aws, Resource, Annotations } from 'aws-cdk-lib';
import { IVpc, ISubnet, SubnetSelection, SelectedSubnets, EnableVpnGatewayOptions, VpnGateway, VpnConnectionType, CfnVPCGatewayAttachment, CfnVPNGatewayRoutePropagation, VpnConnectionOptions, VpnConnection, ClientVpnEndpointOptions, ClientVpnEndpoint, InterfaceVpcEndpointOptions, InterfaceVpcEndpoint, GatewayVpcEndpointOptions, GatewayVpcEndpoint, FlowLogOptions, FlowLog, FlowLogResourceType, SubnetType, SubnetFilter, CfnVPCCidrBlock } from 'aws-cdk-lib/aws-ec2';
import { allRouteTableIds, flatten, subnetGroupNameFromConstructId } from './util';
import { IDependable, Dependable, IConstruct, DependencyGroup } from 'constructs';
import { EgressOnlyInternetGateway, InternetGateway, NatConnectivityType, NatGateway, NatGatewayOptions, Route, VPNGatewayV2 } from './route';
import { EgressOnlyInternetGateway, InternetGateway, NatConnectivityType, NatGateway, NatGatewayOptions, Route, VPCPeeringConnection, VPCPeeringConnectionOptions, VPNGatewayV2 } from './route';
import { ISubnetV2 } from './subnet-v2';
import { AccountPrincipal, Effect, PolicyStatement, Role } from 'aws-cdk-lib/aws-iam';

/**
* Options to define EgressOnlyInternetGateway for VPC
Expand Down Expand Up @@ -96,6 +97,20 @@ export interface IVpcV2 extends IVpc {
*/
readonly ipv4CidrBlock: string;

/**
* Optional to override inferred region
*
* @default - current stack's environment region
*/
readonly region?: string;

/**
* The ID of the AWS account that owns the VPC
*
* @default - the account id of the parent stack
*/
readonly ownerAccountId?: string;

/**
* Add an Egress only Internet Gateway to current VPC.
* Can only be used for ipv6 enabled VPCs.
Expand Down Expand Up @@ -128,6 +143,19 @@ export interface IVpcV2 extends IVpc {
*/
addNatGateway(options: NatGatewayOptions): NatGateway;

/**
* Adds a new role to VPC account
* A cross account role is required for the VPC to peer with another account.
* For more information, see the {@link https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/peer-with-vpc-in-another-account.html}.
*/
createAcceptorVpcRole(requestorAccountId: string, acceptorAccountId: string, acceptorVpcRegion: string): Role;

/**
* Creates a new peering connection
* A peering connection is a private virtual network established between two VPCs.
* For more information, see the {@link https://docs.aws.amazon.com/vpc/latest/peering/what-is-vpc-peering.html}.
*/
createPeeringConnection(id: string, options: VPCPeeringConnectionOptions): VPCPeeringConnection;
}

/**
Expand Down Expand Up @@ -167,6 +195,16 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
*/
public abstract readonly isolatedSubnets: ISubnet[];

/**
* Region for this VPC
*/
public abstract readonly region?: string;

/**
* Identifier of the owner for this VPC
*/
public abstract readonly ownerAccountId?: string;

/**
* AZs for this VPC
*/
Expand Down Expand Up @@ -445,6 +483,45 @@ export abstract class VpcV2Base extends Resource implements IVpcV2 {
});
}

/**
* Creates peering connection role for acceptor VPC
*/
public createAcceptorVpcRole(requestorAccountId: string, acceptorAccountId: string, acceptorVpcRegion: string): Role {
const peeringRole = new Role(this, 'VpcPeeringRole', {
assumedBy: new AccountPrincipal(requestorAccountId),
description: 'Restrictive role for VPC peering',
});

peeringRole.addToPolicy(new PolicyStatement({
effect: Effect.ALLOW,
actions: ['ec2:AcceptVpcPeeringConnection'],
resources: [`arn:${Aws.PARTITION}:ec2:${acceptorVpcRegion}:${requestorAccountId}:vpc/${acceptorAccountId}`],
}));

peeringRole.addToPolicy(new PolicyStatement({
actions: ['ec2:AcceptVpcPeeringConnection'],
effect: Effect.ALLOW,
resources: [`arn:${Aws.PARTITION}:ec2:${acceptorVpcRegion}:${requestorAccountId}:vpc-peering-connection/*`],
conditions: {
StringEquals: {
'ec2:AccepterVpc': `arn:${Aws.PARTITION}:ec2:${acceptorVpcRegion}:${requestorAccountId}:vpc/${acceptorAccountId}`,
},
},
}));

return peeringRole;
}

/**
* Creates a peering connection
*/
public createPeeringConnection(id: string, options: VPCPeeringConnectionOptions): VPCPeeringConnection {
return new VPCPeeringConnection(this, id, {
requestorVpc: this,
...options,
});
}

/**
* Returns the id of the VPN Gateway (if enabled)
*/
Expand Down
28 changes: 27 additions & 1 deletion packages/@aws-cdk/aws-ec2-alpha/lib/vpc-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,20 @@ export interface VpcV2Props {
* @default - autogenerated by CDK
*/
readonly vpcName?: string;

/**
* Optional to override inferred region
*
* @default - current stack's environment region
*/
readonly region?: string;

/**
* The ID of the AWS account that owns the VPC
*
* @default - the account id of the parent stack
*/
readonly ownerAccountId?: string;
1kaileychen marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -243,10 +257,20 @@ export class VpcV2 extends VpcV2Base {
public readonly publicSubnets: ISubnet[];

/**
* Pbulic Subnets that are part of this VPC.
* Public Subnets that are part of this VPC.
*/
public readonly privateSubnets: ISubnet[];

/**
* Region for this VPC
*/
public readonly region?: string;

/**
* Identifier of the owner for this VPC
*/
public readonly ownerAccountId?: string;

/**
* To define dependency on internet connectivity
*/
Expand Down Expand Up @@ -302,6 +326,8 @@ export class VpcV2 extends VpcV2Base {
resource: 'vpc',
resourceName: this.vpcId,
}, this.stack);
this.region = props.region ?? this.stack.region;
this.ownerAccountId = props.ownerAccountId ?? this.stack.account;

if (props.secondaryAddressBlocks) {
const secondaryAddressBlocks: IIpAddresses[] = props.secondaryAddressBlocks;
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ec2-alpha/rosetta/default.ts-fixture
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Fixture with packages imported, but nothing else
import { Construct } from 'constructs';
import { Stack, App, Fn } from 'aws-cdk-lib';
import { VpcV2, SubnetV2, IpAddresses, IpamPoolPublicIpSource, RouteTable, InternetGateway, Route, NatGateway, EgressOnlyInternetGateway } from '@aws-cdk/aws-ec2-alpha';
import { VpcV2, SubnetV2, IpAddresses, IpamPoolPublicIpSource, RouteTable, InternetGateway, Route, NatGateway, EgressOnlyInternetGateway, VPCPeeringConnection } from '@aws-cdk/aws-ec2-alpha';
import { Ipam, AwsServiceName, IpCidr, AddressFamily } from '@aws-cdk/aws-ec2-alpha';
import { NatConnectivityType } from '@aws-cdk/aws-ec2-alpha';
import { SubnetType, VpnConnectionType } from 'aws-cdk-lib/aws-ec2';
Expand Down
Loading
Loading