diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index 22d4798ea7f36..ffee66fe6cc96 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -62,6 +62,8 @@ You can customize the health check for your target group; otherwise it defaults Fargate services will use the `LATEST` platform version by default, but you can override by providing a value for the `platformVersion` property in the constructor. +By setting `redirectHTTP` to true, CDK will automatically create a listener on port 80 that redirects HTTP traffic to the HTTPS port. + Additionally, if more than one application target group are needed, instantiate one of the following: * `ApplicationMultipleTargetGroupsEc2Service` diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index afd8bed71247b..0edd9e70d0171 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -3,7 +3,7 @@ import { IVpc } from '@aws-cdk/aws-ec2'; import { AwsLogDriver, BaseService, CloudMapOptions, Cluster, ContainerImage, ICluster, LogDriver, PropagatedTagSource, Secret } from '@aws-cdk/aws-ecs'; import { ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup, - IApplicationLoadBalancer, ListenerCertificate, + IApplicationLoadBalancer, ListenerCertificate, ListenerAction, } from '@aws-cdk/aws-elasticloadbalancingv2'; import { IRole } from '@aws-cdk/aws-iam'; import { ARecord, IHostedZone, RecordTarget } from '@aws-cdk/aws-route53'; @@ -168,6 +168,14 @@ export interface ApplicationLoadBalancedServiceBaseProps { * @default - AWS Cloud Map service discovery is not enabled. */ readonly cloudMapOptions?: CloudMapOptions; + + /** + * Specifies whether the load balancer should redirect traffic on port 80 to port 443 to support HTTP->HTTPS redirects + * This is only valid if the protocol of the ALB is HTTPS + * + * @default false + */ + readonly redirectHTTP?: boolean; } export interface ApplicationLoadBalancedTaskImageOptions { @@ -276,6 +284,11 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct { */ public readonly listener: ApplicationListener; + /** + * The redirect listener for the service if redirectHTTP is enabled. + */ + public readonly redirectListener?: ApplicationListener; + /** * The target group for the service. */ @@ -322,6 +335,9 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct { if (props.certificate !== undefined && props.protocol !== undefined && props.protocol !== ApplicationProtocol.HTTPS) { throw new Error('The HTTPS protocol must be used when a certificate is given'); } + if (props.protocol !== ApplicationProtocol.HTTPS && props.redirectHTTP === true) { + throw new Error('The HTTPS protocol must be used when redirecting HTTP traffic'); + } const protocol = props.protocol !== undefined ? props.protocol : (props.certificate ? ApplicationProtocol.HTTPS : ApplicationProtocol.HTTP); @@ -353,6 +369,18 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct { if (this.certificate !== undefined) { this.listener.addCertificates('Arns', [ListenerCertificate.fromCertificateManager(this.certificate)]); } + if (props.redirectHTTP) { + this.redirectListener = loadBalancer.addListener('PublicRedirectListener', { + protocol: ApplicationProtocol.HTTP, + port: 80, + open: true, + defaultAction: ListenerAction.redirect({ + port: props.listenerPort?.toString() || '443', + protocol: ApplicationProtocol.HTTPS, + permanent: true, + }), + }); + } let domainName = loadBalancer.loadBalancerDnsName; if (typeof props.domainName !== 'undefined') { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json index 57ef1e2c0e4a9..9a49968b7df63 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.expected.json @@ -396,6 +396,13 @@ "FromPort": 443, "IpProtocol": "tcp", "ToPort": 443 + }, + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 80", + "FromPort": 80, + "IpProtocol": "tcp", + "ToPort": 80 } ], "VpcId": { @@ -460,6 +467,26 @@ } } }, + "myServiceLBPublicRedirectListener0EEF9DCA": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "RedirectConfig": { + "Port": "443", + "Protocol": "HTTPS", + "StatusCode": "HTTP_301" + }, + "Type": "redirect" + } + ], + "LoadBalancerArn": { + "Ref": "myServiceLB168895E1" + }, + "Port": 80, + "Protocol": "HTTP" + } + }, "myServiceCertificate152F9DDA": { "Type": "AWS::CertificateManager::Certificate", "Properties": { @@ -480,7 +507,7 @@ "Type": "A", "AliasTarget": { "DNSName": { - "Fn::Join": + "Fn::Join": [ "", [ diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts index f4ecc0f0a5616..aac90206ac978 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.load-balanced-fargate-service.ts @@ -27,6 +27,7 @@ new ApplicationLoadBalancedFargateService(stack, 'myService', { stack, node: stack.node, }, + redirectHTTP: true, }); app.synth(); \ No newline at end of file