Skip to content

Commit e150648

Browse files
jogoldElad Ben-Israel
authored and
Elad Ben-Israel
committed
feat(ec2): add support for vpn connections (#1899)
Add support for Site-to-Site VPN connections to VPC networks. When VPN connections are specified, a VPN gateway is automatically created and attached to the VPC. By default, routes are propagated on the route tables associated with the private subnets. Propagation to routes tables associated with public and/or isolated subnets is supported. Update VPC context provider to also import vpnGatewayId. References aws/jsii#231
1 parent ae8870d commit e150648

File tree

11 files changed

+1437
-5
lines changed

11 files changed

+1437
-5
lines changed

packages/@aws-cdk/aws-ec2/README.md

+38
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,41 @@ selectable by instantiating one of these classes:
303303
> section of your `cdk.json`.
304304
>
305305
> We will add command-line options to make this step easier in the future.
306+
307+
### VPN connections to a VPC
308+
309+
Create your VPC with VPN connections by specifying the `vpnConnections` props (keys are construct `id`s):
310+
311+
```ts
312+
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {
313+
vpnConnections: {
314+
dynamic: { // Dynamic routing (BGP)
315+
ip: '1.2.3.4'
316+
},
317+
static: { // Static routing
318+
ip: '4.5.6.7',
319+
staticRoutes: [
320+
'192.168.10.0/24',
321+
'192.168.20.0/24'
322+
]
323+
}
324+
}
325+
});
326+
```
327+
328+
To create a VPC that can accept VPN connections, set `vpnGateway` to `true`:
329+
330+
```ts
331+
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {
332+
vpnGateway: true
333+
});
334+
```
335+
336+
VPN connections can then be added:
337+
```ts
338+
vpc.addVpnConnection('Dynamic', {
339+
ip: '1.2.3.4'
340+
});
341+
```
342+
343+
Routes will be propagated on the route tables associated with the private subnets.

packages/@aws-cdk/aws-ec2/lib/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export * from './security-group-rule';
66
export * from './vpc';
77
export * from './vpc-ref';
88
export * from './vpc-network-provider';
9+
export * from './vpn';
910

1011
// AWS::EC2 CloudFormation Resources:
1112
export * from './ec2.generated';

packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts

+31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Construct, IConstruct, IDependable } from "@aws-cdk/cdk";
22
import { subnetName } from './util';
3+
import { VpnConnection, VpnConnectionOptions } from './vpn';
34

45
export interface IVpcSubnet extends IConstruct {
56
/**
@@ -54,6 +55,11 @@ export interface IVpcNetwork extends IConstruct {
5455
*/
5556
readonly vpcRegion: string;
5657

58+
/**
59+
* Identifier for the VPN gateway
60+
*/
61+
readonly vpnGatewayId?: string;
62+
5763
/**
5864
* Return the subnets appropriate for the placement strategy
5965
*/
@@ -68,6 +74,11 @@ export interface IVpcNetwork extends IConstruct {
6874
*/
6975
isPublicSubnet(subnet: IVpcSubnet): boolean;
7076

77+
/**
78+
* Adds a new VPN connection to this VPC
79+
*/
80+
addVpnConnection(id: string, options: VpnConnectionOptions): VpnConnection;
81+
7182
/**
7283
* Exports this VPC so it can be consumed by another stack.
7384
*/
@@ -173,6 +184,11 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork {
173184
*/
174185
public abstract readonly availabilityZones: string[];
175186

187+
/**
188+
* Identifier for the VPN gateway
189+
*/
190+
public abstract readonly vpnGatewayId?: string;
191+
176192
/**
177193
* Dependencies for internet connectivity
178194
*/
@@ -211,6 +227,16 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork {
211227
}[placement.subnetsToUse];
212228
}
213229

230+
/**
231+
* Adds a new VPN connection to this VPC
232+
*/
233+
public addVpnConnection(id: string, options: VpnConnectionOptions): VpnConnection {
234+
return new VpnConnection(this, id, {
235+
vpc: this,
236+
...options
237+
});
238+
}
239+
214240
/**
215241
* Export this VPC from the stack
216242
*/
@@ -291,6 +317,11 @@ export interface VpcNetworkImportProps {
291317
* Must be undefined or have a name for every isolated subnet group.
292318
*/
293319
isolatedSubnetNames?: string[];
320+
321+
/**
322+
* VPN gateway's identifier
323+
*/
324+
vpnGatewayId?: string;
294325
}
295326

296327
export interface VpcSubnetImportProps {

packages/@aws-cdk/aws-ec2/lib/vpc.ts

+84-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import cdk = require('@aws-cdk/cdk');
22
import { ConcreteDependable, IDependable } from '@aws-cdk/cdk';
3-
import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute } from './ec2.generated';
3+
import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnVPNGateway, CfnVPNGatewayRoutePropagation } from './ec2.generated';
44
import { CfnRouteTable, CfnSubnet, CfnSubnetRouteTableAssociation, CfnVPC, CfnVPCGatewayAttachment } from './ec2.generated';
55
import { NetworkBuilder } from './network-util';
66
import { DEFAULT_SUBNET_NAME, ExportSubnetGroup, ImportSubnetGroup, subnetId } from './util';
77
import { VpcNetworkProvider, VpcNetworkProviderProps } from './vpc-network-provider';
88
import { IVpcNetwork, IVpcSubnet, SubnetType, VpcNetworkBase, VpcNetworkImportProps, VpcPlacementStrategy, VpcSubnetImportProps } from './vpc-ref';
9+
import { VpnConnectionOptions, VpnConnectionType } from './vpn';
910

1011
/**
1112
* Name tag constant
@@ -115,6 +116,34 @@ export interface VpcNetworkProps {
115116
* private subnet per AZ
116117
*/
117118
subnetConfiguration?: SubnetConfiguration[];
119+
120+
/**
121+
* Indicates whether a VPN gateway should be created and attached to this VPC.
122+
*
123+
* @default true when vpnGatewayAsn or vpnConnections is specified.
124+
*/
125+
vpnGateway?: boolean;
126+
127+
/**
128+
* The private Autonomous System Number (ASN) for the VPN gateway.
129+
*
130+
* @default Amazon default ASN
131+
*/
132+
vpnGatewayAsn?: number;
133+
134+
/**
135+
* VPN connections to this VPC.
136+
*
137+
* @default no connections
138+
*/
139+
vpnConnections?: { [id: string]: VpnConnectionOptions }
140+
141+
/**
142+
* Where to propagate VPN routes.
143+
*
144+
* @default on the route tables associated with private subnets
145+
*/
146+
vpnRoutePropagation?: SubnetType[]
118147
}
119148

