diff --git a/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts b/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts index 0279df6a967a4..7f37a9c652077 100644 --- a/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts +++ b/packages/aws-cdk-lib/aws-ec2/lib/vpc.ts @@ -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, }); diff --git a/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts b/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts index f5ae1743eae53..fa69738fa2617 100644 --- a/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts +++ b/packages/aws-cdk-lib/aws-ec2/test/vpc.test.ts @@ -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, @@ -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 diff --git a/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md b/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md index e7112c6f9c9e2..7aef148b24512 100644 --- a/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md +++ b/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md @@ -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 | @@ -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 } } ``` @@ -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` | + + diff --git a/packages/aws-cdk-lib/cx-api/README.md b/packages/aws-cdk-lib/cx-api/README.md index 4bc2a079a1b43..38db2a32d6a01 100644 --- a/packages/aws-cdk-lib/cx-api/README.md +++ b/packages/aws-cdk-lib/cx-api/README.md @@ -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 + } +} \ No newline at end of file diff --git a/packages/aws-cdk-lib/cx-api/lib/features.ts b/packages/aws-cdk-lib/cx-api/lib/features.ts index 026535906bfcd..1208979616281 100644 --- a/packages/aws-cdk-lib/cx-api/lib/features.ts +++ b/packages/aws-cdk-lib/cx-api/lib/features.ts @@ -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'; @@ -1578,6 +1579,17 @@ export const FLAGS: Record = { }, ////////////////////////////////////////////////////////////////////// + [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', diff --git a/packages/aws-cdk-lib/recommended-feature-flags.json b/packages/aws-cdk-lib/recommended-feature-flags.json index b847d4afb96b6..625fe4ae7c8ab 100644 --- a/packages/aws-cdk-lib/recommended-feature-flags.json +++ b/packages/aws-cdk-lib/recommended-feature-flags.json @@ -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 } \ No newline at end of file