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(ec2): add support for vpn connections #1899

Merged
merged 14 commits into from
Mar 4, 2019
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