Skip to content

Commit

Permalink
feat(ec2): access a vpc's internet gateway (#7939)
Browse files Browse the repository at this point in the history
This commit introduces a new `internetGateway` attribute to the VPC
construct to allow for creative routing using the default IGW added
with when using a public subnet.

Resolves #5327

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
shearn89 committed Jul 19, 2020
1 parent 566f3b3 commit cb5dad8
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 1 deletion.
45 changes: 45 additions & 0 deletions packages/@aws-cdk/aws-ec2/README.md
Expand Up @@ -252,6 +252,51 @@ DatabaseSubnet1 |`ISOLATED`|`10.0.6.0/28` |#1|Only routes within the VPC
DatabaseSubnet2 |`ISOLATED`|`10.0.6.16/28`|#2|Only routes within the VPC
DatabaseSubnet3 |`ISOLATED`|`10.0.6.32/28`|#3|Only routes within the VPC

### Accessing the Internet Gateway

If you need access to the internet gateway, you can get it's ID like so:

```ts
const igwId = vpc.internetGatewayId;
```

For a VPC with only `ISOLATED` subnets, this value will be undefined.

This is only supported for VPC's created in the stack - currently you're
unable to get the ID for imported VPC's. To do that you'd have to specifically
look up the Internet Gateway by name, which would require knowing the name
beforehand.

