Skip to content

fix(ec2): dual-stack vpc without private subnets creates EgressOnlyInternetGateway (under feature flag) #34437

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

Merged
merged 22 commits into from
May 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/aws-cdk-lib/aws-ec2/lib/vpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1656,7 +1656,13 @@ export class Vpc extends VpcBase {
}

// Create an Egress Only Internet Gateway and attach it if necessary
if (this.useIpv6 && this.privateSubnets) {

const isRequirePrivateSubnetsForEgressOnlyIgw =
FeatureFlags.of(this).isEnabled(cxapi.EC2_REQUIRE_PRIVATE_SUBNETS_FOR_EGRESSONLYINTERNETGATEWAY);

if ((this.useIpv6 && !isRequirePrivateSubnetsForEgressOnlyIgw && this.privateSubnets) ||
(this.useIpv6 && isRequirePrivateSubnetsForEgressOnlyIgw && this.privateSubnets.length > 0)
) {
const eigw = new CfnEgressOnlyInternetGateway(this, 'EIGW6', {
vpcId: this.vpcId,
});
Expand Down
86 changes: 85 additions & 1 deletion packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { testDeprecated } from '@aws-cdk/cdk-build-tools';
import { Annotations, Match, Template } from '../../assertions';
import { App, CfnOutput, CfnResource, Fn, Lazy, Stack, Tags } from '../../core';
import { EC2_RESTRICT_DEFAULT_SECURITY_GROUP } from '../../cx-api';
import { EC2_REQUIRE_PRIVATE_SUBNETS_FOR_EGRESSONLYINTERNETGATEWAY, EC2_RESTRICT_DEFAULT_SECURITY_GROUP } from '../../cx-api';
import {
AclCidr,
AclTraffic,
Expand Down Expand Up @@ -2747,6 +2747,90 @@ describe('vpc', () => {
},
});
});
test('EgressOnlyIGW is created if no private subnet configured in dual stack and feature flag EC2_REQUIRE_PRIVATE_SUBNETS_FOR_EGRESSONLYINTERNETGATEWAY is not enabled', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'DualStackStack');

// WHEN
const vpc = new Vpc(stack, 'Vpc', {
ipProtocol: IpProtocol.DUAL_STACK,
subnetConfiguration: [
{
subnetType: SubnetType.PUBLIC,
name: 'public',
},
],
});

// THEN
Template.fromStack(stack).resourceCountIs('AWS::EC2::EgressOnlyInternetGateway', 1);
});
test('EgressOnlyIGW is created if a private subnet is configured in dual stack and feature flag EC2_REQUIRE_PRIVATE_SUBNETS_FOR_EGRESSONLYINTERNETGATEWAY is not enabled', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'DualStackStack');

// WHEN
const vpc = new Vpc(stack, 'Vpc', {
ipProtocol: IpProtocol.DUAL_STACK,
subnetConfiguration: [
{
subnetType: SubnetType.PUBLIC,
name: 'public',
},
{
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
name: 'private',
},
],
});

// THEN
Template.fromStack(stack).resourceCountIs('AWS::EC2::EgressOnlyInternetGateway', 1);
});

test('EgressOnlyIGW is created if a private subnet is configured in dual stack and feature flag EC2_REQUIRE_PRIVATE_SUBNETS_FOR_EGRESSONLYINTERNETGATEWAY is enabled', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'DualStackStack');
// WHEN
stack.node.setContext(EC2_REQUIRE_PRIVATE_SUBNETS_FOR_EGRESSONLYINTERNETGATEWAY, true);
const vpc = new Vpc(stack, 'Vpc', {
ipProtocol: IpProtocol.DUAL_STACK,
subnetConfiguration: [
{
subnetType: SubnetType.PUBLIC,
name: 'public',
},
{
subnetType: SubnetType.PRIVATE_WITH_EGRESS,
name: 'private',
},
],
});

// THEN
Template.fromStack(stack).resourceCountIs('AWS::EC2::EgressOnlyInternetGateway', 1);
});
test('EgressOnlyIGW is not created if no private subnet is configured in dual stack and feature flag EC2_REQUIRE_PRIVATE_SUBNETS_FOR_EGRESSONLYINTERNETGATEWAY is enabled', () => {
// GIVEN
const app = new App();
const stack = new Stack(app, 'DualStackStack');
stack.node.setContext(EC2_REQUIRE_PRIVATE_SUBNETS_FOR_EGRESSONLYINTERNETGATEWAY, true);
// WHEN
const vpc = new Vpc(stack, 'Vpc', {
ipProtocol: IpProtocol.DUAL_STACK,
subnetConfiguration: [
{
subnetType: SubnetType.PUBLIC,
name: 'public',
},
],
});
// THEN
Template.fromStack(stack).resourceCountIs('AWS::EC2::EgressOnlyInternetGateway', 0);
});

