Skip to content

Commit

Permalink
feat(ec2): rename SubnetTypes to improve clarity with EC2 conventions (
Browse files Browse the repository at this point in the history
…#16348)

Early on in the CDK history, a decision was made to delineate between subnets
with Internet access (i.e., those with a NAT) and those without. The convention
chosen at that time was to label the subnets as `PRIVATE` and `ISOLATED`,
respectively. The intent was to make it clear that subnets without a NAT were
completely isolated from the broader Internet (unless connected through another
subnet).

However, this introduction of a new subnet type that does not match EC2
documentation and naming conventions can cause confusion. Most critically, a
user may select a `PRIVATE` subnet without realizing that it automatically
requires one (or more) NAT gateways. As NAT gateways are not free, this can
lead to unintended charges.

To realign to the EC2 terminology -- while retaining the existing logic
surrounding SubnetTypes -- the existing types of `PRIVATE` and `ISOLATED` are
being renamed to `PRIVATE_WITH_NAT` and `PRIVATE_ISOLATED`, respectively.

fixes #15929


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
njlynch committed Sep 3, 2021
1 parent 174b066 commit 2023004
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 75 deletions.
14 changes: 7 additions & 7 deletions packages/@aws-cdk/aws-ec2/README.md
Expand Up @@ -38,15 +38,15 @@ instances for your project.
A VPC consists of one or more subnets that instances can be placed into. CDK
distinguishes three different subnet types:

* **Public** - public subnets connect directly to the Internet using an
* **Public (`SubnetType.PUBLIC`)** - public subnets connect directly to the Internet using an
Internet Gateway. If you want your instances to have a public IP address
and be directly reachable from the Internet, you must place them in a
public subnet.
* **Private** - instances in private subnets are not directly routable from the
* **Private with Internet Access (`SubnetType.PRIVATE_WITH_NAT`)** - instances in private subnets are not directly routable from the
Internet, and connect out to the Internet via a NAT gateway. By default, a
NAT gateway is created in every public subnet for maximum availability. Be
aware that you will be charged for NAT gateways.
* **Isolated** - isolated subnets do not route from or to the Internet, and
* **Isolated (`SubnetType.PRIVATE_ISOLATED`)** - isolated subnets do not route from or to the Internet, and
as such do not require NAT gateways. They can only connect to or be
connected to from other instances in the same VPC. A default VPC configuration
will not include isolated subnets,
Expand Down Expand Up @@ -245,12 +245,12 @@ const vpc = new ec2.Vpc(this, 'TheVPC', {
{
cidrMask: 24,
name: 'Application',
subnetType: ec2.SubnetType.PRIVATE,
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
},
{
cidrMask: 28,
name: 'Database',
subnetType: ec2.SubnetType.ISOLATED,
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,

// 'reserved' can be used to reserve IP address space. No resources will
// be created for this subnet, but the IP range will be kept available for
Expand Down Expand Up @@ -345,12 +345,12 @@ const vpc = new ec2.Vpc(this, 'TheVPC', {
{
cidrMask: 26,
name: 'Application1',
subnetType: ec2.SubnetType.PRIVATE,
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
},
{
cidrMask: 26,
name: 'Application2',
subnetType: ec2.SubnetType.PRIVATE,
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
reserved: true, // <---- This subnet group is reserved
},
{
Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/aws-ec2/lib/util.ts
Expand Up @@ -16,8 +16,8 @@ export function slugify(x: string): string {
export function defaultSubnetName(type: SubnetType) {
switch (type) {
case SubnetType.PUBLIC: return 'Public';
case SubnetType.PRIVATE: return 'Private';
case SubnetType.ISOLATED: return 'Isolated';
case SubnetType.PRIVATE_WITH_NAT: return 'Private';
case SubnetType.PRIVATE_ISOLATED: return 'Isolated';
}
}

Expand Down Expand Up @@ -132,4 +132,4 @@ export function allRouteTableIds(subnets: ISubnet[]): string[] {

export function flatten<A>(xs: A[][]): A[] {
return Array.prototype.concat.apply([], xs);
}
}
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ec2/lib/vpc-endpoint.ts
Expand Up @@ -125,7 +125,7 @@ export interface GatewayVpcEndpointOptions {
* service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
* // Add only to ISOLATED subnets
* subnets: [
* { subnetType: ec2.SubnetType.ISOLATED }
* { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }
* ]
* });
*
Expand Down
90 changes: 67 additions & 23 deletions packages/@aws-cdk/aws-ec2/lib/vpc.ts
Expand Up @@ -159,26 +159,69 @@ export interface IVpc extends IResource {
*/
export enum SubnetType {
/**
* Isolated Subnets do not route traffic to the Internet (in this VPC).
* Isolated Subnets do not route traffic to the Internet (in this VPC),
* and as such, do not require NAT gateways.
*
* Isolated subnets can only connect to or be connected to from other
* instances in the same VPC. A default VPC configuration will not include
* isolated subnets.
*
* This can be good for subnets with RDS or Elasticache instances,
* or which route Internet traffic through a peer VPC.
*
* @deprecated use `SubnetType.PRIVATE_ISOLATED`
*/
ISOLATED = 'Isolated',

/**
* Isolated Subnets do not route traffic to the Internet (in this VPC),
* and as such, do not require NAT gateways.
*
* Isolated subnets can only connect to or be connected to from other
* instances in the same VPC. A default VPC configuration will not include
* isolated subnets.
*
* This can be good for subnets with RDS or Elasticache instances,
* or which route Internet traffic through a peer VPC.
*/
PRIVATE_ISOLATED = 'Isolated',

/**
* Subnet that routes to the internet, but not vice versa.
*
* Instances in a private subnet can connect to the Internet, but will not
* allow connections to be initiated from the Internet. Internet traffic will
* be routed via a NAT Gateway.
* allow connections to be initiated from the Internet. NAT Gateway(s) are
* required with this subnet type to route the Internet traffic through.
* If a NAT Gateway is not required or desired, use `SubnetType.PRIVATE_ISOLATED` instead.
*
* By default, a NAT gateway is created in every public subnet for maximum availability.
* Be aware that you will be charged for NAT gateways.
*
* Normally a Private subnet will use a NAT gateway in the same AZ, but
* if `natGateways` is used to reduce the number of NAT gateways, a NAT
* gateway from another AZ will be used instead.
*
* @deprecated use `PRIVATE_WITH_NAT`
*/
PRIVATE = 'Private',

/**
* Subnet that routes to the internet (via a NAT gateway), but not vice versa.
*
* Instances in a private subnet can connect to the Internet, but will not
* allow connections to be initiated from the Internet. NAT Gateway(s) are
* required with this subnet type to route the Internet traffic through.
* If a NAT Gateway is not required or desired, use `SubnetType.PRIVATE_ISOLATED` instead.
*
* By default, a NAT gateway is created in every public subnet for maximum availability.
* Be aware that you will be charged for NAT gateways.
*
* Normally a Private subnet will use a NAT gateway in the same AZ, but
* if `natGateways` is used to reduce the number of NAT gateways, a NAT
* gateway from another AZ will be used instead.
*/
PRIVATE_WITH_NAT = 'Private',

/**
* Subnet connected to the Internet
*
Expand Down Expand Up @@ -206,7 +249,7 @@ export interface SubnetSelection {
*
* At most one of `subnetType` and `subnetGroupName` can be supplied.
*
* @default SubnetType.PRIVATE (or ISOLATED or PUBLIC if there are no PRIVATE subnets)
* @default SubnetType.PRIVATE_WITH_NAT (or ISOLATED or PUBLIC if there are no PRIVATE_WITH_NAT subnets)
*/
readonly subnetType?: SubnetType;

Expand Down Expand Up @@ -490,7 +533,7 @@ abstract class VpcBase extends Resource implements IVpc {
subnets = this.selectSubnetObjectsByName(selection.subnetGroupName);

} else { // Or specify by type
const type = selection.subnetType || SubnetType.PRIVATE;
const type = selection.subnetType || SubnetType.PRIVATE_WITH_NAT;
subnets = this.selectSubnetObjectsByType(type);
}

Expand Down Expand Up @@ -523,8 +566,8 @@ abstract class VpcBase extends Resource implements IVpc {

private selectSubnetObjectsByType(subnetType: SubnetType) {
const allSubnets = {
[SubnetType.ISOLATED]: this.isolatedSubnets,
[SubnetType.PRIVATE]: this.privateSubnets,
[SubnetType.PRIVATE_ISOLATED]: this.isolatedSubnets,
[SubnetType.PRIVATE_WITH_NAT]: this.privateSubnets,
[SubnetType.PUBLIC]: this.publicSubnets,
};

Expand Down Expand Up @@ -566,7 +609,8 @@ abstract class VpcBase extends Resource implements IVpc {

if (placement.subnetType === undefined && placement.subnetGroupName === undefined && placement.subnets === undefined) {
// Return default subnet type based on subnets that actually exist
let subnetType = this.privateSubnets.length ? SubnetType.PRIVATE : this.isolatedSubnets.length ? SubnetType.ISOLATED : SubnetType.PUBLIC;
let subnetType = this.privateSubnets.length
? SubnetType.PRIVATE_WITH_NAT : this.isolatedSubnets.length ? SubnetType.PRIVATE_ISOLATED : SubnetType.PUBLIC;
placement = { ...placement, subnetType: subnetType };
}

Expand Down Expand Up @@ -839,12 +883,12 @@ export interface VpcProps {
* {
* cidrMask: 24,
* name: 'application',
* subnetType: ec2.SubnetType.PRIVATE,
* subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
* },
* {
* cidrMask: 28,
* name: 'rds',
* subnetType: ec2.SubnetType.ISOLATED,
* subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
* }
* ]
* });
Expand Down Expand Up @@ -975,7 +1019,7 @@ export interface SubnetConfiguration {
*
* // Iterate the private subnets
* const selection = vpc.selectSubnets({
* subnetType: ec2.SubnetType.PRIVATE
* subnetType: ec2.SubnetType.PRIVATE_WITH_NAT
* });
*
* for (const subnet of selection.subnets) {
Expand Down Expand Up @@ -1004,8 +1048,8 @@ export class Vpc extends VpcBase {
name: defaultSubnetName(SubnetType.PUBLIC),
},
{
subnetType: SubnetType.PRIVATE,
name: defaultSubnetName(SubnetType.PRIVATE),
subnetType: SubnetType.PRIVATE_WITH_NAT,
name: defaultSubnetName(SubnetType.PRIVATE_WITH_NAT),
},
];

Expand All @@ -1020,8 +1064,8 @@ export class Vpc extends VpcBase {
name: defaultSubnetName(SubnetType.PUBLIC),
},
{
subnetType: SubnetType.ISOLATED,
name: defaultSubnetName(SubnetType.ISOLATED),
subnetType: SubnetType.PRIVATE_ISOLATED,
name: defaultSubnetName(SubnetType.PRIVATE_ISOLATED),
},
];

Expand Down Expand Up @@ -1244,7 +1288,7 @@ export class Vpc extends VpcBase {
this.createSubnets();

const allowOutbound = this.subnetConfiguration.filter(
subnet => (subnet.subnetType !== SubnetType.ISOLATED)).length > 0;
subnet => (subnet.subnetType !== SubnetType.PRIVATE_ISOLATED)).length > 0;

// Create an Internet Gateway and attach it if necessary
if (allowOutbound) {
Expand Down Expand Up @@ -1396,12 +1440,12 @@ export class Vpc extends VpcBase {
this.publicSubnets.push(publicSubnet);
subnet = publicSubnet;
break;
case SubnetType.PRIVATE:
case SubnetType.PRIVATE_WITH_NAT:
const privateSubnet = new PrivateSubnet(this, name, subnetProps);
this.privateSubnets.push(privateSubnet);
subnet = privateSubnet;
break;
case SubnetType.ISOLATED:
case SubnetType.PRIVATE_ISOLATED:
const isolatedSubnet = new PrivateSubnet(this, name, subnetProps);
this.isolatedSubnets.push(isolatedSubnet);
subnet = isolatedSubnet;
Expand All @@ -1424,8 +1468,8 @@ const SUBNETNAME_TAG = 'aws-cdk:subnet-name';
function subnetTypeTagValue(type: SubnetType) {
switch (type) {
case SubnetType.PUBLIC: return 'Public';
case SubnetType.PRIVATE: return 'Private';
case SubnetType.ISOLATED: return 'Isolated';
case SubnetType.PRIVATE_WITH_NAT: return 'Private';
case SubnetType.PRIVATE_ISOLATED: return 'Isolated';
}
}

Expand Down Expand Up @@ -1834,8 +1878,8 @@ class ImportedVpc extends VpcBase {

/* eslint-disable max-len */
const pub = new ImportSubnetGroup(props.publicSubnetIds, props.publicSubnetNames, props.publicSubnetRouteTableIds, SubnetType.PUBLIC, this.availabilityZones, 'publicSubnetIds', 'publicSubnetNames', 'publicSubnetRouteTableIds');
const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, props.privateSubnetRouteTableIds, SubnetType.PRIVATE, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames', 'privateSubnetRouteTableIds');
const iso = new ImportSubnetGroup(props.isolatedSubnetIds, props.isolatedSubnetNames, props.isolatedSubnetRouteTableIds, SubnetType.ISOLATED, this.availabilityZones, 'isolatedSubnetIds', 'isolatedSubnetNames', 'isolatedSubnetRouteTableIds');
const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, props.privateSubnetRouteTableIds, SubnetType.PRIVATE_WITH_NAT, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames', 'privateSubnetRouteTableIds');
const iso = new ImportSubnetGroup(props.isolatedSubnetIds, props.isolatedSubnetNames, props.isolatedSubnetRouteTableIds, SubnetType.PRIVATE_ISOLATED, this.availabilityZones, 'isolatedSubnetIds', 'isolatedSubnetNames', 'isolatedSubnetRouteTableIds');
/* eslint-enable max-len */

this.publicSubnets = pub.import(this);
Expand Down Expand Up @@ -2028,7 +2072,7 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat
* They seem pointless but I see no reason to prevent it.
*/
function determineNatGatewayCount(requestedCount: number | undefined, subnetConfig: SubnetConfiguration[], azCount: number) {
const hasPrivateSubnets = subnetConfig.some(c => c.subnetType === SubnetType.PRIVATE && !c.reserved);
const hasPrivateSubnets = subnetConfig.some(c => c.subnetType === SubnetType.PRIVATE_WITH_NAT && !c.reserved);
const hasPublicSubnets = subnetConfig.some(c => c.subnetType === SubnetType.PUBLIC);

const count = requestedCount !== undefined ? Math.min(requestedCount, azCount) : (hasPrivateSubnets ? azCount : 0);
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-ec2/test/bastion-host.test.ts
Expand Up @@ -28,7 +28,7 @@ nodeunitShim({
const vpc = new Vpc(stack, 'VPC', {
subnetConfiguration: [
{
subnetType: SubnetType.ISOLATED,
subnetType: SubnetType.PRIVATE_ISOLATED,
name: 'Isolated',
},
],
Expand All @@ -53,7 +53,7 @@ nodeunitShim({
const vpc = new Vpc(stack, 'VPC', {
subnetConfiguration: [
{
subnetType: SubnetType.ISOLATED,
subnetType: SubnetType.PRIVATE_ISOLATED,
name: 'Isolated',
},
],
Expand Down
Expand Up @@ -27,7 +27,7 @@ class VpcReservedPrivateSubnetStack extends cdk.Stack {
},
{
name: 'private',
subnetType: ec2.SubnetType.PRIVATE,
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
reserved: true,
},
],
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ec2/test/integ.vpc-gateway.ts
Expand Up @@ -12,7 +12,7 @@ const vpc = new ec2.Vpc(stack, 'MyVpc', {
name: 'Public',
},
{
subnetType: ec2.SubnetType.ISOLATED,
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
name: 'Isolated',
},
],
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ec2/test/integ.vpc-networkacl.ts
Expand Up @@ -10,7 +10,7 @@ const vpc = new ec2.Vpc(stack, 'MyVpc');

const nacl1 = new ec2.NetworkAcl(stack, 'myNACL1', {
vpc,
subnetSelection: { subnetType: ec2.SubnetType.PRIVATE },
subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT },
});

nacl1.addEntry('AllowDNSEgress', {
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts
Expand Up @@ -64,7 +64,7 @@ nodeunitShim({
subnetType: SubnetType.PUBLIC,
},
{
subnetType: SubnetType.PRIVATE,
subnetType: SubnetType.PRIVATE_WITH_NAT,
},
],
},
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-ec2/test/vpc.from-lookup.test.ts
Expand Up @@ -166,7 +166,7 @@ nodeunitShim({
});

// WHEN
const subnets = vpc.selectSubnets({ subnetType: SubnetType.PRIVATE, onePerAz: true });
const subnets = vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_WITH_NAT, onePerAz: true });

// THEN: we got 2 subnets and not 4
test.deepEqual(subnets.subnets.map(s => s.availabilityZone), ['us-east-1c', 'us-east-1d']);
Expand Down

0 comments on commit 2023004

Please sign in to comment.