CloudFormation Network Experimentation Stack: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
Line 8: Line 8:
=Stack=
=Stack=
<syntaxhighlight lang='yaml'>
<syntaxhighlight lang='yaml'>
AWSTemplateFormatVersion: '2010-09-09'
Description: Public Load Balancer Tests
Parameters:
  Color:
    Type: String
    Default: green
  ApplicationPort:
    Type: Number
    Default: 10003
  Image:
    Type: String
    Default: 777777777777.dkr.ecr.ap-northeast-1.amazonaws.com/themyscira:latest
  BastionAmi:
    Type: String
    Default: ami-0f9ae750e8274075b
  AvailabilityZoneSuffix1:
    Type: String
    Default: a
  AvailabilityZoneSuffix2:
    Type: String
    Default: c
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.20.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: false
      InstanceTenancy: "default"
      Tags:
        - Key: Name
          Value: !Sub ${Color}-vpc
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${Color}-igw
  InternetGatewayVpcAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  #
  # This is required to give container downloading capabilities to processes only having access to private subnets
  #
  ElasticIP:
    Type: AWS::EC2::EIP
    DependsOn:
      - InternetGatewayVpcAttachment
    Properties:
      Domain: vpc
  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      SubnetId: !Ref PublicSubnet1
      AllocationId: !GetAtt ElasticIP.AllocationId
      Tags:
        - Key: Name
          Value: !Sub ${Color}-nat
  #
  # Public/private subnet pair 1
  #
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.20.1.0/24
      AvailabilityZone: !Sub ${AWS::Region}${AvailabilityZoneSuffix1}
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${Color}-public-subnet-1
  PublicSubnet1RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Color}-public-subnet-1-rt
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicSubnet1RouteTable
      SubnetId: !Ref PublicSubnet1
  InternetRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicSubnet1RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.20.3.0/24
      AvailabilityZone: !Sub ${AWS::Region}${AvailabilityZoneSuffix1}
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${Color}-private-subnet-1
  PrivateSubnet1RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Color}-private-subnet-1-rt
  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubnet1RouteTable
      SubnetId: !Ref PrivateSubnet1
  #
  # This is required to give container downloading capabilities to processes only having access to private subnets
  #
  PrivateSubnet1RouteToNAT:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateSubnet1RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway
  #
  # Public/private subnet pair 2
  #
  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.20.2.0/24
      AvailabilityZone: !Sub ${AWS::Region}${AvailabilityZoneSuffix2}
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${Color}-public-subnet-2
  PublicSubnet2RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Color}-public-subnet-2-rt
  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicSubnet2RouteTable
      SubnetId: !Ref PublicSubnet2
  InternetRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicSubnet2RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.20.4.0/24
      AvailabilityZone: !Sub ${AWS::Region}${AvailabilityZoneSuffix2}
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${Color}-private-subnet-2
  PrivateSubnet2RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Color}-private-subnet-2-rt
  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubnet2RouteTable
      SubnetId: !Ref PrivateSubnet2
  #
  # This is required to give container downloading capabilities to processes only having access to private subnets
  #
  PrivateSubnet2RouteToNAT:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateSubnet2RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway
  #
  # Bastion
  #
  BastionInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref BastionAmi
      KeyName: !Sub infinity-${AWS::Region}
      InstanceType: t2.micro
      NetworkInterfaces:
        - AssociatePublicIpAddress: 'true'
          DeviceIndex: '0'
          GroupSet:
            - !Ref BastionSecurityGroup
          SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${Color}-bastion
  BastionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${Color}-bastion-sg
      VpcId: !Ref VPC
      GroupDescription: !Sub Security group for ${Color} bastion
      SecurityGroupIngress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
  #
  # The application load balancer
  #
  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub ${Color}-public-alb
      Scheme: internet-facing
      Type: application
      IpAddressType: ipv4
      LoadBalancerAttributes:
        - Key: access_logs.s3.enabled
          Value: false
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      SecurityGroups:
        - !Ref ApplicationLoadBalancerSecurityGroup
  ApplicationLoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${Color}-public-alb-sg
      GroupDescription: something
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
  #
  # The service to balance load for
  #
  ApplicationLoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: !Ref ApplicationPort
      Protocol: HTTP
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          Type: 'forward'
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    #
    # DependsOn is important, I've seen race conditions with the load balancer
    #
    DependsOn: ApplicationLoadBalancer
    Properties:
      Name: !Sub ${Color}-themyscira-tg
      VpcId: !Ref VPC
      Protocol: HTTP
      Port: !Ref ApplicationPort
      TargetType: ip
      HealthCheckProtocol: HTTP
      HealthCheckIntervalSeconds: 60
      HealthCheckTimeoutSeconds: 10
      HealthyThresholdCount: 3
      UnhealthyThresholdCount: 3
      HealthCheckPath: '/actuator/health'
  #
  # Elastic Container Service Resources
  #
  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${Color}-cluster
  ServiceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: themyscira security group
      VpcId: !Ref VPC
      GroupName: !Sub ${Color}-service-sg
      SecurityGroupIngress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
  ServiceDefinition:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: !Sub ${Color}-themyscira
      LaunchType: FARGATE
      Cluster: !Ref Cluster
      TaskDefinition: !Ref TaskDefinition
      DesiredCount: 1
      HealthCheckGracePeriodSeconds: 60
      LoadBalancers:
        - ContainerName: !Sub themyscira-container
          ContainerPort: !Ref ApplicationPort
          TargetGroupArn: !Ref TargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref ServiceSecurityGroup
          Subnets:
            - !Ref PrivateSubnet1
            - !Ref PrivateSubnet2
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    DependsOn: ServiceLogGroup
    Properties:
      Family: themyscira
      RequiresCompatibilities: [ FARGATE ]
      NetworkMode: awsvpc
      Cpu: '2048'
      Memory: '4096'
      TaskRoleArn: !GetAtt TaskRole.Arn
      ExecutionRoleArn: !GetAtt TaskExecutionRole.Arn
      ContainerDefinitions:
        - Name: !Sub themyscira-container
          Cpu: '2048'
          Memory: '4096'
          Essential: 'true'
          Environment:
            - Name: SPRING_PROFILES_ACTIVE
              Value: !Sub generic-profile
            - Name: SERVER_PORT
              Value: !Ref ApplicationPort
          Image: !Ref Image
          PortMappings:
            - HostPort: !Ref ApplicationPort
              ContainerPort: !Ref ApplicationPort
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Sub /${Color}/themyscira
              awslogs-region: !Sub ${AWS::Region}
              awslogs-stream-prefix: task
  #
  # ECS Roles
  #
  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::Region}-${Color}-task-role
      Path: /service-role/
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ecs-tasks.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: generic-in-line-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource: '*'
                Effect: Allow
                Action:
                  # Rules which allow ECS to attach network interfaces to instances
                  # on your behalf in order for awsvpc networking mode to work right
                  - ec2:AttachNetworkInterface
                  - ec2:CreateNetworkInterface
                  - ec2:CreateNetworkInterfacePermission
                  - ec2:DeleteNetworkInterface
                  - ec2:DeleteNetworkInterfacePermission
                  - 'ec2:Describe*'
                  - ec2:DetachNetworkInterface
                  # Rules which allow ECS to update load balancers on your behalf
                  # with the information about how to send traffic to your containers
                  - elasticloadbalancing:DeregisterInstancesFromLoadBalancer
                  - elasticloadbalancing:DeregisterTargets
                  - 'elasticloadbalancing:Describe*'
                  - elasticloadbalancing:RegisterInstancesWithLoadBalancer
                  - elasticloadbalancing:RegisterTargets
                  # Allow the ECS Tasks resource access
                  - sns:Publish
                  - dynamodb:GetItem
                  - dynamodb:BatchGetItem
                  - dynamodb:PutItem
                  - dynamodb:UpdateItem
                  - dynamodb:DescribeTable
                  - dynamodb:CreateTable
                  - dynamodb:Query
                  - dynamodb:Scan
                  - kinesis:DescribeStream
                  - kinesis:PutRecord
                  - kinesis:PutRecords
                  - firehose:DescribeDeliveryStream
                  - firehose:ListDeliveryStreams
                  - firehose:PutRecord
                  - firehose:PutRecordBatch
                  - kinesis:GetShardIterator
                  - kinesis:GetRecords
                  - kinesis:ListStreams
                  - kinesis:ListShards
                  - kinesis:CreateStream
                  - 's3:*'
                  # Required by Kasa campaign library
                  - ssm:GetParametersByPath
                  # Required by all applications that use the encryption library and need access to the microworld's
                  # master key
                  - 'kms:*'
  TaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::Region}-${Color}-task-execution-role
      Path: /service-role/
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ecs-tasks.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: generic-in-line-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource: '*'
                Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:GetDownloadUrlForLayer
                  - ecr:BatchGetImage
                  - ecr:BatchCheckLayerAvailability
              - Resource: '*'
                Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
              - Resource: '*'
                Effect: Allow
                Action:
                  - ec2:AuthorizeSecurityGroupIngress
                  - ec2:Describe*
                  - elasticloadbalancing:DeregisterInstancesFromLoadBalancer
                  - elasticloadbalancing:Describe*
                  - elasticloadbalancing:RegisterInstancesWithLoadBalancer
                  - elasticloadbalancing:DeregisterTargets
                  - elasticloadbalancing:DescribeTargetGroups
                  - elasticloadbalancing:DescribeTargetHealth
                  - elasticloadbalancing:RegisterTargets
  ServiceLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /${Color}/themyscira
      RetentionInDays: 1