This can be useful for configuring routing using a combination of gateways:
for more information see [Routing](#routing) below.

#### Routing

It's possible to add routes to any subnets using the `addRoute()` method. If for
example you want an isolated subnet to have a static route via the default
Internet Gateway created for the public subnet - perhaps for routing a VPN
connection - you can do so like this:

```ts
const vpc = ec2.Vpc(this, "VPC", {
subnetConfiguration: [{
subnetType: SubnetType.PUBLIC,
name: 'Public',
},{
subnetType: SubnetType.ISOLATED,
name: 'Isolated',
}]
})
(vpc.isolatedSubnets[0] as Subnet).addRoute("StaticRoute", {
routerId: vpc.internetGatewayId,
routerType: RouterType.GATEWAY,
destinationCidrBlock: "8.8.8.8/32",
})
```

*Note that we cast to `Subnet` here because the list of subnets only returns an
`ISubnet`.*

### Reserving subnet IP space

There are situations where the IP space for a subnet or number of subnets
Expand Down
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/vpc.ts
Expand Up @@ -94,6 +94,7 @@ export interface IVpc extends IResource {
* Identifier for the VPN gateway
*/
readonly vpnGatewayId?: string;

/**
* Dependable that can be depended upon to force internet connectivity established on the VPC
*/
Expand Down Expand Up @@ -1098,6 +1099,12 @@ export class Vpc extends VpcBase {
*/
public readonly availabilityZones: string[];

/**
* Internet Gateway for the VPC. Note that in case the VPC is configured only
* with ISOLATED subnets, this attribute will be `undefined`.
*/
public readonly internetGatewayId?: string;

public readonly internetConnectivityEstablished: IDependable;

/**
Expand Down Expand Up @@ -1184,6 +1191,9 @@ export class Vpc extends VpcBase {
if (allowOutbound) {
const igw = new CfnInternetGateway(this, 'IGW', {
});

this.internetGatewayId = igw.ref;

this._internetConnectivityEstablished.add(igw);
const att = new CfnVPCGatewayAttachment(this, 'VPCGW', {
internetGatewayId: igw.ref,
Expand Down
168 changes: 168 additions & 0 deletions packages/@aws-cdk/aws-ec2/test/integ.vpc-gateway.expected.json
@@ -0,0 +1,168 @@
{
"Resources": {
"MyVpcF9F0CA6F": {
"Type": "AWS::EC2::VPC",
"Properties": {
"CidrBlock": "10.0.0.0/16",
"EnableDnsHostnames": true,
"EnableDnsSupport": true,
"InstanceTenancy": "default",
"Tags": [
{
"Key": "Name",
"Value": "aws-cdk-ec2-vpc-gateway/MyVpc"
}
]
}
},
"MyVpcPublicSubnet1SubnetF6608456": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"CidrBlock": "10.0.0.0/17",
"VpcId": {
"Ref": "MyVpcF9F0CA6F"
},
"AvailabilityZone": "test-region-1a",
"MapPublicIpOnLaunch": true,
"Tags": [
{
"Key": "aws-cdk:subnet-name",
"Value": "Public"
},
{
"Key": "aws-cdk:subnet-type",
"Value": "Public"
},
{
"Key": "Name",
"Value": "aws-cdk-ec2-vpc-gateway/MyVpc/PublicSubnet1"
}
]
}
},
"MyVpcPublicSubnet1RouteTableC46AB2F4": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {
"Ref": "MyVpcF9F0CA6F"
},
"Tags": [
{
"Key": "Name",
"Value": "aws-cdk-ec2-vpc-gateway/MyVpc/PublicSubnet1"
}
]
}
},
"MyVpcPublicSubnet1RouteTableAssociation2ECEE1CB": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"RouteTableId": {
"Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4"
},
"SubnetId": {
"Ref": "MyVpcPublicSubnet1SubnetF6608456"
}
}
},
"MyVpcPublicSubnet1DefaultRoute95FDF9EB": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {
"Ref": "MyVpcPublicSubnet1RouteTableC46AB2F4"
},
"DestinationCidrBlock": "0.0.0.0/0",
"GatewayId": {
"Ref": "MyVpcIGW5C4A4F63"
}
},
"DependsOn": [
"MyVpcVPCGW488ACE0D"
]
},
"MyVpcIsolatedSubnet1Subnet2259FE9F": {
"Type": "AWS::EC2::Subnet",
"Properties": {
"CidrBlock": "10.0.128.0/17",
"VpcId": {
"Ref": "MyVpcF9F0CA6F"
},
"AvailabilityZone": "test-region-1a",
"MapPublicIpOnLaunch": false,
"Tags": [
{
"Key": "aws-cdk:subnet-name",
"Value": "Isolated"
},
{
"Key": "aws-cdk:subnet-type",
"Value": "Isolated"
},
{
"Key": "Name",
"Value": "aws-cdk-ec2-vpc-gateway/MyVpc/IsolatedSubnet1"
}
]
}
},
"MyVpcIsolatedSubnet1RouteTable67AEA7B8": {
"Type": "AWS::EC2::RouteTable",
"Properties": {
"VpcId": {
"Ref": "MyVpcF9F0CA6F"
},
"Tags": [
{
"Key": "Name",
"Value": "aws-cdk-ec2-vpc-gateway/MyVpc/IsolatedSubnet1"
}
]
}
},
"MyVpcIsolatedSubnet1RouteTableAssociationCDAE5449": {
"Type": "AWS::EC2::SubnetRouteTableAssociation",
"Properties": {
"RouteTableId": {
"Ref": "MyVpcIsolatedSubnet1RouteTable67AEA7B8"
},
"SubnetId": {
"Ref": "MyVpcIsolatedSubnet1Subnet2259FE9F"
}
}
},
"MyVpcIsolatedSubnet1MyRouteCDD7D172": {
"Type": "AWS::EC2::Route",
"Properties": {
"RouteTableId": {
"Ref": "MyVpcIsolatedSubnet1RouteTable67AEA7B8"
},
"DestinationCidrBlock": "8.8.8.8/32",
"GatewayId": {
"Ref": "MyVpcIGW5C4A4F63"
}
}
},
"MyVpcIGW5C4A4F63": {
"Type": "AWS::EC2::InternetGateway",
"Properties": {
"Tags": [
{
"Key": "Name",
"Value": "aws-cdk-ec2-vpc-gateway/MyVpc"
}
]
}
},
"MyVpcVPCGW488ACE0D": {
"Type": "AWS::EC2::VPCGatewayAttachment",
"Properties": {
"VpcId": {
"Ref": "MyVpcF9F0CA6F"
},
"InternetGatewayId": {
"Ref": "MyVpcIGW5C4A4F63"
}
}
}
}
}
27 changes: 27 additions & 0 deletions packages/@aws-cdk/aws-ec2/test/integ.vpc-gateway.ts
@@ -0,0 +1,27 @@
import * as cdk from '@aws-cdk/core';
import * as ec2 from '../lib';

const app = new cdk.App();
const stack = new cdk.Stack(app, 'aws-cdk-ec2-vpc-gateway');

const vpc = new ec2.Vpc(stack, 'MyVpc', {
maxAzs: 1,
subnetConfiguration: [
{
subnetType: ec2.SubnetType.PUBLIC,
name: 'Public',
},
{
subnetType: ec2.SubnetType.ISOLATED,
name: 'Isolated',
},
],
});

(vpc.isolatedSubnets[0] as ec2.Subnet).addRoute('MyRoute', {
routerId: vpc.internetGatewayId!,
routerType: ec2.RouterType.GATEWAY,
destinationCidrBlock: '8.8.8.8/32',
});

app.synth();
52 changes: 51 additions & 1 deletion packages/@aws-cdk/aws-ec2/test/vpc.test.ts
Expand Up @@ -71,6 +71,13 @@ nodeunitShim({
test.done();
},

'can refer to the internet gateway'(test: Test) {
const stack = getTestStack();
const vpc = new Vpc(stack, 'TheVPC');
test.deepEqual(stack.resolve(vpc.internetGatewayId), { Ref: 'TheVPCIGWFA25CC08' });
test.done();
},

'with only isolated subnets, the VPC should not contain an IGW or NAT Gateways'(test: Test) {
const stack = getTestStack();
new Vpc(stack, 'TheVPC', {
Expand Down Expand Up @@ -103,7 +110,8 @@ nodeunitShim({
},
],
});
expect(stack).to(countResources('AWS::EC2::InternetGateway', 1));
expect(stack).to(countResources('AWS::EC2::InternetGateway', 1))
;
expect(stack).notTo(haveResource('AWS::EC2::NatGateway'));
test.done();
},
Expand Down Expand Up @@ -158,6 +166,48 @@ nodeunitShim({
test.done();
},

'with isolated and public subnet, should be able to use the internet gateway to define routes'(test: Test) {
const stack = getTestStack();
const vpc = new Vpc(stack, 'TheVPC', {
subnetConfiguration: [
{
subnetType: SubnetType.ISOLATED,
name: 'isolated',
},
{
subnetType: SubnetType.PUBLIC,
name: 'public',
},
],
});
(vpc.isolatedSubnets[0] as Subnet).addRoute('TheRoute', {
routerId: vpc.internetGatewayId!,
routerType: RouterType.GATEWAY,
destinationCidrBlock: '8.8.8.8/32',
});
expect(stack).to(haveResource('AWS::EC2::InternetGateway'));
expect(stack).to(haveResourceLike('AWS::EC2::Route', {
DestinationCidrBlock: '8.8.8.8/32',
GatewayId: { },
}));
test.done();
},

'with only isolated subnets the internet gateway should be undefined'(test: Test) {
const stack = getTestStack();
const vpc = new Vpc(stack, 'TheVPC', {
subnetConfiguration: [
{
subnetType: SubnetType.ISOLATED,
name: 'isolated',
},
],
});
test.equal(vpc.internetGatewayId, undefined);
expect(stack).notTo(haveResource('AWS::EC2::InternetGateway'));
test.done();
},

'with subnets and reserved subnets defined, VPC subnet count should not contain reserved subnets '(test: Test) {
const stack = getTestStack();
new Vpc(stack, 'TheVPC', {
Expand Down

0 comments on commit cb5dad8

Please sign in to comment.