AWSTemplateFormatVersion: '2010-09-09'
Description: Membership/Subscription promotions tool
Parameters:
  Name:
    Description: The name given to the autoscaling group
    Type: String
  Stage:
    Description: Environment name
    Type: String
    AllowedValues:
    - PROD
    - CODE
  MaxInstances:
    Description: Maximum number of instances. This should be (at least) double the
      desired capacity.
    Type: Number
  MinInstances:
    Description: Minimum number of instances
    Type: Number
  VpcId:
    Description: ID of the VPC onto which to launch the application
    Type: AWS::EC2::VPC::Id
  PrivateVpcSubnets:
    Description: Public subnets to use in VPC
    Type: List<AWS::EC2::Subnet::Id>
  PublicVpcSubnets:
    Description: Public subnets to use for the ELB
    Type: List<AWS::EC2::Subnet::Id>
  AmiId:
    Description: Custom AMI to use for instances
    Type: String
  AlarmTopicARN:
    Description: ARN of the SNS topic which the no healthy hosts CloudWatch alarm should report to
    Type: String
  Stack:
    Description: Application stack
    Type: String
    Default: membership
  SslArn:
    Description: SSL certificate ARN
    Type: String
  App:
    Description: Application name
    Type: String
    Default: promotions-tool

Mappings:
    StageMap:
      CODE:
        DNSName: promo.code.memsub-promotions.gutools.co.uk
        ReadableS3Resources:
          - arn:aws:s3:::gu-reader-revenue-private/membership/promotions-tool/CODE/*
          - arn:aws:s3:::gu-promotions-tool-dist/*
          - arn:aws:s3:::membership-private/google-auth-service-account-certificate.json
          - arn:aws:s3:::gu-zuora-catalog/PROD/* # i.e. generated by the PROD catalog service (this should be used when running in any stage)
      PROD:
        DNSName: memsub-promotions.gutools.co.uk
        ReadableS3Resources:
          - arn:aws:s3:::gu-reader-revenue-private/membership/promotions-tool/PROD/*
          - arn:aws:s3:::gu-promotions-tool-dist/*
          - arn:aws:s3:::membership-private/google-auth-service-account-certificate.json
          - arn:aws:s3:::gu-zuora-catalog/PROD/*

Resources:
  ServerRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: /
      Policies:
      - PolicyName: root
        PolicyDocument:
          Statement:
          # Explicitly deny access to all S3 resources except for those defined in ReadableS3Resources
          # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_evaluation-logic.html#policy-eval-denyallow
          - Effect: Deny
            Action: s3:*
            NotResource: !FindInMap [StageMap, !Ref Stage, ReadableS3Resources]
          - Effect: Allow
            Action: s3:GetObject
            Resource: !FindInMap [StageMap, !Ref Stage, ReadableS3Resources]
          - Effect: Allow
            Action:
            - ec2:DescribeTags
            - ec2:DescribeInstances
            - autoscaling:DescribeAutoScalingGroups
            - autoscaling:DescribeAutoScalingInstances
            Resource: '*'
          - Effect: Allow
            Action:
            - dynamodb:PutItem
            - dynamodb:BatchWriteItem
            - dynamodb:DescribeTable
            - dynamodb:GetItem
            - dynamodb:BatchGetItem
            - dynamodb:UpdateItem
            - dynamodb:DeleteItem
            - dynamodb:Scan
            - dynamodb:Query
            Resource:
            - arn:aws:dynamodb:*:*:table/MembershipSub-Promotions-PROD
            - arn:aws:dynamodb:*:*:table/MembershipSub-Campaigns-PROD
            - arn:aws:dynamodb:*:*:table/MembershipSub-Promotions-CODE
            - arn:aws:dynamodb:*:*:table/MembershipSub-Campaigns-CODE
          - Effect: Allow
            Action:
            - cloudwatch:*
            - logs:*
            Resource: '*'
      - PolicyName: SSMTunnel
        PolicyDocument:
          Version: 2012-10-17
          Statement:
            - Effect: Allow
              Action:
                - ssm:UpdateInstanceInformation
                - ssmmessages:CreateControlChannel
                - ssmmessages:CreateDataChannel
                - ssmmessages:OpenControlChannel
                - ssmmessages:OpenDataChannel
              Resource: '*'
      ManagedPolicyArns:
      - !Sub arn:aws:iam::${AWS::AccountId}:policy/guardian-ec2-role-for-ssm
  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
      - !Ref 'ServerRole'
  LoadBalancer:
    Type: AWS::ElasticLoadBalancing::LoadBalancer
    Properties:
      LoadBalancerName: !Join ['-', [!Ref 'App', !Ref 'Stage']]
      Scheme: internet-facing
      Listeners:
      - LoadBalancerPort: '443'
        InstancePort: '9000'
        Protocol: HTTPS
        SSLCertificateId: !Ref 'SslArn'
      CrossZone: 'true'
      HealthCheck:
        Target: HTTP:9000/healthcheck
        HealthyThreshold: '2'
        UnhealthyThreshold: '10'
        Interval: '30'
        Timeout: '10'
      Subnets: !Ref 'PublicVpcSubnets'
      SecurityGroups:
      - !Ref 'LoadBalancerSecurityGroup'
      ConnectionDrainingPolicy:
        Enabled: 'true'
        Timeout: '60'
  DNSRecord:
    Type: AWS::Route53::RecordSetGroup
    Properties:
      Comment: Alias to memsub-promotions app
      HostedZoneName: memsub-promotions.gutools.co.uk.
      RecordSets:
        - AliasTarget:
            DNSName: !GetAtt LoadBalancer.DNSName
            HostedZoneId: !GetAtt LoadBalancer.CanonicalHostedZoneNameID
          Name: !FindInMap [StageMap, !Ref Stage, DNSName]
          Type: A
  AutoscalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AvailabilityZones: !GetAZs ''
      LaunchConfigurationName: !Ref 'LaunchConfig'
      MinSize: !Ref 'MinInstances'
      MaxSize: !Ref 'MaxInstances'
      HealthCheckType: ELB
      HealthCheckGracePeriod: 400
      LoadBalancerNames:
      - !Ref 'LoadBalancer'
      Tags:
      - Key: Stage
        Value: !Ref 'Stage'
        PropagateAtLaunch: 'true'
      - Key: Name
        Value: !Join [':', [!Ref 'Stage', !Ref 'App']]
        PropagateAtLaunch: 'true'
      - Key: Stack
        Value: !Ref 'Stack'
        PropagateAtLaunch: 'true'
      - Key: App
        Value: !Ref 'App'
        PropagateAtLaunch: 'true'
      - Key: Role
        Value: !Ref 'App'
        PropagateAtLaunch: 'true'
      VPCZoneIdentifier: !Ref 'PrivateVpcSubnets'
  LaunchConfig:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      ImageId: !Ref 'AmiId'
      SecurityGroups:
      - !Ref 'InstanceSecurityGroup'
      - !Ref WazuhSecurityGroup
      InstanceType: t4g.micro
      MetadataOptions:
        HttpTokens: required
      AssociatePublicIpAddress: 'False'
      IamInstanceProfile: !Ref 'InstanceProfile'
      UserData:
        "Fn::Base64":
          !Sub
            - |
              #!/bin/bash -ev
              CONF_DIR=/etc/promotions-tool
              aws s3 cp s3://gu-promotions-tool-dist/${Stack}/${Stage}/${App}/promotions-tool_1.0-SNAPSHOT_all.deb /tmp
              dpkg -i /tmp/promotions-tool_1.0-SNAPSHOT_all.deb
              mkdir -p /etc/gu
              aws s3 cp s3://gu-reader-revenue-private/${Stack}/${App}/${Stage}/memsub-promotions-keys.conf /etc/gu
              chown promotions-tool /etc/gu/memsub-promotions-keys.conf
              chmod 0600 /etc/gu/memsub-promotions-keys.conf
              echo "stage=${Stage}" >> /etc/gu/memsub-promotions-keys.conf

              /opt/cloudwatch-logs/configure-logs application membership ${Stage} promotions-tool /var/log/promotions-tool/promotions-tool.log
            - {}
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref 'VpcId'
      GroupDescription: Open up HTTPS ingress from internet
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '443'
        ToPort: '443'
        CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
      - IpProtocol: tcp
        FromPort: '9000'
        ToPort: '9000'
        CidrIp: 0.0.0.0/0
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref 'VpcId'
      GroupDescription: Open up HTTP ingress from the load balancer
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '9000'
        ToPort: '9000'
        SourceSecurityGroupId: !Ref 'LoadBalancerSecurityGroup'
      SecurityGroupEgress:
      - IpProtocol: tcp
        FromPort: '443'
        ToPort: '443'
        CidrIp: 0.0.0.0/0
  WazuhSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow outbound traffic from wazuh agent to manager
      VpcId:
        Ref: VpcId
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 1514
          ToPort: 1515
          CidrIp: 0.0.0.0/0

  alarmNoHealthyHosts:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled: 'true'
      AlarmDescription: There are insufficient healthy hosts
      ComparisonOperator: LessThanThreshold
      EvaluationPeriods: '1'
      MetricName: HealthyHostCount
      Namespace: AWS/ELB
      Period: '60'
      Statistic: Average
      Threshold: !Ref 'MinInstances'
      AlarmActions:
      - !Ref 'AlarmTopicARN'
      InsufficientDataActions:
      - !Ref 'AlarmTopicARN'
      Dimensions:
      - Name: LoadBalancerName
        Value: !Ref 'LoadBalancer'
Outputs:
  LoadBalancer:
    Value: !GetAtt [LoadBalancer, DNSName]
