diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/aws-cdk-lib/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index 7225753d7eec0..26d6e4211c0a2 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -15,7 +15,8 @@ import { IRole } from '../../../aws-iam'; import { ARecord, IHostedZone, RecordTarget, CnameRecord } from '../../../aws-route53'; import { LoadBalancerTarget } from '../../../aws-route53-targets'; import * as cdk from '../../../core'; -import { Duration } from '../../../core'; +import { Duration, FeatureFlags } from '../../../core'; +import * as cxapi from '../../../cx-api'; /** * Describes the type of DNS record the service should create @@ -474,7 +475,8 @@ export abstract class ApplicationLoadBalancedServiceBase extends Construct { this.desiredCount = props.desiredCount || 1; this.internalDesiredCount = props.desiredCount; - const internetFacing = props.publicLoadBalancer ?? true; + const internetFacing = props.publicLoadBalancer ?? + FeatureFlags.of(this).isEnabled(cxapi.ECS_PATTERNS_FARGATE_SERVICE_BASE_HAS_PUBLIC_LB_BY_DEFAULT); if (props.idleTimeout) { const idleTimeout = props.idleTimeout.toSeconds(); @@ -486,7 +488,7 @@ export abstract class ApplicationLoadBalancedServiceBase extends Construct { const lbProps: ApplicationLoadBalancerProps = { vpc: this.cluster.vpc, loadBalancerName: props.loadBalancerName, - internetFacing, + internetFacing: internetFacing, idleTimeout: props.idleTimeout, ipAddressType: props.ipAddressType, }; diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts b/packages/aws-cdk-lib/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts index 9edbf5011b2f1..f42369f94569d 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts @@ -9,6 +9,8 @@ import { IRole } from '../../../aws-iam'; import { ARecord, CnameRecord, IHostedZone, RecordTarget } from '../../../aws-route53'; import { LoadBalancerTarget } from '../../../aws-route53-targets'; import * as cdk from '../../../core'; +import { FeatureFlags } from '../../../core'; +import * as cxapi from '../../../cx-api/index'; /** * Describes the type of DNS record the service should create @@ -368,11 +370,12 @@ export abstract class NetworkLoadBalancedServiceBase extends Construct { this.desiredCount = props.desiredCount || 1; this.internalDesiredCount = props.desiredCount; - const internetFacing = props.publicLoadBalancer ?? true; + const internetFacing = props.publicLoadBalancer ?? + FeatureFlags.of(this).isEnabled(cxapi.ECS_PATTERNS_FARGATE_SERVICE_BASE_HAS_PUBLIC_LB_BY_DEFAULT); const lbProps: NetworkLoadBalancerProps = { vpc: this.cluster.vpc, - internetFacing, + internetFacing: internetFacing, ipAddressType: props.ipAddressType, }; diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s.test.ts index 6e5885fd45ab0..cb79c4b5800f4 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/ec2/l3s.test.ts @@ -9,6 +9,7 @@ import { ApplicationLoadBalancer, ApplicationProtocol, ApplicationProtocolVersio import { PublicHostedZone } from '../../../aws-route53'; import * as cloudmap from '../../../aws-servicediscovery'; import * as cdk from '../../../core'; +import * as cxapi from '../../../cx-api'; import * as ecsPatterns from '../../lib'; describe('ApplicationLoadBalancedEc2Service', () => { @@ -81,6 +82,76 @@ describe('ApplicationLoadBalancedEc2Service', () => { }); }); + test('ECS loadbalanced construct with feature flag private lb override', () => { + // GIVEN + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ECS_PATTERNS_FARGATE_SERVICE_BASE_HAS_PUBLIC_LB_BY_DEFAULT, false); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 1024, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + entryPoint: ['echo', 'ecs-is-awesome'], + command: ['/bin/bash'], + }, + desiredCount: 2, + ipAddressType: IpAddressType.DUAL_STACK, + }); + + // THEN - stack contains a load balancer and a service + Template.fromStack(stack).resourceCountIs('AWS::ElasticLoadBalancingV2::LoadBalancer', 1); + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Scheme: 'internal', + Type: 'application', + IpAddressType: 'dualstack', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + DesiredCount: 2, + LaunchType: 'EC2', + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { + ContainerDefinitions: [ + Match.objectLike({ + Environment: [ + { + Name: 'TEST_ENVIRONMENT_VARIABLE1', + Value: 'test environment variable 1 value', + }, + { + Name: 'TEST_ENVIRONMENT_VARIABLE2', + Value: 'test environment variable 2 value', + }, + ], + Memory: 1024, + DockerLabels: { + label1: 'labelValue1', + label2: 'labelValue2', + }, + EntryPoint: ['echo', 'ecs-is-awesome'], + Command: ['/bin/bash'], + }), + ], + }); + }); + test('multiple capacity provider strategies are set', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts index 5e761e2c30a44..7188ebcfce703 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts @@ -7,6 +7,7 @@ import { ApplicationProtocol, IpAddressType, SslPolicy } from '../../../aws-elas import { CompositePrincipal, Role, ServicePrincipal } from '../../../aws-iam'; import { PublicHostedZone } from '../../../aws-route53'; import { Duration, Stack } from '../../../core'; +import * as cxapi from '../../../cx-api'; import { ApplicationLoadBalancedFargateService, ApplicationMultipleTargetGroupsFargateService, NetworkLoadBalancedFargateService, NetworkMultipleTargetGroupsFargateService } from '../../lib'; const enableExecuteCommandPermissions = { @@ -139,6 +140,33 @@ describe('Application Load Balancer', () => { IpAddressType: 'dualstack', }); }); + + test('dualstack application load balancer with feature flag override for private lb', () => { + // GIVEN + const stack = new Stack(); + stack.node.setContext(cxapi.ECS_PATTERNS_FARGATE_SERVICE_BASE_HAS_PUBLIC_LB_BY_DEFAULT, false); + + const vpc = new Vpc(stack, 'VPC', { + ipProtocol: IpProtocol.DUAL_STACK, + }); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + }, + ipAddressType: IpAddressType.DUAL_STACK, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Scheme: 'internal', + Type: 'application', + IpAddressType: 'dualstack', + }); + }); }); describe('ApplicationMultipleTargetGroupsFargateService', () => { diff --git a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts index 9d81dbe53ea04..066be45fe8fe1 100644 --- a/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts +++ b/packages/aws-cdk-lib/aws-ecs-patterns/test/fargate/load-balanced-fargate-service.test.ts @@ -10,6 +10,7 @@ import * as iam from '../../../aws-iam'; import * as route53 from '../../../aws-route53'; import * as cloudmap from '../../../aws-servicediscovery'; import * as cdk from '../../../core'; +import * as cxapi from '../../../cx-api'; import * as ecsPatterns from '../../lib'; describe('ApplicationLoadBalancedFargateService', () => { @@ -1457,6 +1458,28 @@ describe('NetworkLoadBalancedFargateService', () => { }); }); + test('setting loadBalancerType to Network with feature flag override creates an NLB private', () => { + // GIVEN + const stack = new cdk.Stack(); + stack.node.setContext(cxapi.ECS_PATTERNS_FARGATE_SERVICE_BASE_HAS_PUBLIC_LB_BY_DEFAULT, false); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.NetworkLoadBalancedFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('/aws/aws-example-app'), + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', { + Type: 'network', + Scheme: 'internal', + }); + }); + test('setting loadBalancerType to Network and publicLoadBalancer to false creates an NLB Private', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md b/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md index ff008f4ea1225..25b1dfa973b77 100644 --- a/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md +++ b/packages/aws-cdk-lib/cx-api/FEATURE_FLAGS.md @@ -81,6 +81,7 @@ Flags come in three types: | [@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics](#aws-cdkcorecfnincluderejectcomplexresourceupdatecreatepolicyintrinsics) | When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values. | 2.161.0 | (fix) | | [@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy](#aws-cdkaws-stepfunctions-tasksfixrunecstaskpolicy) | When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN. | 2.163.0 | (fix) | | [@aws-cdk/aws-dynamodb:resourcePolicyPerReplica](#aws-cdkaws-dynamodbresourcepolicyperreplica) | When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas | 2.164.0 | (fix) | +| [@aws-cdk/aws-ecs-patterns:fargateServiceBaseHasPublicLBDefault](#aws-ecs-patterns-fargateServiceBaseHasPublicLBDefault) | When enabled LBs created for Fargate Service will be public by default | 2.172.0 | (fix) | @@ -150,7 +151,8 @@ The following json shows the current recommended set of flags, as `cdk init` wou "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, - "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, + "@aws-cdk/aws-ecs-patterns:fargateServiceBaseHasPublicLBDefault": false } } ``` @@ -1528,5 +1530,20 @@ This is a feature flag as the old behavior was technically incorrect but users m | (not in v1) | | | | 2.164.0 | `false` | `true` | +### @aws-cdk/aws-ecs-patterns:fargateServiceBaseHasPublicLBDefault + +*When enabled LBs created for Fargate Service will be public by default* (fix) + +If this flag is not set, the default behavior for `ApplicationLoadBalancedFargateService` and `NetworkLoadBalancedFargateService` is to create a public load balancer. + +If this flag is set to false, the behavior is that the load balancer will be private by default. + +This is a feature flag as to keep compatibility with the old behavior. + + +| Since | Default | Recommended | +|-------------| ----- | ----- | +| (not in v1) | | | +| 2.172.0 | `true` | `false` | diff --git a/packages/aws-cdk-lib/cx-api/lib/features.ts b/packages/aws-cdk-lib/cx-api/lib/features.ts index 9bc3d0d5a8977..e91e910f97578 100644 --- a/packages/aws-cdk-lib/cx-api/lib/features.ts +++ b/packages/aws-cdk-lib/cx-api/lib/features.ts @@ -115,6 +115,7 @@ export const USE_CORRECT_VALUE_FOR_INSTANCE_RESOURCE_ID_PROPERTY = '@aws-cdk/aws export const CFN_INCLUDE_REJECT_COMPLEX_RESOURCE_UPDATE_CREATE_POLICY_INTRINSICS = '@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics'; export const LAMBDA_NODEJS_SDK_V3_EXCLUDE_SMITHY_PACKAGES = '@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages'; export const STEPFUNCTIONS_TASKS_FIX_RUN_ECS_TASK_POLICY = '@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy'; +export const ECS_PATTERNS_FARGATE_SERVICE_BASE_HAS_PUBLIC_LB_BY_DEFAULT = '@aws-cdk/ecs-patterns:fargateServiceBaseHasPublicLBDefault'; export const FLAGS: Record = { ////////////////////////////////////////////////////////////////////// @@ -1250,6 +1251,22 @@ export const FLAGS: Record = { introducedIn: { v2: '2.163.0' }, recommendedValue: true, }, + + ////////////////////////////////////////////////////////////////////// + [ECS_PATTERNS_FARGATE_SERVICE_BASE_HAS_PUBLIC_LB_BY_DEFAULT]: { + type: FlagType.BugFix, + summary: 'When enabled, the load balancers created will be public by default', + detailsMd: ` + By default, the load balancers created by ECS Patterns Fargate Service Base construct are public. + This is not ideal for cases where you need everything to be private. + + When this feature flag is enabled (default True for compatability), the load balancers will be public by default. + However, if you want to make them private by default, you can set this property to false. + `, + introducedIn: { v2: '2.172.0' }, + defaults: { v2: true }, + recommendedValue: false, + }, }; const CURRENT_MV = 'v2';