</syntaxhighlight>
</syntaxhighlight>

Revision as of 21:08, 1 May 2019

Internal

Overview

Network Experimentation Stack (public and private subnets, internet and NAT gateways, ALB and a ECS-based application.

Stack

AWSTemplateFormatVersion: '2010-09-09'

Description: Public Load Balancer Tests

Parameters:

  Color:
    Type: String
    Default: green

  ApplicationPort:
    Type: Number
    Default: 10003

  Image:
    Type: String
    Default: 777777777777.dkr.ecr.ap-northeast-1.amazonaws.com/themyscira:latest

  BastionAmi:
    Type: String
    Default: ami-0f9ae750e8274075b

  AvailabilityZoneSuffix1:
    Type: String
    Default: a

  AvailabilityZoneSuffix2:
    Type: String
    Default: c

Resources:

  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.20.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: false
      InstanceTenancy: "default"
      Tags:
        - Key: Name
          Value: !Sub ${Color}-vpc

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${Color}-igw

  InternetGatewayVpcAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  #
  # This is required to give container downloading capabilities to processes only having access to private subnets
  #

  ElasticIP:
    Type: AWS::EC2::EIP
    DependsOn:
      - InternetGatewayVpcAttachment
    Properties:
      Domain: vpc

  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      SubnetId: !Ref PublicSubnet1
      AllocationId: !GetAtt ElasticIP.AllocationId
      Tags:
        - Key: Name
          Value: !Sub ${Color}-nat

  #
  # Public/private subnet pair 1
  #

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.20.1.0/24
      AvailabilityZone: !Sub ${AWS::Region}${AvailabilityZoneSuffix1}
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${Color}-public-subnet-1

  PublicSubnet1RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Color}-public-subnet-1-rt

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicSubnet1RouteTable
      SubnetId: !Ref PublicSubnet1

  InternetRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicSubnet1RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.20.3.0/24
      AvailabilityZone: !Sub ${AWS::Region}${AvailabilityZoneSuffix1}
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${Color}-private-subnet-1

  PrivateSubnet1RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Color}-private-subnet-1-rt

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubnet1RouteTable
      SubnetId: !Ref PrivateSubnet1

  #
  # This is required to give container downloading capabilities to processes only having access to private subnets
  #

  PrivateSubnet1RouteToNAT:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateSubnet1RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway

  #
  # Public/private subnet pair 2
  #

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.20.2.0/24
      AvailabilityZone: !Sub ${AWS::Region}${AvailabilityZoneSuffix2}
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${Color}-public-subnet-2

  PublicSubnet2RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Color}-public-subnet-2-rt

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicSubnet2RouteTable
      SubnetId: !Ref PublicSubnet2

  InternetRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicSubnet2RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.20.4.0/24
      AvailabilityZone: !Sub ${AWS::Region}${AvailabilityZoneSuffix2}
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${Color}-private-subnet-2

  PrivateSubnet2RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Color}-private-subnet-2-rt

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateSubnet2RouteTable
      SubnetId: !Ref PrivateSubnet2

  #
  # This is required to give container downloading capabilities to processes only having access to private subnets
  #

  PrivateSubnet2RouteToNAT:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateSubnet2RouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway

  #
  # Bastion
  #

  BastionInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref BastionAmi
      KeyName: !Sub infinity-${AWS::Region}
      InstanceType: t2.micro
      NetworkInterfaces:
        - AssociatePublicIpAddress: 'true'
          DeviceIndex: '0'
          GroupSet:
            - !Ref BastionSecurityGroup
          SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${Color}-bastion

  BastionSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${Color}-bastion-sg
      VpcId: !Ref VPC
      GroupDescription: !Sub Security group for ${Color} bastion
      SecurityGroupIngress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0

  #
  # The application load balancer
  #

  ApplicationLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub ${Color}-public-alb
      Scheme: internet-facing
      Type: application
      IpAddressType: ipv4
      LoadBalancerAttributes:
        - Key: access_logs.s3.enabled
          Value: false
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      SecurityGroups:
        - !Ref ApplicationLoadBalancerSecurityGroup

  ApplicationLoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${Color}-public-alb-sg
      GroupDescription: something
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0

  #
  # The service to balance load for
  #

  ApplicationLoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref ApplicationLoadBalancer
      Port: !Ref ApplicationPort
      Protocol: HTTP
      DefaultActions:
        - TargetGroupArn: !Ref TargetGroup
          Type: 'forward'

  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    #
    # DependsOn is important, I've seen race conditions with the load balancer
    #
    DependsOn: ApplicationLoadBalancer
    Properties:
      Name: !Sub ${Color}-themyscira-tg
      VpcId: !Ref VPC
      Protocol: HTTP
      Port: !Ref ApplicationPort
      TargetType: ip
      HealthCheckProtocol: HTTP
      HealthCheckIntervalSeconds: 60
      HealthCheckTimeoutSeconds: 10
      HealthyThresholdCount: 3
      UnhealthyThresholdCount: 3
      HealthCheckPath: '/actuator/health'

  #
  # Elastic Container Service Resources
  #

  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${Color}-cluster

  ServiceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: themyscira security group
      VpcId: !Ref VPC
      GroupName: !Sub ${Color}-service-sg
      SecurityGroupIngress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0

  ServiceDefinition:
    Type: AWS::ECS::Service
    Properties:
      ServiceName: !Sub ${Color}-themyscira
      LaunchType: FARGATE
      Cluster: !Ref Cluster
      TaskDefinition: !Ref TaskDefinition
      DesiredCount: 1
      HealthCheckGracePeriodSeconds: 60
      LoadBalancers:
        - ContainerName: !Sub themyscira-container
          ContainerPort: !Ref ApplicationPort
          TargetGroupArn: !Ref TargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - !Ref ServiceSecurityGroup
          Subnets:
            - !Ref PrivateSubnet1
            - !Ref PrivateSubnet2

  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    DependsOn: ServiceLogGroup
    Properties:
      Family: themyscira
      RequiresCompatibilities: [ FARGATE ]
      NetworkMode: awsvpc
      Cpu: '2048'
      Memory: '4096'
      TaskRoleArn: !GetAtt TaskRole.Arn
      ExecutionRoleArn: !GetAtt TaskExecutionRole.Arn
      ContainerDefinitions:
        - Name: !Sub themyscira-container
          Cpu: '2048'
          Memory: '4096'
          Essential: 'true'
          Environment:
            - Name: SPRING_PROFILES_ACTIVE
              Value: !Sub generic-profile
            - Name: SERVER_PORT
              Value: !Ref ApplicationPort
          Image: !Ref Image
          PortMappings:
            - HostPort: !Ref ApplicationPort
              ContainerPort: !Ref ApplicationPort
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Sub /${Color}/themyscira
              awslogs-region: !Sub ${AWS::Region}
              awslogs-stream-prefix: task

  #
  # ECS Roles
  #

  TaskRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::Region}-${Color}-task-role
      Path: /service-role/
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ecs-tasks.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: generic-in-line-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource: '*'
                Effect: Allow
                Action:
                  # Rules which allow ECS to attach network interfaces to instances
                  # on your behalf in order for awsvpc networking mode to work right
                  - ec2:AttachNetworkInterface
                  - ec2:CreateNetworkInterface
                  - ec2:CreateNetworkInterfacePermission
                  - ec2:DeleteNetworkInterface
                  - ec2:DeleteNetworkInterfacePermission
                  - 'ec2:Describe*'
                  - ec2:DetachNetworkInterface
                  # Rules which allow ECS to update load balancers on your behalf
                  # with the information about how to send traffic to your containers
                  - elasticloadbalancing:DeregisterInstancesFromLoadBalancer
                  - elasticloadbalancing:DeregisterTargets
                  - 'elasticloadbalancing:Describe*'
                  - elasticloadbalancing:RegisterInstancesWithLoadBalancer
                  - elasticloadbalancing:RegisterTargets
                  # Allow the ECS Tasks resource access
                  - sns:Publish
                  - dynamodb:GetItem
                  - dynamodb:BatchGetItem
                  - dynamodb:PutItem
                  - dynamodb:UpdateItem
                  - dynamodb:DescribeTable
                  - dynamodb:CreateTable
                  - dynamodb:Query
                  - dynamodb:Scan
                  - kinesis:DescribeStream
                  - kinesis:PutRecord
                  - kinesis:PutRecords
                  - firehose:DescribeDeliveryStream
                  - firehose:ListDeliveryStreams
                  - firehose:PutRecord
                  - firehose:PutRecordBatch
                  - kinesis:GetShardIterator
                  - kinesis:GetRecords
                  - kinesis:ListStreams
                  - kinesis:ListShards
                  - kinesis:CreateStream
                  - 's3:*'
                  # Required by Kasa campaign library
                  - ssm:GetParametersByPath
                  # Required by all applications that use the encryption library and need access to the microworld's
                  # master key
                  - 'kms:*'

  TaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${AWS::Region}-${Color}-task-execution-role
      Path: /service-role/
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ecs-tasks.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: generic-in-line-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Resource: '*'
                Effect: Allow
                Action:
                  - ecr:GetAuthorizationToken
                  - ecr:GetDownloadUrlForLayer
                  - ecr:BatchGetImage
                  - ecr:BatchCheckLayerAvailability
              - Resource: '*'
                Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
              - Resource: '*'
                Effect: Allow
                Action:
                  - ec2:AuthorizeSecurityGroupIngress
                  - ec2:Describe*
                  - elasticloadbalancing:DeregisterInstancesFromLoadBalancer
                  - elasticloadbalancing:Describe*
                  - elasticloadbalancing:RegisterInstancesWithLoadBalancer
                  - elasticloadbalancing:DeregisterTargets
                  - elasticloadbalancing:DescribeTargetGroups
                  - elasticloadbalancing:DescribeTargetHealth
                  - elasticloadbalancing:RegisterTargets

  ServiceLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub /${Color}/themyscira
      RetentionInDays: 1