Skip to content

Commit e1794e3

Browse files
authored
feat(ec2): allow private non-nat subnets (#21699)
---- Closes: #21697 and might close #21699 Not all private subnets need to have a NAT gateway for egress; an example would be when using Transit Gateway. I have incorporated the idea expressed in #21189 to add a more generic `PRIVATE_WITH_EGRESS` subnet type. This PR is largely a rename and a small logic change in `determineNatGatewayCount` ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 1bc4526 commit e1794e3

File tree

45 files changed

+191
-107
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+191
-107
lines changed

packages/@aws-cdk/aws-apigatewayv2/lib/http/vpc-link.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export class VpcLink extends Resource implements IVpcLink {
9999

100100
this.vpcLinkId = cfnResource.ref;
101101

102-
const { subnets } = props.vpc.selectSubnets(props.subnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT });
102+
const { subnets } = props.vpc.selectSubnets(props.subnets ?? { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS });
103103
this.addSubnets(...subnets);
104104

105105
if (props.securityGroups) {

packages/@aws-cdk/aws-appsync/test/appsync-rds.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('Rds Data Source configuration', () => {
3636
credentials: { username: 'clusteradmin' },
3737
clusterIdentifier: 'db-endpoint-test',
3838
vpc,
39-
vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT },
39+
vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS },
4040
securityGroups: [securityGroup],
4141
defaultDatabaseName: 'Animals',
4242
});
@@ -235,7 +235,7 @@ describe('adding rds data source from imported api', () => {
235235
credentials: { username: 'clusteradmin' },
236236
clusterIdentifier: 'db-endpoint-test',
237237
vpc,
238-
vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_NAT },
238+
vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS },
239239
securityGroups: [securityGroup],
240240
defaultDatabaseName: 'Animals',
241241
});
@@ -269,4 +269,4 @@ describe('adding rds data source from imported api', () => {
269269
ApiId: { 'Fn::GetAtt': ['baseApiCDA4D43A', 'ApiId'] },
270270
});
271271
});
272-
});
272+
});

packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1825,7 +1825,7 @@ test('can use Vpc imported from unparseable list tokens', () => {
18251825
vpc,
18261826
allowAllOutbound: false,
18271827
associatePublicIpAddress: false,
1828-
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT },
1828+
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
18291829
});
18301830

18311831
// THEN

packages/@aws-cdk/aws-batch/test/compute-environment.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ describe('Batch Compute Environment', () => {
375375
],
376376
type: batch.ComputeResourceType.ON_DEMAND,
377377
vpcSubnets: {
378-
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
378+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
379379
},
380380
} as batch.ComputeResources,
381381
enabled: false,

packages/@aws-cdk/aws-cloud9/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ new cloud9.Ec2Environment(this, 'Cloud9Env2', {
5656
const c9env = new cloud9.Ec2Environment(this, 'Cloud9Env3', {
5757
vpc,
5858
subnetSelection: {
59-
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
59+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
6060
},
6161
imageId: cloud9.ImageId.AMAZON_LINUX_2,
6262
});

packages/@aws-cdk/aws-cloud9/test/cloud9.environment.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ test('create resource correctly with vpc, imageId, and subnetSelection', () => {
2828
new cloud9.Ec2Environment(stack, 'C9Env', {
2929
vpc,
3030
subnetSelection: {
31-
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
31+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
3232
},
3333
imageId: cloud9.ImageId.AMAZON_LINUX_2,
3434
});
@@ -146,4 +146,4 @@ test.each([
146146
ImageId: expected,
147147
SubnetId: Match.anyValue(),
148148
});
149-
});
149+
});

packages/@aws-cdk/aws-docdb/test/cluster.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ describe('DatabaseCluster', () => {
108108
vpc,
109109
instanceType: ec2.InstanceType.of(ec2.InstanceClass.R5, ec2.InstanceSize.LARGE),
110110
vpcSubnets: {
111-
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
111+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
112112
},
113113
});
114114
}).toThrowError('Cluster requires at least 2 subnets, got 1');

