Skip to content

Commit

Permalink
feat(ec2): add support for vpn connections (#1899)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
jogold authored and Elad Ben-Israel committed Mar 4, 2019
1 parent ae8870d commit e150648
Show file tree
Hide file tree
Showing 11 changed files with 1,437 additions and 5 deletions.
38 changes: 38 additions & 0 deletions packages/@aws-cdk/aws-ec2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,41 @@ selectable by instantiating one of these classes:
> section of your `cdk.json`.
>
> We will add command-line options to make this step easier in the future.
### VPN connections to a VPC

Create your VPC with VPN connections by specifying the `vpnConnections` props (keys are construct `id`s):

```ts
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {
vpnConnections: {
dynamic: { // Dynamic routing (BGP)
ip: '1.2.3.4'
},
static: { // Static routing
ip: '4.5.6.7',
staticRoutes: [
'192.168.10.0/24',
'192.168.20.0/24'
]
}
}
});
```

To create a VPC that can accept VPN connections, set `vpnGateway` to `true`:

```ts
const vpc = new ec2.VpcNetwork(stack, 'MyVpc', {
vpnGateway: true
});
```

VPN connections can then be added:
```ts
vpc.addVpnConnection('Dynamic', {
ip: '1.2.3.4'
});
```

Routes will be propagated on the route tables associated with the private subnets.
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-ec2/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './security-group-rule';
export * from './vpc';
export * from './vpc-ref';
export * from './vpc-network-provider';
export * from './vpn';

// AWS::EC2 CloudFormation Resources:
export * from './ec2.generated';
31 changes: 31 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/vpc-ref.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Construct, IConstruct, IDependable } from "@aws-cdk/cdk";
import { subnetName } from './util';
import { VpnConnection, VpnConnectionOptions } from './vpn';

export interface IVpcSubnet extends IConstruct {
/**
Expand Down Expand Up @@ -54,6 +55,11 @@ export interface IVpcNetwork extends IConstruct {
*/
readonly vpcRegion: string;

/**
* Identifier for the VPN gateway
*/
readonly vpnGatewayId?: string;

/**
* Return the subnets appropriate for the placement strategy
*/
Expand All @@ -68,6 +74,11 @@ export interface IVpcNetwork extends IConstruct {
*/
isPublicSubnet(subnet: IVpcSubnet): boolean;

/**
* Adds a new VPN connection to this VPC
*/
addVpnConnection(id: string, options: VpnConnectionOptions): VpnConnection;

/**
* Exports this VPC so it can be consumed by another stack.
*/
Expand Down Expand Up @@ -173,6 +184,11 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork {
*/
public abstract readonly availabilityZones: string[];

/**
* Identifier for the VPN gateway
*/
public abstract readonly vpnGatewayId?: string;

/**
* Dependencies for internet connectivity
*/
Expand Down Expand Up @@ -211,6 +227,16 @@ export abstract class VpcNetworkBase extends Construct implements IVpcNetwork {
}[placement.subnetsToUse];
}

/**
* Adds a new VPN connection to this VPC
*/
public addVpnConnection(id: string, options: VpnConnectionOptions): VpnConnection {
return new VpnConnection(this, id, {
vpc: this,
...options
});
}

/**
* Export this VPC from the stack
*/
Expand Down Expand Up @@ -291,6 +317,11 @@ export interface VpcNetworkImportProps {
* Must be undefined or have a name for every isolated subnet group.
*/
isolatedSubnetNames?: string[];

/**
* VPN gateway's identifier
*/
vpnGatewayId?: string;
}

export interface VpcSubnetImportProps {
Expand Down
86 changes: 84 additions & 2 deletions packages/@aws-cdk/aws-ec2/lib/vpc.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import cdk = require('@aws-cdk/cdk');
import { ConcreteDependable, IDependable } from '@aws-cdk/cdk';
import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute } from './ec2.generated';
import { CfnEIP, CfnInternetGateway, CfnNatGateway, CfnRoute, CfnVPNGateway, CfnVPNGatewayRoutePropagation } from './ec2.generated';
import { CfnRouteTable, CfnSubnet, CfnSubnetRouteTableAssociation, CfnVPC, CfnVPCGatewayAttachment } from './ec2.generated';
import { NetworkBuilder } from './network-util';
import { DEFAULT_SUBNET_NAME, ExportSubnetGroup, ImportSubnetGroup, subnetId } from './util';
import { VpcNetworkProvider, VpcNetworkProviderProps } from './vpc-network-provider';
import { IVpcNetwork, IVpcSubnet, SubnetType, VpcNetworkBase, VpcNetworkImportProps, VpcPlacementStrategy, VpcSubnetImportProps } from './vpc-ref';
import { VpnConnectionOptions, VpnConnectionType } from './vpn';

/**
* Name tag constant
Expand Down Expand Up @@ -115,6 +116,34 @@ export interface VpcNetworkProps {
* private subnet per AZ
*/
subnetConfiguration?: SubnetConfiguration[];

/**
* Indicates whether a VPN gateway should be created and attached to this VPC.
*
* @default true when vpnGatewayAsn or vpnConnections is specified.
*/
vpnGateway?: boolean;

/**
* The private Autonomous System Number (ASN) for the VPN gateway.
*
* @default Amazon default ASN
*/
vpnGatewayAsn?: number;

/**
* VPN connections to this VPC.
*
* @default no connections
*/
vpnConnections?: { [id: string]: VpnConnectionOptions }

/**
* Where to propagate VPN routes.
*
* @default on the route tables associated with private subnets
*/
vpnRoutePropagation?: SubnetType[]
}

/**
Expand Down Expand Up @@ -250,6 +279,11 @@ export class VpcNetwork extends VpcNetworkBase {
*/
public readonly availabilityZones: string[];

/**
* Identifier for the VPN gateway
*/
public readonly vpnGatewayId?: string;

/**
* The VPC resource
*/
Expand Down Expand Up @@ -343,6 +377,51 @@ export class VpcNetwork extends VpcNetworkBase {
privateSubnet.addDefaultNatRouteEntry(ngwId);
});
}

