-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Changes from 5 commits
45e69c7
5312496
e95b7ff
766c882
0692784
657ca96
b0cf686
a589734
3aaa1fe
b456cc4
8d30a7f
c77f614
cbf0235
bddf05f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
@@ -115,6 +116,27 @@ 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 } | ||
} | ||
|
||
/** | ||
|
@@ -250,6 +272,11 @@ export class VpcNetwork extends VpcNetworkBase { | |
*/ | ||
public readonly availabilityZones: string[]; | ||
|
||
/** | ||
* Identifier for the VPN gateway | ||
*/ | ||
public readonly vpnGatewayId?: string; | ||
|
||
/** | ||
* The VPC resource | ||
*/ | ||
|
@@ -343,6 +370,40 @@ export class VpcNetwork extends VpcNetworkBase { | |
privateSubnet.addDefaultNatRouteEntry(ngwId); | ||
}); | ||
} | ||
|
||
if (props.vpnConnections && props.vpnGateway === false) { | ||
throw new Error('Cannot specify `vpnConnections` 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 private subnets | ||
const routePropagation = new CfnVPNGatewayRoutePropagation(this, 'RoutePropagation', { | ||
routeTableIds: (this.privateSubnets as VpcPrivateSubnet[]).map(subnet => subnet.routeTableId), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should consider allowing an option to allow for subnetgroups to be chosen by the user of this to decide what to propogate - there are reasons you might want to route public (and isolated) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, on it. |
||
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 || {}; | ||
Object.keys(vpnConnections).forEach(cId => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Style: I prefer: for (const [ id, vpn ] of Object.entries(vpnConnections)) {
// ...
} |
||
this.addVpnConnection(cId, vpnConnections[cId]); | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
|
@@ -355,6 +416,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, | ||
|
@@ -523,7 +585,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(); | ||
|
||
|
@@ -653,12 +715,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'); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import cdk = require('@aws-cdk/cdk'); | ||
import { CfnCustomerGateway, CfnVPNConnection, CfnVPNConnectionRoute } from './ec2.generated'; | ||
import { IVpcNetwork } from './vpc-ref'; | ||
|
||
export interface IVpnConnection extends cdk.IConstruct { | ||
/** | ||
* The id of the VPN connection. | ||
*/ | ||
readonly vpnId: string; | ||
|
||
/** | ||
* The id of the customer gateway. | ||
*/ | ||
readonly customerGatewayId: string; | ||
|
||
/** | ||
* The ip address of the customer gateway. | ||
*/ | ||
readonly customerGatewayIp: string; | ||
|
||
/** | ||
* The ASN of the customer gateway. | ||
*/ | ||
readonly customerGatewayAsn: number; | ||
} | ||
export interface VpnTunnelOption { | ||
eladb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/** | ||
* The pre-shared key (PSK) to establish initial authentication between the virtual | ||
* private gateway and customer gateway. | ||
*/ | ||
presharedKey: string; | ||
|
||
/** | ||
* The range of inside IP addresses for the tunnel. Any specified CIDR blocks must be | ||
* unique across all VPN connections that use the same virtual private gateway. | ||
*/ | ||
tunnelInsideCidr: string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both things here are independently optional https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-vpnconnection-vpntunneloptionsspecification.html and so one might want to set one and not the other. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will fix. |
||
} | ||
|
||
export interface VpnConnectionOptions { | ||
/** | ||
* The ip address of the customer gateway. | ||
*/ | ||
ip: string; | ||
|
||
/** | ||
* The ASN of the customer gateway. | ||
* | ||
* @default 65000 | ||
*/ | ||
asn?: number; | ||
|
||
/** | ||
* The static routes to be routed from the VPN gateway to the customer gateway. | ||
* | ||
* @default Dynamic routing (BGP) | ||
*/ | ||
staticRoutes?: string[]; | ||
|
||
/** | ||
* Tunnel options for the VPN connection. | ||
*/ | ||
vpnTunnelOptions?: VpnTunnelOption[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tests don't appear to cover this. |
||
} | ||
|
||
export interface VpnConnectionProps extends VpnConnectionOptions { | ||
/** | ||
* The VPC to connect to. | ||
*/ | ||
vpc: IVpcNetwork; | ||
} | ||
|
||
/** | ||
* The VPN connection type. | ||
*/ | ||
export enum VpnConnectionType { | ||
/** | ||
* The IPsec 1 VPN connection type. | ||
*/ | ||
IPsec1 = 'ipsec.1' | ||
} | ||
|
||
export class VpnConnection extends cdk.Construct implements IVpnConnection { | ||
public readonly vpnId: string; | ||
public readonly customerGatewayId: string; | ||
public readonly customerGatewayIp: string; | ||
public readonly customerGatewayAsn: number; | ||
|
||
constructor(scope: cdk.Construct, id: string, props: VpnConnectionProps) { | ||
super(scope, id); | ||
|
||
if (!props.vpc.vpnGatewayId) { | ||
throw new Error('Cannot create a VPN connection when VPC has no VPN gateway.'); | ||
} | ||
|
||
const type = VpnConnectionType.IPsec1; | ||
const bgpAsn = props.asn || 65000; | ||
|
||
const customerGateway = new CfnCustomerGateway(this, 'CustomerGateway', { | ||
bgpAsn, | ||
ipAddress: props.ip, | ||
type | ||
}); | ||
|
||
this.customerGatewayId = customerGateway.customerGatewayName; | ||
this.customerGatewayAsn = bgpAsn; | ||
this.customerGatewayIp = props.ip; | ||
|
||
const vpnConnection = new CfnVPNConnection(this, 'Resource', { | ||
type, | ||
customerGatewayId: customerGateway.customerGatewayName, | ||
staticRoutesOnly: props.staticRoutes ? true : false, | ||
vpnGatewayId: props.vpc.vpnGatewayId, | ||
vpnTunnelOptionsSpecifications: props.vpnTunnelOptions | ||
}); | ||
|
||
this.vpnId = vpnConnection.vpnConnectionName; | ||
|
||
if (props.staticRoutes) { | ||
props.staticRoutes.forEach(route => { | ||
new CfnVPNConnectionRoute(this, `Route${route.replace(/[^\d]/g, '')}`, { | ||
destinationCidrBlock: route, | ||
vpnConnectionId: this.vpnId | ||
}); | ||
}); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"export"?