120149
/**
@@ -250,6 +279,11 @@ export class VpcNetwork extends VpcNetworkBase {
250279
*/
251280
public readonly availabilityZones: string[];
252281

282+
/**
283+
* Identifier for the VPN gateway
284+
*/
285+
public readonly vpnGatewayId?: string;
286+
253287
/**
254288
* The VPC resource
255289
*/
@@ -343,6 +377,51 @@ export class VpcNetwork extends VpcNetworkBase {
343377
privateSubnet.addDefaultNatRouteEntry(ngwId);
344378
});
345379
}
380+
381+
if ((props.vpnConnections || props.vpnGatewayAsn) && props.vpnGateway === false) {
382+
throw new Error('Cannot specify `vpnConnections` or `vpnGatewayAsn` when `vpnGateway` is set to false.');
383+
}
384+
385+
if (props.vpnGateway || props.vpnConnections || props.vpnGatewayAsn) {
386+
const vpnGateway = new CfnVPNGateway(this, 'VpnGateway', {
387+
amazonSideAsn: props.vpnGatewayAsn,
388+
type: VpnConnectionType.IPsec1
389+
});
390+
391+
const attachment = new CfnVPCGatewayAttachment(this, 'VPCVPNGW', {
392+
vpcId: this.vpcId,
393+
vpnGatewayId: vpnGateway.vpnGatewayName
394+
});
395+
396+
this.vpnGatewayId = vpnGateway.vpnGatewayName;
397+
398+
// Propagate routes on route tables associated with the right subnets
399+
const vpnRoutePropagation = props.vpnRoutePropagation || [SubnetType.Private];
400+
let subnets: IVpcSubnet[] = [];
401+
if (vpnRoutePropagation.includes(SubnetType.Public)) {
402+
subnets = [...subnets, ...this.publicSubnets];
403+
}
404+
if (vpnRoutePropagation.includes(SubnetType.Private)) {
405+
subnets = [...subnets, ...this.privateSubnets];
406+
}
407+
if (vpnRoutePropagation.includes(SubnetType.Isolated)) {
408+
subnets = [...subnets, ...this.isolatedSubnets];
409+
}
410+
const routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', {
411+
routeTableIds: (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId),
412+
vpnGatewayId: this.vpnGatewayId
413+
});
414+
415+
// The AWS::EC2::VPNGatewayRoutePropagation resource cannot use the VPN gateway
416+
// until it has successfully attached to the VPC.
417+
// See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpn-gatewayrouteprop.html
418+
routePropagation.node.addDependency(attachment);
419+
420+
const vpnConnections = props.vpnConnections || {};
421+
for (const [connectionId, connection] of Object.entries(vpnConnections)) {
422+
this.addVpnConnection(connectionId, connection);
423+
}
424+
}
346425
}
347426

348427
/**
@@ -355,6 +434,7 @@ export class VpcNetwork extends VpcNetworkBase {
355434

356435
return {
357436
vpcId: new cdk.Output(this, 'VpcId', { value: this.vpcId }).makeImportValue().toString(),
437+
vpnGatewayId: new cdk.Output(this, 'VpnGatewayId', { value: this.vpnGatewayId }).makeImportValue().toString(),
358438
availabilityZones: this.availabilityZones,
359439
publicSubnetIds: pub.ids,
360440
publicSubnetNames: pub.names,
@@ -523,7 +603,7 @@ export class VpcSubnet extends cdk.Construct implements IVpcSubnet {
523603
/**
524604
* The routeTableId attached to this subnet.
525605
*/
526-
private readonly routeTableId: string;
606+
public readonly routeTableId: string;
527607

528608
private readonly internetDependencies = new ConcreteDependable();
529609

@@ -653,12 +733,14 @@ class ImportedVpcNetwork extends VpcNetworkBase {
653733
public readonly privateSubnets: IVpcSubnet[];
654734
public readonly isolatedSubnets: IVpcSubnet[];
655735
public readonly availabilityZones: string[];
736+
public readonly vpnGatewayId?: string;
656737

657738
constructor(scope: cdk.Construct, id: string, private readonly props: VpcNetworkImportProps) {
658739
super(scope, id);
659740

660741
this.vpcId = props.vpcId;
661742
this.availabilityZones = props.availabilityZones;
743+
this.vpnGatewayId = props.vpnGatewayId;
662744

663745
// tslint:disable:max-line-length
664746
const pub = new ImportSubnetGroup(props.publicSubnetIds, props.publicSubnetNames, SubnetType.Public, this.availabilityZones, 'publicSubnetIds', 'publicSubnetNames');

0 commit comments

Comments
 (0)