if ((props.vpnConnections || props.vpnGatewayAsn) && props.vpnGateway === false) {
throw new Error('Cannot specify `vpnConnections` or `vpnGatewayAsn` when `vpnGateway` is set to false.');
}

if (props.vpnGateway || props.vpnConnections || props.vpnGatewayAsn) {
const vpnGateway = new CfnVPNGateway(this, 'VpnGateway', {
amazonSideAsn: props.vpnGatewayAsn,
type: VpnConnectionType.IPsec1
});

const attachment = new CfnVPCGatewayAttachment(this, 'VPCVPNGW', {
vpcId: this.vpcId,
vpnGatewayId: vpnGateway.vpnGatewayName
});

this.vpnGatewayId = vpnGateway.vpnGatewayName;

// Propagate routes on route tables associated with the right subnets
const vpnRoutePropagation = props.vpnRoutePropagation || [SubnetType.Private];
let subnets: IVpcSubnet[] = [];
if (vpnRoutePropagation.includes(SubnetType.Public)) {
subnets = [...subnets, ...this.publicSubnets];
}
if (vpnRoutePropagation.includes(SubnetType.Private)) {
subnets = [...subnets, ...this.privateSubnets];
}
if (vpnRoutePropagation.includes(SubnetType.Isolated)) {
subnets = [...subnets, ...this.isolatedSubnets];
}
const routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', {
routeTableIds: (subnets as VpcSubnet[]).map(subnet => subnet.routeTableId),
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
routePropagation.node.addDependency(attachment);

const vpnConnections = props.vpnConnections || {};
for (const [connectionId, connection] of Object.entries(vpnConnections)) {
this.addVpnConnection(connectionId, connection);
}
}
}

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

return {
vpcId: new cdk.Output(this, 'VpcId', { value: this.vpcId }).makeImportValue().toString(),
vpnGatewayId: new cdk.Output(this, 'VpnGatewayId', { value: this.vpnGatewayId }).makeImportValue().toString(),
availabilityZones: this.availabilityZones,
publicSubnetIds: pub.ids,
publicSubnetNames: pub.names,
Expand Down Expand Up @@ -523,7 +603,7 @@ export class VpcSubnet extends cdk.Construct implements IVpcSubnet {
/**
* The routeTableId attached to this subnet.
*/
private readonly routeTableId: string;
public readonly routeTableId: string;

private readonly internetDependencies = new ConcreteDependable();

Expand Down Expand Up @@ -653,12 +733,14 @@ class ImportedVpcNetwork extends VpcNetworkBase {
public readonly privateSubnets: IVpcSubnet[];
public readonly isolatedSubnets: IVpcSubnet[];
public readonly availabilityZones: string[];
public readonly vpnGatewayId?: string;

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

this.vpcId = props.vpcId;
this.availabilityZones = props.availabilityZones;
this.vpnGatewayId = props.vpnGatewayId;

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

0 comments on commit e150648

Please sign in to comment.