AWSTemplateFormatVersion: '2010-09-09'
Description: Deploy a public S3 proxy service on AWS Fargate, hosted in a private subnet, and accessible via a public load balancer.
Parameters:
  VPC:
    Type: AWS::EC2::VPC::Id
    Description: VPC Id
  PrivateSubnetOne:
    Type: AWS::EC2::Subnet::Id
    Description: First private Subnet ID
  PrivateSubnetTwo:
    Type: AWS::EC2::Subnet::Id
    Description: Second private Subnet ID
  PublicSubnetOne:
    Type: AWS::EC2::Subnet::Id
    Description: First public Subnet ID
  PublicSubnetTwo:
    Type: AWS::EC2::Subnet::Id
    Description: Second public Subnet ID
  S3BucketName:
    Type: String
    Description: The name of your S3 bucket            
  ImageRepository:
    Type: String
    Description: The S3 proxy repository URI.
  ImageTag:
    Type: String
    Default: "1.0.6"
    Description: The Docker image tag of the S3 proxy.
  ContainerPort:
    Type: Number
    Default: 80
    Description: What port number the application inside the docker container is binding to
  ContainerCpu:
    Type: Number
    Default: 256
    Description: How much CPU to give the container. 1024 is 1 CPU
  ContainerMemory:
    Type: Number
    Default: 512
    Description: How much memory in megabytes to give the container
  DesiredCount:
    Type: Number
    Default: 2
    Description: How many copies of the service task to

Resources:

  # ECS Resources
  ECSCluster:
    Type: AWS::ECS::Cluster

  PublicLoadBalancerSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Access to the public load balancer
      VpcId: !Ref 'VPC'
  PublicLoadBalancerIngressFromECS:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Only accept traffic from a container in the fargate container security group
      GroupId: !Ref 'PublicLoadBalancerSG'
      IpProtocol: -1
      SourceSecurityGroupId: !Ref 'FargateContainerSecurityGroup'
  PublicLoadBalancerIngressFromInternet:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Accept https traffic from internet
      GroupId: !Ref 'PublicLoadBalancerSG'
      IpProtocol: tcp
      FromPort: 443
      ToPort: 443
      CidrIp: '0.0.0.0/0'
  PublicLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      LoadBalancerAttributes:
      - Key: idle_timeout.timeout_seconds
        Value: '30'
      Subnets:
        - !Ref PublicSubnetOne
        - !Ref PublicSubnetTwo
      SecurityGroups: [!Ref 'PublicLoadBalancerSG']
  PublicLoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    DependsOn:
      - PublicLoadBalancer
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref 'TargetGroup'
          Type: 'forward'
      LoadBalancerArn: !Ref 'PublicLoadBalancer'
      Port: 80
      Protocol: HTTP
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties: 
      LogGroupName:  !Join ['/', ['/ecs', !Ref 'AWS::StackName']]
      RetentionInDays: 30

  FargateContainerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Access to the Fargate containers
      VpcId: !Ref 'VPC'
  EcsSecurityGroupIngressFromPrivateALB:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      Description: Ingress from the private ALB
      GroupId: !Ref 'FargateContainerSecurityGroup'
      IpProtocol: -1
      SourceSecurityGroupId: !Ref 'PublicLoadBalancerSG'

  ECSTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service: [ecs-tasks.amazonaws.com]
          Action: ['sts:AssumeRole']
      Path: /
      Policies:
        - PolicyName: AmazonECSTaskExecutionRolePolicy
          PolicyDocument:
            Statement:
            - Effect: Allow
              Action:
                # Allow the ECS Tasks to download images from ECR
                - 'ecr:GetAuthorizationToken'
                - 'ecr:BatchCheckLayerAvailability'
                - 'ecr:GetDownloadUrlForLayer'
                - 'ecr:BatchGetImage'

                # Allow the ECS tasks to upload logs to CloudWatch
                - 'logs:CreateLogStream'
                - 'logs:PutLogEvents'
              Resource: '*'

  # The task definition. This is a simple metadata description of what
  # container to run, and what resource requirements it has.
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Ref 'AWS::StackName'
      Cpu: !Ref 'ContainerCpu'
      Memory: !Ref 'ContainerMemory'
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ExecutionRoleArn: !Ref 'ECSTaskExecutionRole'
      ContainerDefinitions:
        - Name: !Ref 'AWS::StackName'
          Cpu: !Ref 'ContainerCpu'
          Memory: !Ref 'ContainerMemory'
          Image: !Join [':', [!Ref ImageRepository, !Ref 'ImageTag']]
          Environment:
            - Name: S3_BUCKET
              Value: !Ref 'S3BucketName'
          PortMappings:
            - ContainerPort: !Ref 'ContainerPort'
          LogConfiguration:
            LogDriver: awslogs
            Options:
              'awslogs-region': !Ref 'AWS::Region'
              'awslogs-group': !Join ['/', ['/ecs', !Ref 'AWS::StackName']]
              'awslogs-stream-prefix': 'ecs'

  # The service. The service is a resource which allows you to run multiple
  # copies of a type of task, and gather up their logs and metrics, as well
  # as monitor the number of running tasks and replace any that have crashed
  Service:
    Type: AWS::ECS::Service
    DependsOn:
      - TargetGroup
      - PublicLoadBalancerListener
    Properties:
      ServiceName: !Ref 'AWS::StackName'
      Cluster: !Ref 'ECSCluster'
      LaunchType: FARGATE
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 75
      DesiredCount: !Ref 'DesiredCount'
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups:
            - !Ref 'FargateContainerSecurityGroup'
          Subnets:
            - !Ref 'PrivateSubnetOne'
            - !Ref 'PrivateSubnetTwo'
      TaskDefinition: !Ref 'TaskDefinition'
      LoadBalancers:
        - ContainerName: !Ref 'AWS::StackName'
          ContainerPort: !Ref 'ContainerPort'
          TargetGroupArn: !Ref 'TargetGroup'

  # A target group. This is used for keeping track of all the tasks, and
  # what IP addresses / port numbers they have. You can query it yourself,
  # to use the addresses yourself, but most often this target group is just
  # connected to an application load balancer, or network load balancer, so
  # it can automatically distribute traffic across all the targets.
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckIntervalSeconds: 6
      HealthCheckPath: /ping
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      TargetType: ip
      Name: !Ref 'AWS::StackName'
      Port: !Ref 'ContainerPort'
      Protocol: HTTP
      UnhealthyThresholdCount: 2
      VpcId: !Ref 'VPC'

  ## AWS Global Accelerator in front of the load balancer
  Accelerator:
    Type: AWS::GlobalAccelerator::Accelerator
    Properties:
      Name: S3Proxy
      Enabled: true
  Listener:
    Type: AWS::GlobalAccelerator::Listener
    Properties:
      AcceleratorArn:
        Ref: Accelerator
      Protocol: TCP
      PortRanges:
      - FromPort: 443
        ToPort: 443
  EndpointGroup:
    Type: AWS::GlobalAccelerator::EndpointGroup
    Properties:
      ListenerArn:
        Ref: Listener
      EndpointGroupRegion:
        !Ref 'AWS::Region'
      TrafficDialPercentage: 100
      EndpointConfigurations:
      - EndpointId:
          Ref: PublicLoadBalancer
