From e02c6ccda4a5ab7afd98f05165e77782b703907e Mon Sep 17 00:00:00 2001 From: Clare Liguori Date: Wed, 25 Sep 2019 04:35:26 -0700 Subject: [PATCH] feat(ecs): add protocol option and default certificate for HTTPS services (#4120) * feat(ecs): add protocol option and default certificate for HTTPS services * swap protocol validation logic --- .../application-load-balanced-service-base.ts | 49 +- .../aws-ecs-patterns/test/ec2/test.l3s.ts | 106 ++- ...eg.https-fargate-service.lit.expected.json | 705 ++++++++++++++++++ .../integ.https-fargate-service.lit.ts | 35 + 4 files changed, 887 insertions(+), 8 deletions(-) create mode 100644 packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.https-fargate-service.lit.expected.json create mode 100644 packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.https-fargate-service.lit.ts 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 023bcfb1af74f..6f5057edbb61e 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 @@ -1,7 +1,7 @@ -import { ICertificate } from '@aws-cdk/aws-certificatemanager'; +import { DnsValidatedCertificate, ICertificate } from '@aws-cdk/aws-certificatemanager'; import { IVpc } from '@aws-cdk/aws-ec2'; import { AwsLogDriver, BaseService, Cluster, ContainerImage, ICluster, LogDriver, Secret } from '@aws-cdk/aws-ecs'; -import { ApplicationListener, ApplicationLoadBalancer, ApplicationTargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2'; +import { ApplicationListener, ApplicationLoadBalancer, ApplicationProtocol, ApplicationTargetGroup } from '@aws-cdk/aws-elasticloadbalancingv2'; import { IRole } from '@aws-cdk/aws-iam'; import { AddressRecordTarget, ARecord, IHostedZone } from '@aws-cdk/aws-route53'; import { LoadBalancerTarget } from '@aws-cdk/aws-route53-targets'; @@ -62,9 +62,11 @@ export interface ApplicationLoadBalancedServiceBaseProps { /** * Certificate Manager certificate to associate with the load balancer. - * Setting this option will set the load balancer port to 443. + * Setting this option will set the load balancer protocol to HTTPS. * - * @default - No certificate associated with the load balancer. + * @default - No certificate associated with the load balancer, if using + * the HTTP protocol. For HTTPS, a DNS-validated certificate will be + * created for the load balancer's specified domain name. */ readonly certificate?: ICertificate; @@ -89,6 +91,17 @@ export interface ApplicationLoadBalancedServiceBaseProps { */ readonly enableLogging?: boolean; + /** + * The protocol for connections from clients to the load balancer. + * The load balancer port is determined from the protocol (port 80 for + * HTTP, port 443 for HTTPS). A domain name and zone must be also be + * specified if using HTTPS. + * + * @default HTTP. If a certificate is specified, the protocol will be + * set by default to HTTPS. + */ + readonly protocol?: ApplicationProtocol; + /** * The domain name for the service, e.g. "api.example.com." * @@ -162,6 +175,9 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct { public readonly listener: ApplicationListener; public readonly targetGroup: ApplicationTargetGroup; + + public readonly certificate: ICertificate; + /** * The cluster that hosts the service. */ @@ -199,14 +215,33 @@ export abstract class ApplicationLoadBalancedServiceBase extends cdk.Construct { port: 80 }; + 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'); + } + const protocol = props.protocol !== undefined ? props.protocol : (props.certificate ? ApplicationProtocol.HTTPS : ApplicationProtocol.HTTP); + this.listener = this.loadBalancer.addListener('PublicListener', { - port: props.certificate !== undefined ? 443 : 80, + protocol, open: true }); this.targetGroup = this.listener.addTargets('ECS', targetProps); - if (props.certificate !== undefined) { - this.listener.addCertificateArns('Arns', [props.certificate.certificateArn]); + if (protocol === ApplicationProtocol.HTTPS) { + if (typeof props.domainName === 'undefined' || typeof props.domainZone === 'undefined') { + throw new Error('A domain name and zone is required when using the HTTPS protocol'); + } + + if (props.certificate !== undefined) { + this.certificate = props.certificate; + } else { + this.certificate = new DnsValidatedCertificate(this, 'Certificate', { + domainName: props.domainName, + hostedZone: props.domainZone + }); + } + } + if (this.certificate !== undefined) { + this.listener.addCertificateArns('Arns', [this.certificate.certificateArn]); } if (typeof props.domainName !== 'undefined') { diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts index 3db1e84fd3dce..8c8cec67f6526 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/test.l3s.ts @@ -3,6 +3,7 @@ import { Certificate } from '@aws-cdk/aws-certificatemanager'; import ec2 = require('@aws-cdk/aws-ec2'); import ecs = require('@aws-cdk/aws-ecs'); import { AwsLogDriver } from '@aws-cdk/aws-ecs'; +import { ApplicationProtocol } from '@aws-cdk/aws-elasticloadbalancingv2'; import { PublicHostedZone } from '@aws-cdk/aws-route53'; import cdk = require('@aws-cdk/core'); import { Test } from 'nodeunit'; @@ -176,7 +177,8 @@ export = { })); expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { - Port: 80 + Port: 80, + Protocol: 'HTTP' })); test.done(); @@ -249,6 +251,7 @@ export = { expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { Port: 443, + Protocol: 'HTTPS', Certificates: [{ CertificateArn: "helloworld" }] @@ -274,6 +277,69 @@ export = { test.done(); }, + 'test Fargateloadbalanced construct with TLS and default certificate'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); + + // WHEN + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + image: ecs.ContainerImage.fromRegistry('test'), + domainName: 'api.example.com', + domainZone: zone, + protocol: ApplicationProtocol.HTTPS + }); + + // THEN - stack contains a load balancer, a service, and a certificate + expect(stack).to(haveResource('AWS::CloudFormation::CustomResource', { + ServiceToken: { + 'Fn::GetAtt': [ + 'ServiceCertificateCertificateRequestorFunctionB69CD117', + 'Arn' + ] + }, + DomainName: 'api.example.com', + HostedZoneId: { + Ref: "HostedZoneDB99F866" + } + })); + + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::LoadBalancer')); + + expect(stack).to(haveResource('AWS::ElasticLoadBalancingV2::Listener', { + Port: 443, + Protocol: 'HTTPS', + Certificates: [{ + CertificateArn: { 'Fn::GetAtt': [ + 'ServiceCertificateCertificateRequestorResource0FC297E9', + 'Arn' + ]} + }] + })); + + expect(stack).to(haveResource("AWS::ECS::Service", { + DesiredCount: 1, + LaunchType: "FARGATE", + })); + + expect(stack).to(haveResource('AWS::Route53::RecordSet', { + Name: 'api.example.com.', + HostedZoneId: { + Ref: "HostedZoneDB99F866" + }, + Type: 'A', + AliasTarget: { + HostedZoneId: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'CanonicalHostedZoneID'] }, + DNSName: { 'Fn::GetAtt': ['ServiceLBE9A1ADBC', 'DNSName'] }, + } + })); + + test.done(); + }, + "errors when setting domainName but not domainZone"(test: Test) { // GIVEN const stack = new cdk.Stack(); @@ -291,6 +357,44 @@ export = { test.done(); }, + + 'errors when setting both HTTP protocol and certificate'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + test.throws(() => { + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + image: ecs.ContainerImage.fromRegistry('test'), + protocol: ApplicationProtocol.HTTP, + certificate: Certificate.fromCertificateArn(stack, 'Cert', 'helloworld') + }); + }); + + test.done(); + }, + + 'errors when setting HTTPS protocol but not domain name'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // THEN + test.throws(() => { + new ecsPatterns.ApplicationLoadBalancedFargateService(stack, 'Service', { + cluster, + image: ecs.ContainerImage.fromRegistry('test'), + protocol: ApplicationProtocol.HTTPS + }); + }); + + test.done(); + }, + 'test Fargate loadbalanced construct with optional log driver input'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.https-fargate-service.lit.expected.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.https-fargate-service.lit.expected.json new file mode 100644 index 0000000000000..d6c89f83b3391 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.https-fargate-service.lit.expected.json @@ -0,0 +1,705 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-fargate-https-integ/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/17", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "Name", + "Value": "aws-fargate-https-integ/Vpc/PublicSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-fargate-https-integ/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc" + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-fargate-https-integ/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/17", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "Name", + "Value": "aws-fargate-https-integ/Vpc/PrivateSubnet1" + }, + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-fargate-https-integ/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-fargate-https-integ/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "FargateCluster7CCD5F93": { + "Type": "AWS::ECS::Cluster" + }, + "ZoneA5DE4B68": { + "Type": "AWS::Route53::HostedZone", + "Properties": { + "Name": "example.com." + } + }, + "HttpsServiceLB2B7D4391": { + "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", + "Properties": { + "Scheme": "internet-facing", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "HttpsServiceLBSecurityGroup3315ED72", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + ], + "Type": "application" + }, + "DependsOn": [ + "VpcPublicSubnet1DefaultRoute3DA9E72A" + ] + }, + "HttpsServiceLBSecurityGroup3315ED72": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Automatically created Security Group for ELB awsfargatehttpsintegHttpsServiceLBCF9E1632", + "SecurityGroupIngress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow from anyone on port 443", + "FromPort": 443, + "IpProtocol": "tcp", + "ToPort": 443 + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "HttpsServiceLBSecurityGrouptoawsfargatehttpsintegHttpsServiceSecurityGroup7F7B1B9D8009693239": { + "Type": "AWS::EC2::SecurityGroupEgress", + "Properties": { + "GroupId": { + "Fn::GetAtt": [ + "HttpsServiceLBSecurityGroup3315ED72", + "GroupId" + ] + }, + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "DestinationSecurityGroupId": { + "Fn::GetAtt": [ + "HttpsServiceSecurityGroup1DB8E541", + "GroupId" + ] + }, + "FromPort": 80, + "ToPort": 80 + } + }, + "HttpsServiceLBPublicListenerA29C2E85": { + "Type": "AWS::ElasticLoadBalancingV2::Listener", + "Properties": { + "DefaultActions": [ + { + "TargetGroupArn": { + "Ref": "HttpsServiceLBPublicListenerECSGroupB82D39F0" + }, + "Type": "forward" + } + ], + "LoadBalancerArn": { + "Ref": "HttpsServiceLB2B7D4391" + }, + "Port": 443, + "Protocol": "HTTPS", + "Certificates": [ + { + "CertificateArn": { + "Fn::GetAtt": [ + "HttpsServiceCertificateCertificateRequestorResource8FEA57AA", + "Arn" + ] + } + } + ] + } + }, + "HttpsServiceLBPublicListenerECSGroupB82D39F0": { + "Type": "AWS::ElasticLoadBalancingV2::TargetGroup", + "Properties": { + "Port": 80, + "Protocol": "HTTP", + "TargetType": "ip", + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "HttpsServiceCertificateCertificateRequestorFunctionServiceRole8C94B3C2": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "HttpsServiceCertificateCertificateRequestorFunctionServiceRoleDefaultPolicy67F01126": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "acm:RequestCertificate", + "acm:DescribeCertificate", + "acm:DeleteCertificate" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "route53:GetChange", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "route53:changeResourceRecordSets", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:route53:::hostedzone/", + { + "Ref": "ZoneA5DE4B68" + } + ] + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "HttpsServiceCertificateCertificateRequestorFunctionServiceRoleDefaultPolicy67F01126", + "Roles": [ + { + "Ref": "HttpsServiceCertificateCertificateRequestorFunctionServiceRole8C94B3C2" + } + ] + } + }, + "HttpsServiceCertificateCertificateRequestorFunction2154F89F": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": { + "Ref": "HttpsServiceCertificateCertificateRequestorFunctionCodeS3Bucket7CB97C4F" + }, + "S3Key": { + "Fn::Join": [ + "", + [ + { + "Fn::Select": [ + 0, + { + "Fn::Split": [ + "||", + { + "Ref": "HttpsServiceCertificateCertificateRequestorFunctionCodeS3VersionKey6CF8F6B8" + } + ] + } + ] + }, + { + "Fn::Select": [ + 1, + { + "Fn::Split": [ + "||", + { + "Ref": "HttpsServiceCertificateCertificateRequestorFunctionCodeS3VersionKey6CF8F6B8" + } + ] + } + ] + } + ] + ] + } + }, + "Handler": "index.certificateRequestHandler", + "Role": { + "Fn::GetAtt": [ + "HttpsServiceCertificateCertificateRequestorFunctionServiceRole8C94B3C2", + "Arn" + ] + }, + "Runtime": "nodejs8.10", + "Timeout": 900 + }, + "DependsOn": [ + "HttpsServiceCertificateCertificateRequestorFunctionServiceRoleDefaultPolicy67F01126", + "HttpsServiceCertificateCertificateRequestorFunctionServiceRole8C94B3C2" + ] + }, + "HttpsServiceCertificateCertificateRequestorResource8FEA57AA": { + "Type": "AWS::CloudFormation::CustomResource", + "Properties": { + "ServiceToken": { + "Fn::GetAtt": [ + "HttpsServiceCertificateCertificateRequestorFunction2154F89F", + "Arn" + ] + }, + "DomainName": "test.example.com", + "HostedZoneId": { + "Ref": "ZoneA5DE4B68" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "HttpsServiceDNS5E40A908": { + "Type": "AWS::Route53::RecordSet", + "Properties": { + "Name": "test.example.com.", + "Type": "A", + "AliasTarget": { + "DNSName": { + "Fn::GetAtt": [ + "HttpsServiceLB2B7D4391", + "DNSName" + ] + }, + "HostedZoneId": { + "Fn::GetAtt": [ + "HttpsServiceLB2B7D4391", + "CanonicalHostedZoneID" + ] + } + }, + "HostedZoneId": { + "Ref": "ZoneA5DE4B68" + } + } + }, + "HttpsServiceTaskDefTaskRole0B461899": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "HttpsServiceTaskDef7A8C3AFB": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "HttpsServiceTaskDefwebLogGroup66A85C3A" + }, + "awslogs-stream-prefix": "HttpsService", + "awslogs-region": { + "Ref": "AWS::Region" + } + } + }, + "Name": "web", + "PortMappings": [ + { + "ContainerPort": 80, + "Protocol": "tcp" + } + ] + } + ], + "Cpu": "256", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "HttpsServiceTaskDefExecutionRole95184BE8", + "Arn" + ] + }, + "Family": "awsfargatehttpsintegHttpsServiceTaskDef8DF846FB", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "HttpsServiceTaskDefTaskRole0B461899", + "Arn" + ] + } + } + }, + "HttpsServiceTaskDefwebLogGroup66A85C3A": { + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "HttpsServiceTaskDefExecutionRole95184BE8": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "HttpsServiceTaskDefExecutionRoleDefaultPolicy593CA896": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "HttpsServiceTaskDefwebLogGroup66A85C3A", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "HttpsServiceTaskDefExecutionRoleDefaultPolicy593CA896", + "Roles": [ + { + "Ref": "HttpsServiceTaskDefExecutionRole95184BE8" + } + ] + } + }, + "HttpsService8599769C": { + "Type": "AWS::ECS::Service", + "Properties": { + "TaskDefinition": { + "Ref": "HttpsServiceTaskDef7A8C3AFB" + }, + "Cluster": { + "Ref": "FargateCluster7CCD5F93" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "DesiredCount": 1, + "EnableECSManagedTags": false, + "HealthCheckGracePeriodSeconds": 60, + "LaunchType": "FARGATE", + "LoadBalancers": [ + { + "ContainerName": "web", + "ContainerPort": 80, + "TargetGroupArn": { + "Ref": "HttpsServiceLBPublicListenerECSGroupB82D39F0" + } + } + ], + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "HttpsServiceSecurityGroup1DB8E541", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + ] + } + } + }, + "DependsOn": [ + "HttpsServiceLBPublicListenerECSGroupB82D39F0", + "HttpsServiceLBPublicListenerA29C2E85" + ] + }, + "HttpsServiceSecurityGroup1DB8E541": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-fargate-https-integ/HttpsService/Service/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "HttpsServiceSecurityGroupfromawsfargatehttpsintegHttpsServiceLBSecurityGroup2B62793C8017B5B73E": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "IpProtocol": "tcp", + "Description": "Load balancer to target", + "FromPort": 80, + "GroupId": { + "Fn::GetAtt": [ + "HttpsServiceSecurityGroup1DB8E541", + "GroupId" + ] + }, + "SourceSecurityGroupId": { + "Fn::GetAtt": [ + "HttpsServiceLBSecurityGroup3315ED72", + "GroupId" + ] + }, + "ToPort": 80 + } + } + }, + "Parameters": { + "HttpsServiceCertificateCertificateRequestorFunctionCodeS3Bucket7CB97C4F": { + "Type": "String", + "Description": "S3 bucket for asset \"aws-fargate-https-integ/HttpsService/Certificate/CertificateRequestorFunction/Code\"" + }, + "HttpsServiceCertificateCertificateRequestorFunctionCodeS3VersionKey6CF8F6B8": { + "Type": "String", + "Description": "S3 key for asset version \"aws-fargate-https-integ/HttpsService/Certificate/CertificateRequestorFunction/Code\"" + }, + "HttpsServiceCertificateCertificateRequestorFunctionCodeArtifactHashC69E3CE8": { + "Type": "String", + "Description": "Artifact hash for asset \"aws-fargate-https-integ/HttpsService/Certificate/CertificateRequestorFunction/Code\"" + } + }, + "Outputs": { + "HttpsServiceLoadBalancerDNSE2E79469": { + "Value": { + "Fn::GetAtt": [ + "HttpsServiceLB2B7D4391", + "DNSName" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.https-fargate-service.lit.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.https-fargate-service.lit.ts new file mode 100644 index 0000000000000..88fe9ac2ffc03 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.https-fargate-service.lit.ts @@ -0,0 +1,35 @@ +import ec2 = require('@aws-cdk/aws-ec2'); +import ecs = require('@aws-cdk/aws-ecs'); +import elb = require('@aws-cdk/aws-elasticloadbalancingv2'); +import route53 = require('@aws-cdk/aws-route53'); +import cdk = require('@aws-cdk/core'); + +import { ApplicationLoadBalancedFargateService } from '../../lib'; + +const app = new cdk.App(); + +class EventStack extends cdk.Stack { + constructor(scope: cdk.App, id: string) { + super(scope, id); + + const vpc = new ec2.Vpc(this, 'Vpc', { maxAzs: 1 }); + const cluster = new ecs.Cluster(this, 'FargateCluster', { vpc }); + + const domainZone = new route53.HostedZone(this, 'Zone', { + zoneName: 'example.com' + }); + + /// !show + new ApplicationLoadBalancedFargateService(this, 'HttpsService', { + cluster, + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + protocol: elb.ApplicationProtocol.HTTPS, + domainName: 'test.example.com', + domainZone + }); + /// !hide + } +} + +new EventStack(app, 'aws-fargate-https-integ'); +app.synth();