packages/@aws-cdk/aws-ec2/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ distinguishes three different subnet types:
4242
Internet Gateway. If you want your instances to have a public IP address
4343
and be directly reachable from the Internet, you must place them in a
4444
public subnet.
45-
* **Private with Internet Access (`SubnetType.PRIVATE_WITH_NAT`)** - instances in private subnets are not directly routable from the
46-
Internet, and connect out to the Internet via a NAT gateway. By default, a
47-
NAT gateway is created in every public subnet for maximum availability. Be
45+
* **Private with Internet Access (`SubnetType.PRIVATE_WITH_EGRESS`)** - instances in private subnets are not directly routable from the
46+
Internet, and you must provide a way to connect out to the Internet.
47+
By default, a NAT gateway is created in every public subnet for maximum availability. Be
4848
aware that you will be charged for NAT gateways.
49+
Alternatively you can set `natGateways:0` and provide your own egress configuration (i.e through Transit Gateway)
4950
* **Isolated (`SubnetType.PRIVATE_ISOLATED`)** - isolated subnets do not route from or to the Internet, and
5051
as such do not require NAT gateways. They can only connect to or be
5152
connected to from other instances in the same VPC. A default VPC configuration
@@ -260,7 +261,7 @@ const vpc = new ec2.Vpc(this, 'TheVPC', {
260261
{
261262
cidrMask: 24,
262263
name: 'Application',
263-
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
264+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
264265
},
265266
{
266267
cidrMask: 28,
@@ -363,12 +364,12 @@ const vpc = new ec2.Vpc(this, 'TheVPC', {
363364
{
364365
cidrMask: 26,
365366
name: 'Application1',
366-
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
367+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
367368
},
368369
{
369370
cidrMask: 26,
370371
name: 'Application2',
371-
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
372+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
372373
reserved: true, // <---- This subnet group is reserved
373374
},
374375
{

packages/@aws-cdk/aws-ec2/lib/util.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function defaultSubnetName(type: SubnetType) {
1717
switch (type) {
1818
case SubnetType.PUBLIC: return 'Public';
1919
case SubnetType.PRIVATE_WITH_NAT:
20+
case SubnetType.PRIVATE_WITH_EGRESS:
2021
case SubnetType.PRIVATE:
2122
return 'Private';
2223
case SubnetType.PRIVATE_ISOLATED:

packages/@aws-cdk/aws-ec2/lib/vpc.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,25 @@ export enum SubnetType {
188188
*/
189189
ISOLATED = 'Deprecated_Isolated',
190190

191+
/**
192+
* Subnet that routes to the internet, but not vice versa.
193+
*
194+
* Instances in a private subnet can connect to the Internet, but will not
195+
* allow connections to be initiated from the Internet. Egress to the internet will
196+
* need to be provided.
197+
* NAT Gateway(s) are the default solution to providing this subnet type the ability to route Internet traffic.
198+
* If a NAT Gateway is not required or desired, set `natGateways:0` or use
199+
* `SubnetType.PRIVATE_ISOLATED` instead.
200+
*
201+
* By default, a NAT gateway is created in every public subnet for maximum availability.
202+
* Be aware that you will be charged for NAT gateways.
203+
*
204+
* Normally a Private subnet will use a NAT gateway in the same AZ, but
205+
* if `natGateways` is used to reduce the number of NAT gateways, a NAT
206+
* gateway from another AZ will be used instead.
207+
*/
208+
PRIVATE_WITH_EGRESS = 'Private',
209+
191210
/**
192211
* Subnet that routes to the internet (via a NAT gateway), but not vice versa.
193212
*
@@ -202,8 +221,9 @@ export enum SubnetType {
202221
* Normally a Private subnet will use a NAT gateway in the same AZ, but
203222
* if `natGateways` is used to reduce the number of NAT gateways, a NAT
204223
* gateway from another AZ will be used instead.
224+
* @deprecated use `PRIVATE_WITH_EGRESS`
205225
*/
206-
PRIVATE_WITH_NAT = 'Private',
226+
PRIVATE_WITH_NAT = 'Deprecated_Private_NAT',
207227

208228
/**
209229
* Subnet that routes to the internet, but not vice versa.
@@ -220,7 +240,7 @@ export enum SubnetType {
220240
* if `natGateways` is used to reduce the number of NAT gateways, a NAT
221241
* gateway from another AZ will be used instead.
222242
*
223-
* @deprecated use `PRIVATE_WITH_NAT`
243+
* @deprecated use `PRIVATE_WITH_EGRESS`
224244
*/
225245
PRIVATE = 'Deprecated_Private',
226246

@@ -251,7 +271,7 @@ export interface SubnetSelection {
251271
*
252272
* At most one of `subnetType` and `subnetGroupName` can be supplied.
253273
*
254-
* @default SubnetType.PRIVATE_WITH_NAT (or ISOLATED or PUBLIC if there are no PRIVATE_WITH_NAT subnets)
274+
* @default SubnetType.PRIVATE_WITH_EGRESS (or ISOLATED or PUBLIC if there are no PRIVATE_WITH_EGRESS subnets)
255275
*/
256276
readonly subnetType?: SubnetType;
257277

@@ -552,7 +572,7 @@ abstract class VpcBase extends Resource implements IVpc {
552572
subnets = this.selectSubnetObjectsByName(selection.subnetGroupName);
553573

554574
} else { // Or specify by type
555-
const type = selection.subnetType || SubnetType.PRIVATE_WITH_NAT;
575+
const type = selection.subnetType || SubnetType.PRIVATE_WITH_EGRESS;
556576
subnets = this.selectSubnetObjectsByType(type);
557577
}
558578

@@ -588,6 +608,7 @@ abstract class VpcBase extends Resource implements IVpc {
588608
[SubnetType.PRIVATE_ISOLATED]: this.isolatedSubnets,
589609
[SubnetType.ISOLATED]: this.isolatedSubnets,
590610
[SubnetType.PRIVATE_WITH_NAT]: this.privateSubnets,
611+
[SubnetType.PRIVATE_WITH_EGRESS]: this.privateSubnets,
591612
[SubnetType.PRIVATE]: this.privateSubnets,
592613
[SubnetType.PUBLIC]: this.publicSubnets,
593614
};
@@ -631,7 +652,7 @@ abstract class VpcBase extends Resource implements IVpc {
631652
if (placement.subnetType === undefined && placement.subnetGroupName === undefined && placement.subnets === undefined) {
632653
// Return default subnet type based on subnets that actually exist
633654
let subnetType = this.privateSubnets.length
634-
? SubnetType.PRIVATE_WITH_NAT : this.isolatedSubnets.length ? SubnetType.PRIVATE_ISOLATED : SubnetType.PUBLIC;
655+
? SubnetType.PRIVATE_WITH_EGRESS : this.isolatedSubnets.length ? SubnetType.PRIVATE_ISOLATED : SubnetType.PUBLIC;
635656
placement = { ...placement, subnetType: subnetType };
636657
}
637658

@@ -915,7 +936,7 @@ export interface VpcProps {
915936
* {
916937
* cidrMask: 24,
917938
* name: 'application',
918-
* subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
939+
* subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
919940
* },
920941
* {
921942
* cidrMask: 28,
@@ -1067,7 +1088,7 @@ export interface SubnetConfiguration {
10671088
*
10681089
* // Iterate the private subnets
10691090
* const selection = vpc.selectSubnets({
1070-
* subnetType: ec2.SubnetType.PRIVATE_WITH_NAT
1091+
* subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS
10711092
* });
10721093
*
10731094
* for (const subnet of selection.subnets) {
@@ -1096,8 +1117,8 @@ export class Vpc extends VpcBase {
10961117
name: defaultSubnetName(SubnetType.PUBLIC),
10971118
},
10981119
{
1099-
subnetType: SubnetType.PRIVATE_WITH_NAT,
1100-
name: defaultSubnetName(SubnetType.PRIVATE_WITH_NAT),
1120+
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
1121+
name: defaultSubnetName(SubnetType.PRIVATE_WITH_EGRESS),
11011122
},
11021123
];
11031124

@@ -1531,6 +1552,7 @@ export class Vpc extends VpcBase {
15311552
this.publicSubnets.push(publicSubnet);
15321553
subnet = publicSubnet;
15331554
break;
1555+
case SubnetType.PRIVATE_WITH_EGRESS:
15341556
case SubnetType.PRIVATE_WITH_NAT:
15351557
case SubnetType.PRIVATE:
15361558
const privateSubnet = new PrivateSubnet(this, name, subnetProps);
@@ -1561,6 +1583,7 @@ const SUBNETNAME_TAG = 'aws-cdk:subnet-name';
15611583
function subnetTypeTagValue(type: SubnetType) {
15621584
switch (type) {
15631585
case SubnetType.PUBLIC: return 'Public';
1586+
case SubnetType.PRIVATE_WITH_EGRESS:
15641587
case SubnetType.PRIVATE_WITH_NAT:
15651588
case SubnetType.PRIVATE:
15661589
return 'Private';
@@ -2007,7 +2030,7 @@ class ImportedVpc extends VpcBase {
20072030

20082031
/* eslint-disable max-len */
20092032
const pub = new ImportSubnetGroup(props.publicSubnetIds, props.publicSubnetNames, props.publicSubnetRouteTableIds, SubnetType.PUBLIC, this.availabilityZones, 'publicSubnetIds', 'publicSubnetNames', 'publicSubnetRouteTableIds');
2010-
const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, props.privateSubnetRouteTableIds, SubnetType.PRIVATE_WITH_NAT, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames', 'privateSubnetRouteTableIds');
2033+
const priv = new ImportSubnetGroup(props.privateSubnetIds, props.privateSubnetNames, props.privateSubnetRouteTableIds, SubnetType.PRIVATE_WITH_EGRESS, this.availabilityZones, 'privateSubnetIds', 'privateSubnetNames', 'privateSubnetRouteTableIds');
20112034
const iso = new ImportSubnetGroup(props.isolatedSubnetIds, props.isolatedSubnetNames, props.isolatedSubnetRouteTableIds, SubnetType.PRIVATE_ISOLATED, this.availabilityZones, 'isolatedSubnetIds', 'isolatedSubnetNames', 'isolatedSubnetRouteTableIds');
20122035
/* eslint-enable max-len */
20132036

@@ -2207,13 +2230,13 @@ class ImportedSubnet extends Resource implements ISubnet, IPublicSubnet, IPrivat
22072230
* They seem pointless but I see no reason to prevent it.
22082231
*/
22092232
function determineNatGatewayCount(requestedCount: number | undefined, subnetConfig: SubnetConfiguration[], azCount: number) {
2210-
const hasPrivateSubnets = subnetConfig.some(c => (c.subnetType === SubnetType.PRIVATE_WITH_NAT
2211-
|| c.subnetType === SubnetType.PRIVATE) && !c.reserved);
2233+
const hasPrivateSubnets = subnetConfig.some(c => (c.subnetType === SubnetType.PRIVATE_WITH_EGRESS
2234+
|| c.subnetType === SubnetType.PRIVATE || c.subnetType === SubnetType.PRIVATE_WITH_NAT) && !c.reserved);
22122235
const hasPublicSubnets = subnetConfig.some(c => c.subnetType === SubnetType.PUBLIC);
22132236

22142237
const count = requestedCount !== undefined ? Math.min(requestedCount, azCount) : (hasPrivateSubnets ? azCount : 0);
22152238

2216-
if (count === 0 && hasPrivateSubnets) {
2239+
if (count === 0 && hasPrivateSubnets && subnetConfig.some(c => c.subnetType === SubnetType.PRIVATE_WITH_NAT)) {
22172240
// eslint-disable-next-line max-len
22182241
throw new Error('If you do not want NAT gateways (natGateways=0), make sure you don\'t configure any PRIVATE subnets in \'subnetConfiguration\' (make them PUBLIC or ISOLATED instead)');
22192242
}

packages/@aws-cdk/aws-ec2/test/integ.reserved-private-subnet.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class VpcReservedPrivateSubnetStack extends cdk.Stack {
2727
},
2828
{
2929
name: 'private',
30-
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
30+
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
3131
reserved: true,
3232
},
3333
],

packages/@aws-cdk/aws-ec2/test/integ.vpc-networkacl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const vpc = new ec2.Vpc(stack, 'MyVpc');
1010

1111
const nacl1 = new ec2.NetworkAcl(stack, 'myNACL1', {
1212
vpc,
13-
subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT },
13+
subnetSelection: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
1414
});
1515

1616
nacl1.addEntry('AllowDNSEgress', {

packages/@aws-cdk/aws-ec2/test/vpc-endpoint.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ describe('vpc endpoint', () => {
6363
subnetType: SubnetType.PUBLIC,
6464
},
6565
{
66-
subnetType: SubnetType.PRIVATE_WITH_NAT,
66+
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
6767
},
6868
],
6969
},

packages/@aws-cdk/aws-ec2/test/vpc.from-lookup.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ describe('vpc from lookup', () => {
165165
});
166166

167167
// WHEN
168-
const subnets = vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_WITH_NAT, onePerAz: true });
168+
const subnets = vpc.selectSubnets({ subnetType: SubnetType.PRIVATE_WITH_EGRESS, onePerAz: true });
169169

170170
// THEN: we got 2 subnets and not 4
171171
expect(subnets.subnets.map(s => s.availabilityZone)).toEqual(['us-east-1c', 'us-east-1d']);

0 commit comments

Comments
 (0)