Description: > This template deploys a VPC, with a pair of public and private subnets spread across two Availabilty Zones. It deploys an Internet Gateway, with a default route on the public subnets. It deploys a pair of NAT Gateways (one in each AZ), and default routes for them in the private subnets. Parameters: EnvironmentName: Description: An environment name that will be prefixed to resource names Type: String VpcCIDR: Description: Please enter the IP range (CIDR notation) for this VPC Type: String Default: 10.0.0.0/16 PublicSubnet1CIDR: Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone Type: String Default: 10.0.0.0/19 PublicSubnet2CIDR: Description: Please enter the IP range (CIDR notation) for the public subnet in the second Availability Zone Type: String Default: 10.0.32.0/19 PrivateSubnet1CIDR: Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone Type: String Default: 10.0.64.0/19 PrivateSubnet2CIDR: Description: Please enter the IP range (CIDR notation) for the private subnet in the second Availability Zone Type: String Default: 10.0.96.0/19 LogGroupName: Type: String Default: "AppMeshExamples/mtls-acm-appmesh" ECSServiceLogGroupRetentionInDays: Type: Number Default: 30 ECSServicesDomain: Type: String Description: "Domain name registered under Route-53 that will be used for Service Discovery" Default: mtls-ec2.svc.cluster.local TlsState: Type: String Default: mtls Description: State of TLS in AWS App Mesh AllowedValues: - no-tls - 1way-tls - mtls AppMeshMeshName: Type: String Description: Name of mesh EnvoyImageName: Type: String Description: The image to use for the Envoy container ColorTellerImageName: Description: The name for the color teller image Type: String EC2Ami: Description: EC2 AMI ID Type: AWS::SSM::Parameter::Value Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" Conditions: 1wayTls: !Equals - !Ref TlsState - 1way-tls mtls: !Equals - !Ref TlsState - mtls setTls: !Or - Condition: mtls - Condition: 1wayTls Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCIDR EnableDnsHostnames: true Tags: - Key: Name Value: !Ref EnvironmentName InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Ref EnvironmentName InternetGatewayAttachment: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC PublicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PublicSubnet1CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName} Public Subnet (AZ1) PublicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Ref PublicSubnet2CIDR MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub ${EnvironmentName} Public Subnet (AZ2) PrivateSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 0, !GetAZs '' ] CidrBlock: !Ref PrivateSubnet1CIDR MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub ${EnvironmentName} Private Subnet (AZ1) - Key: "kubernetes.io/role/internal-elb" Value: "1" PrivateSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPC AvailabilityZone: !Select [ 1, !GetAZs '' ] CidrBlock: !Ref PrivateSubnet2CIDR MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub ${EnvironmentName} Private Subnet (AZ2) - Key: "kubernetes.io/role/internal-elb" Value: "1" NatGateway1EIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc NatGateway2EIP: Type: AWS::EC2::EIP DependsOn: InternetGatewayAttachment Properties: Domain: vpc NatGateway1: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGateway1EIP.AllocationId SubnetId: !Ref PublicSubnet1 NatGateway2: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NatGateway2EIP.AllocationId SubnetId: !Ref PublicSubnet2 PublicRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName} Public Routes DefaultPublicRoute: Type: AWS::EC2::Route DependsOn: InternetGatewayAttachment Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet1 PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet2 PrivateRouteTable1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName} Private Routes (AZ1) DefaultPrivateRoute1: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable1 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway1 PrivateSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable1 SubnetId: !Ref PrivateSubnet1 PrivateRouteTable2: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${EnvironmentName} Private Routes (AZ2) DefaultPrivateRoute2: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable2 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway2 PrivateSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable2 SubnetId: !Ref PrivateSubnet2 AcmPcaColorTellerRootCA: Type: AWS::ACMPCA::CertificateAuthority Properties: Type: ROOT KeyAlgorithm: RSA_2048 SigningAlgorithm: SHA256WITHRSA Subject: CommonName: "AcmPcaColorTeller" AcmPcaColorTellerRootCACert: Type: 'AWS::ACMPCA::Certificate' Properties: CertificateAuthorityArn: !Ref AcmPcaColorTellerRootCA CertificateSigningRequest: !GetAtt - AcmPcaColorTellerRootCA - CertificateSigningRequest SigningAlgorithm: SHA256WITHRSA TemplateArn: 'arn:aws:acm-pca:::template/RootCACertificate/V1' Validity: Type: YEARS Value: 10 AcmPcaColorTellerRootCAActivation: Type: 'AWS::ACMPCA::CertificateAuthorityActivation' Properties: CertificateAuthorityArn: !Ref AcmPcaColorTellerRootCA Certificate: !GetAtt - AcmPcaColorTellerRootCACert - Certificate Status: ACTIVE AcmPcaColorTellerEndpointCert: Type: AWS::CertificateManager::Certificate DependsOn: AcmPcaColorTellerRootCAActivation Properties: CertificateAuthorityArn: !Ref AcmPcaColorTellerRootCA DomainName: !Sub 'colorteller.${ECSServicesDomain}' AcmPcaColorGatewayRootCA: Type: AWS::ACMPCA::CertificateAuthority Properties: Type: ROOT KeyAlgorithm: RSA_2048 SigningAlgorithm: SHA256WITHRSA Subject: CommonName: "AcmPcaColorGateway" AcmPcaColorGatewayRootCACert: Type: 'AWS::ACMPCA::Certificate' Properties: CertificateAuthorityArn: !Ref AcmPcaColorGatewayRootCA CertificateSigningRequest: !GetAtt - AcmPcaColorGatewayRootCA - CertificateSigningRequest SigningAlgorithm: SHA256WITHRSA TemplateArn: 'arn:aws:acm-pca:::template/RootCACertificate/V1' Validity: Type: YEARS Value: 10 AcmPcaColorGatewayRootCAActivation: Type: 'AWS::ACMPCA::CertificateAuthorityActivation' Properties: CertificateAuthorityArn: !Ref AcmPcaColorGatewayRootCA Certificate: !GetAtt - AcmPcaColorGatewayRootCACert - Certificate Status: ACTIVE AcmPcaColorGatewayEndpointCert: Type: AWS::CertificateManager::Certificate DependsOn: AcmPcaColorGatewayRootCAActivation Properties: CertificateAuthorityArn: !Ref AcmPcaColorGatewayRootCA DomainName: !Sub 'colorgateway.${ECSServicesDomain}' ECSCluster: Type: AWS::ECS::Cluster Properties: ClusterName: !Ref EnvironmentName ECSServiceSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: "Security group for the service" VpcId: !Ref VPC SecurityGroupIngress: - CidrIp: !GetAtt VPC.CidrBlock IpProtocol: -1 TaskIamRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: [ "ecs-tasks.amazonaws.com" ] Action: sts:AssumeRole ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchFullAccess' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AWSAppMeshEnvoyAccess' Policies: - PolicyName: access-to-acm PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: acm:ExportCertificate Resource: - !Ref AcmPcaColorTellerEndpointCert - !Ref AcmPcaColorGatewayEndpointCert - Effect: Allow Action: acm-pca:GetCertificateAuthorityCertificate Resource: - !Ref AcmPcaColorTellerRootCA - !Ref AcmPcaColorGatewayRootCA TaskExecutionIamRole: Type: AWS::IAM::Role Properties: Path: / AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: [ "ecs-tasks.amazonaws.com" ] Action: sts:AssumeRole ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/CloudWatchFullAccess' - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly' Policies: - PolicyName: secretsmanager-access PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: 'secretsmanager:GetSecretValue' Resource: !Ref SecretCert ECSServiceLogGroup: Type: 'AWS::Logs::LogGroup' Properties: LogGroupName: !Ref LogGroupName RetentionInDays: !Ref ECSServiceLogGroupRetentionInDays ECSServiceDiscoveryNamespace: Type: AWS::ServiceDiscovery::PrivateDnsNamespace Properties: Vpc: !Ref VPC Name: !Ref ECSServicesDomain Mesh: Type: 'AWS::AppMesh::Mesh' Properties: MeshName: !Ref AppMeshMeshName Gateway: Type: 'AWS::AppMesh::VirtualGateway' Properties: MeshName: !GetAtt Mesh.MeshName VirtualGatewayName: gateway-vgw Spec: !If - setTls - !If - 1wayTls - BackendDefaults: ClientPolicy: TLS: Enforce: true Validation: Trust: ACM: CertificateAuthorityArns: - !Ref AcmPcaColorTellerRootCA Listeners: - PortMapping: Port: 9080 Protocol: http - BackendDefaults: ClientPolicy: TLS: Certificate: File: CertificateChain: /keys/colorgateway_endpoint_cert_chain.pem PrivateKey: /keys/colorgateway_endpoint_dec_pri_key.pem Enforce: true Validation: Trust: ACM: CertificateAuthorityArns: - !Ref AcmPcaColorTellerRootCA Listeners: - PortMapping: Port: 9080 Protocol: http - Listeners: - PortMapping: Port: 9080 Protocol: http Service: Type: 'AWS::AppMesh::VirtualService' Properties: MeshName: !GetAtt Mesh.MeshName VirtualServiceName: !Sub colorteller.${ECSServicesDomain} Spec: Provider: VirtualNode: VirtualNodeName: !GetAtt Node.VirtualNodeName Route: Type: 'AWS::AppMesh::GatewayRoute' Properties: MeshName: !GetAtt Mesh.MeshName VirtualGatewayName: gateway-vgw GatewayRouteName: colorteller-route Spec: HttpRoute: Action: Target: VirtualService: VirtualServiceName: !GetAtt Service.VirtualServiceName Match: Prefix: / Node: Type: 'AWS::AppMesh::VirtualNode' Properties: MeshName: !GetAtt Mesh.MeshName VirtualNodeName: colorteller-vn Spec: Listeners: - PortMapping: Port: 9080 Protocol: http TLS: !If - setTls - !If - 1wayTls - Certificate: ACM: CertificateArn: !Ref AcmPcaColorTellerEndpointCert Mode: STRICT - Certificate: ACM: CertificateArn: !Ref AcmPcaColorTellerEndpointCert Mode: STRICT Validation: Trust: File: CertificateChain: /keys/colorgateway_endpoint_cert_chain.pem - !Ref 'AWS::NoValue' ServiceDiscovery: AWSCloudMap: NamespaceName: !Ref ECSServicesDomain ServiceName: colorteller SecretCert: Type: AWS::SecretsManager::Secret Properties: SecretString: | { "GatewayCertificate": "tempcert", "GatewayCertificateChain": "tempcertchain", "GatewayPrivateKey": "privatekey", "Passphrase": "passphrase" } InitCertRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: [lambda.amazonaws.com] Action: sts:AssumeRole ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Policies: - PolicyName: acm-secretsmanager-access PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: acm:ExportCertificate Resource: - !Ref AcmPcaColorTellerEndpointCert - !Ref AcmPcaColorGatewayEndpointCert - Effect: Allow Action: acm-pca:GetCertificateAuthorityCertificate Resource: - !Ref AcmPcaColorTellerRootCA - !Ref AcmPcaColorGatewayRootCA - Effect: Allow Action: - secretsmanager:GetRandomPassword Resource: '*' - Effect: Allow Action: secretsmanager:PutSecretValue Resource: !Ref SecretCert InitCertFunction: Type: AWS::Lambda::Function Properties: Description: Initial function to populate secrets manager from ACM Handler: index.lambda_handler Role: !GetAtt InitCertRole.Arn Runtime: python3.8 Timeout: 900 Environment: Variables: COLOR_GATEWAY_ACM_ARN: !Ref AcmPcaColorGatewayEndpointCert SECRET: !Ref SecretCert Code: ZipFile: | import json import boto3 import base64 import os import cfnresponse sm = boto3.client('secretsmanager') cm = boto3.client('acm') gate_cm = os.environ['COLOR_GATEWAY_ACM_ARN'] secret = os.environ['SECRET'] def lambda_handler(event, context): print (json.dumps(event)) if (event['RequestType'] == 'Delete') or (event['RequestType'] == 'Update'): cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, '') elif event['RequestType'] == 'Create': try: passphrase = sm.get_random_password(ExcludePunctuation=True)['RandomPassword'] passphrase_enc = base64.b64encode(passphrase.encode('utf-8')) gate_rsp = cm.export_certificate(CertificateArn=gate_cm, Passphrase=passphrase_enc) sm_value={} sm_value['GatewayCertificate']=gate_rsp['Certificate'] sm_value['GatewayCertificateChain']=gate_rsp['CertificateChain'] sm_value['GatewayPrivateKey']=gate_rsp['PrivateKey'] sm_value['Passphrase']=passphrase sm.put_secret_value(SecretId=secret, SecretString=json.dumps(sm_value)) cfnresponse.send(event, context, cfnresponse.SUCCESS, {}, '') except Exception as e: print(e) cfnresponse.send(event, context, cfnresponse.FAILED, {}, '') InitCert: Type: Custom::InitCert Properties: ServiceToken: !GetAtt InitCertFunction.Arn Secret: !Ref SecretCert ColorTellerServiceDiscoveryRecord: Type: 'AWS::ServiceDiscovery::Service' Properties: Name: "colorteller" DnsConfig: NamespaceId: !Ref ECSServiceDiscoveryNamespace DnsRecords: - Type: A TTL: 300 HealthCheckCustomConfig: FailureThreshold: 1 ColorTellerTaskDefinition: Type: AWS::ECS::TaskDefinition Properties: RequiresCompatibilities: - 'FARGATE' Family: 'colorteller' NetworkMode: 'awsvpc' Cpu: 256 Memory: 512 TaskRoleArn: !GetAtt TaskIamRole.Arn ExecutionRoleArn: !GetAtt TaskExecutionIamRole.Arn ProxyConfiguration: Type: 'APPMESH' ContainerName: 'envoy' ProxyConfigurationProperties: - Name: 'IgnoredUID' Value: '1337' - Name: 'ProxyIngressPort' Value: '15000' - Name: 'ProxyEgressPort' Value: '15001' - Name: 'AppPorts' Value: '9080' - Name: 'EgressIgnoredIPs' Value: '169.254.170.2,169.254.169.254' ContainerDefinitions: - Name: 'app' Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ColorTellerImageName}' Essential: true DependsOn: - ContainerName: 'envoy' Condition: 'HEALTHY' LogConfiguration: LogDriver: 'awslogs' Options: awslogs-group: !Ref ECSServiceLogGroup awslogs-region: !Ref 'AWS::Region' awslogs-stream-prefix: 'colorteller' PortMappings: - ContainerPort: 9080 Protocol: 'http' Environment: - Name: 'PORT' Value: 9080 - Name: 'COLOR' Value: 'yellow' - Name: envoy Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EnvoyImageName}' Essential: true User: '1337' Ulimits: - Name: "nofile" HardLimit: 15000 SoftLimit: 15000 PortMappings: - ContainerPort: 9901 Protocol: 'tcp' HealthCheck: Command: - 'CMD-SHELL' - 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE' Interval: 5 Timeout: 2 Retries: 3 LogConfiguration: LogDriver: 'awslogs' Options: awslogs-group: !Ref ECSServiceLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: 'colorteller' Environment: - Name: 'APPMESH_RESOURCE_ARN' Value: !Sub 'mesh/${AppMeshMeshName}/virtualNode/colorteller-vn' - Name: AWS_REGION Value: !Ref 'AWS::Region' - Name: ENVOY_LOG_LEVEL Value: warning # Set secret environment variable for the container Secrets: - Name: CertSecret ValueFrom: !Ref SecretCert ColorTellerService: Type: 'AWS::ECS::Service' DependsOn: InitCert Properties: Cluster: !Ref ECSCluster DeploymentConfiguration: MaximumPercent: 200 MinimumHealthyPercent: 100 DesiredCount: 1 LaunchType: FARGATE ServiceRegistries: - RegistryArn: 'Fn::GetAtt': ColorTellerServiceDiscoveryRecord.Arn NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: DISABLED SecurityGroups: - !Ref ECSServiceSecurityGroup Subnets: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 TaskDefinition: !Ref ColorTellerTaskDefinition PublicLoadBalancerSG: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Access to the public facing load balancer VpcId: !Ref VPC SecurityGroupIngress: - CidrIp: 0.0.0.0/0 IpProtocol: tcp FromPort: 80 ToPort: 80 GatewayServiceDiscoveryRecord: Type: 'AWS::ServiceDiscovery::Service' Properties: Name: "gateway" DnsConfig: NamespaceId: !Ref ECSServiceDiscoveryNamespace DnsRecords: - Type: A TTL: 300 HealthCheckCustomConfig: FailureThreshold: 1 GatewayTaskDefinition: Type: AWS::ECS::TaskDefinition Properties: RequiresCompatibilities: - 'FARGATE' Family: 'gateway' NetworkMode: 'awsvpc' Cpu: 256 Memory: 512 TaskRoleArn: !GetAtt TaskIamRole.Arn ExecutionRoleArn: !GetAtt TaskExecutionIamRole.Arn ContainerDefinitions: - Name: envoy Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EnvoyImageName}' Essential: true User: '1337' Ulimits: - Name: "nofile" HardLimit: 15000 SoftLimit: 15000 PortMappings: - ContainerPort: 9080 Protocol: 'tcp' - ContainerPort: 9901 Protocol: 'tcp' HealthCheck: Command: - 'CMD-SHELL' - 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE' Interval: 5 Timeout: 2 Retries: 3 LogConfiguration: LogDriver: 'awslogs' Options: awslogs-group: !Ref ECSServiceLogGroup awslogs-region: !Ref AWS::Region awslogs-stream-prefix: 'gateway' Environment: - Name: 'APPMESH_RESOURCE_ARN' Value: !Sub 'mesh/${AppMeshMeshName}/virtualGateway/gateway-vgw' - Name: AWS_REGION Value: !Ref 'AWS::Region' - Name: ENVOY_LOG_LEVEL Value: warning # Set secret environment variable for the container Secrets: - Name: CertSecret ValueFrom: !Ref SecretCert GatewayService: Type: 'AWS::ECS::Service' DependsOn: InitCert Properties: Cluster: !Ref ECSCluster DeploymentConfiguration: MaximumPercent: 200 MinimumHealthyPercent: 100 DesiredCount: 1 LaunchType: FARGATE ServiceRegistries: - RegistryArn: !GetAtt GatewayServiceDiscoveryRecord.Arn NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: DISABLED SecurityGroups: - !Ref ECSServiceSecurityGroup Subnets: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 LoadBalancers: - ContainerName: envoy ContainerPort: 9080 TargetGroupArn: !Ref WebTargetGroup TaskDefinition: !Ref GatewayTaskDefinition # public NLB for gateway PublicLoadBalancer: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Scheme: internet-facing Type: network Subnets: - !Ref PublicSubnet1 - !Ref PublicSubnet2 WebTargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup DependsOn: - PublicLoadBalancer Properties: Protocol: TCP TargetType: ip Name: !Sub "${EnvironmentName}-web2" Port: 80 TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: 120 VpcId: !Ref VPC PublicLoadBalancerListener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: !Ref WebTargetGroup Type: 'forward' LoadBalancerArn: !Ref 'PublicLoadBalancer' Port: 80 Protocol: TCP BastionHost: Type: AWS::EC2::Instance Properties: ImageId: !Ref EC2Ami InstanceType: t3.micro SecurityGroupIds: - !Ref ECSServiceSecurityGroup SubnetId: !Ref PrivateSubnet1 IamInstanceProfile: !Ref BastionInstanceProfile Tags: - Key: Name Value: bastion-host BastionInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref BastionInstanceRole BastionInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: [ec2.amazonaws.com] Action: sts:AssumeRole ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore' Outputs: ColorAppEndpoint: Description: Public endpoint for Color App service Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']]