test('error should occur if IPv6 properties are provided for a non-dual-stack VPC', () => {
// GIVEN
Expand Down
19 changes: 18 additions & 1 deletion packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Flags come in three types:
| [@aws-cdk/core:aspectPrioritiesMutating](#aws-cdkcoreaspectprioritiesmutating) | When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING. | 2.189.1 | new default |
| [@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions](#aws-cdks3-notificationsadds3trustkeypolicyforsnssubscriptions) | Add an S3 trust policy to a KMS key resource policy for SNS subscriptions. | 2.195.0 | fix |
| [@aws-cdk/aws-s3:publicAccessBlockedByDefault](#aws-cdkaws-s3publicaccessblockedbydefault) | When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined. | V2NEXT | fix |
| [@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway](#aws-cdkaws-ec2requireprivatesubnetsforegressonlyinternetgateway) | When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC. | V2NEXT | fix |

<!-- END table -->

Expand Down Expand Up @@ -185,7 +186,8 @@ The following json shows the current recommended set of flags, as `cdk init` wou
"@aws-cdk/aws-dynamodb:retainTableReplica": true,
"@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true,
"@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true,
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true,
"@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true
}
}
```
Expand Down Expand Up @@ -2125,4 +2127,19 @@ The new behavior from this feature will allow a user, for example, to set 1 of t
| V2NEXT | `false` | `true` |


### @aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway

*When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC.*

Flag type: Backwards incompatible bugfix

When this feature flag is enabled, EgressOnlyGateway resource will not be created when you create a vpc with only public subnets.


| Since | Default | Recommended |
| ----- | ----- | ----- |
| (not in v1) | | |
| V2NEXT | `false` | `true` |


<!-- END details -->
15 changes: 15 additions & 0 deletions packages/aws-cdk-lib/cx-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -732,3 +732,18 @@ _cdk.json_
}
}
```

* `@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway`

When this feature flag is enabled, EgressOnlyGateway is created only for dual-stack VPC with private subnets

When this feature flag is disabled, EgressOnlyGateway resource is created for all dual-stack VPC regardless of subnet type

_cdk.json_

```json
{
"context": {
"@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true
}
}
12 changes: 12 additions & 0 deletions packages/aws-cdk-lib/cx-api/lib/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export const DYNAMODB_TABLE_RETAIN_TABLE_REPLICA = '@aws-cdk/aws-dynamodb:retain
export const LOG_USER_POOL_CLIENT_SECRET_VALUE = '@aws-cdk/cognito:logUserPoolClientSecretValue';
export const PIPELINE_REDUCE_CROSS_ACCOUNT_ACTION_ROLE_TRUST_SCOPE = '@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope';
export const S3_TRUST_KEY_POLICY_FOR_SNS_SUBSCRIPTIONS = '@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions';
export const EC2_REQUIRE_PRIVATE_SUBNETS_FOR_EGRESSONLYINTERNETGATEWAY = '@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway';
export const USE_RESOURCEID_FOR_VPCV2_MIGRATION = '@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration';
export const S3_PUBLIC_ACCESS_BLOCKED_BY_DEFAULT = '@aws-cdk/aws-s3:publicAccessBlockedByDefault';

Expand Down Expand Up @@ -1578,6 +1579,17 @@ export const FLAGS: Record<string, FlagInfo> = {
},

//////////////////////////////////////////////////////////////////////
[EC2_REQUIRE_PRIVATE_SUBNETS_FOR_EGRESSONLYINTERNETGATEWAY]: {
type: FlagType.BugFix,
summary: 'When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC.',
detailsMd: `
When this feature flag is enabled, EgressOnlyGateway resource will not be created when you create a vpc with only public subnets.
`,
introducedIn: { v2: 'V2NEXT' },
recommendedValue: true,
},

/// ///////////////////////////////////////////////////////////////////
[USE_RESOURCEID_FOR_VPCV2_MIGRATION]: {
type: FlagType.ApiDefault,
summary: 'When enabled, use resource IDs for VPC V2 migration',
Expand Down
3 changes: 2 additions & 1 deletion packages/aws-cdk-lib/recommended-feature-flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,6 @@
"@aws-cdk/aws-dynamodb:retainTableReplica": true,
"@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true,
"@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true,
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true,
"@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true
}
Loading