AWSTemplateFormatVersion: '2010-09-09'
Description: tag-manager
Parameters:
  Stage:
    Description: Environment name
    Type: String
    AllowedValues:
    - PROD
    - CODE
    Default: PROD
  VpcId:
    Description: ID of the VPC onto which to launch the application eg. vpc-1234abcd
    Type: AWS::EC2::VPC::Id
    Default: vpc-381fa95d
  PublicVpcSubnets:
    Description: Subnets to use in VPC for public internet-facing ELB eg. subnet-abcd1234
    Type: List<AWS::EC2::Subnet::Id>
    Default: subnet-c3620fa6,subnet-2b37bd5c,subnet-3667c86f
  PrivateVpcSubnets:
    Description: Subnets to use in VPC for private EC2 instances eg. subnet-abcd1234
    Type: List<AWS::EC2::Subnet::Id>
    Default: subnet-c2620fa7,subnet-2a37bd5d,subnet-2967c870
  AMI:
    Description: AMI id
    Type: String
  GuardianIP:
    Description: Ip range for the office
    Type: String
    Default: 77.91.248.0/21
  CertificateArn:
    Description: ARN of the SSL certificate for this service
    Type: String
  VulnerabilityScanningSecurityGroup:
    Description: Security group that grants access to the account's Vulnerability
      Scanner
    Type: AWS::EC2::SecurityGroup::Id
  CapiPreviewRole:
    Type: String
    Description: ARN of the CAPI preview role
  AlertTopicArn:
    Description: The ARN of the SNS topic to notify when an alarm occurs
    Type: AWS::SSM::Parameter::Value<String>
    Default: /account/services/alert.topic.arn
  LoggingStreamName:
    Type: AWS::SSM::Parameter::Value<String>
    Description: Name of the kinesis stream for logging to ELK (logs.gutools)
    Default: /account/services/logging.stream.name
  LoggingStreamArn:
    Type: AWS::SSM::Parameter::Value<String>
    Description: ARN of the kinesis stream for logging to ELK (logs.gutools)
    Default: /account/services/logging.stream
Conditions:
  IsProd: !Equals [!Ref Stage, PROD]
Mappings:
  Config:
    CODE:
      MinSize: 1
      MaxSize: 2
      DesiredCapacity: 1
      InstanceType: t4g.medium
      DNSName: tagmanager.code.dev-gutools.co.uk
    PROD:
      MinSize: 3
      MaxSize: 6
      DesiredCapacity: 3
      InstanceType: t4g.medium
      DNSName: tagmanager.gutools.co.uk
Resources:
  TagManagerRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
        - Fn::ImportValue: !Sub "guardian-ec2-for-ssm-GuardianEC2ForSSMPolicy"
      AssumeRolePolicyDocument:
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: /
  TagManagerDescribeEC2Policy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TagManagersDescribeEC2Policy
      PolicyDocument:
        Statement:
        - Effect: Allow
          Resource: "*"
          Action:
          - ec2:Describe*
          - autoscaling:DescribeAutoScalingGroups
          - autoscaling:DescribeAutoScalingInstances
      Roles:
      - !Ref 'TagManagerRole'
  TagManagerGetDistributablesPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TagManagerGetDistributablesPolicy
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action:
          - s3:GetObject
          Resource:
          - arn:aws:s3:::composer-dist/*
      Roles:
      - !Ref 'TagManagerRole'
  TagManagerGetConfigPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TagManagerGetConfigPolicy
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action:
          - s3:GetObject
          Resource:
          - arn:aws:s3:::guconf-flexible/tag-manager/*
      Roles:
      - !Ref 'TagManagerRole'
  TagManagerPanDomainPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TagManagerPanDomainPolicy
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action:
          - s3:GetObject
          Resource:
          - arn:aws:s3:::pan-domain-auth-settings/*
      Roles:
      - !Ref 'TagManagerRole'
  TagManagerStaticFilesWritePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TagManagerStaticFilesWritePolicy
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action: sts:AssumeRole
          Resource: arn:aws:iam::642631414762:role/composerWriteToStaticBucket
      Roles:
      - !Ref 'TagManagerRole'
  TagManagerPermissionsBucketPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TagManagerPermissionsBucketPolicy
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action:
          - s3:GetObject
          Resource:
          - arn:aws:s3:::permissions-cache/*
      Roles:
      - !Ref 'TagManagerRole'
  TagManagerCloudwatchPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TagManagerCloudwatchPolicy
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action:
          - cloudwatch:*
          Resource: '*'
      Roles:
      - !Ref 'TagManagerRole'
  TagManagersDynamoPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TagManagerDynamoPolicy
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action:
          - dynamodb:*
          Resource: '*'
      Roles:
      - !Ref 'TagManagerRole'
  TagManagerKinesisPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TagManagerKinesisPolicy
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action:
          - kinesis:*
          Resource: '*'
      Roles:
      - !Ref 'TagManagerRole'
  TagManagerSQSPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: TagManagerSQSPolicy
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action:
          - sqs:*
          Resource: '*'
      Roles:
      - !Ref 'TagManagerRole'
  LogServerPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: LogServerPolicy
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - kinesis:PutRecord
              - kinesis:PutRecords
              - kinesis:DescribeStream
            Resource: !Ref LoggingStreamArn
      Roles:
      - !Ref 'TagManagerRole'
  TagManagerInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
      - !Ref 'TagManagerRole'
  AppServerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: tag manager Application servers
      VpcId: !Ref 'VpcId'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 9000
        ToPort: 9000
        CidrIp: !Ref 'GuardianIP'
      - IpProtocol: tcp
        FromPort: 9000
        ToPort: 9000
        SourceSecurityGroupId: !Ref 'LoadBalancerSecurityGroup'
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: tag manager application load balancer
      VpcId: !Ref 'VpcId'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '80'
        ToPort: '80'
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: '443'
        ToPort: '443'
        CidrIp: 0.0.0.0/0
  TagManagerLoadBalancer:
    Type: AWS::ElasticLoadBalancing::LoadBalancer
    Properties:
      SecurityGroups:
      - !GetAtt [LoadBalancerSecurityGroup, GroupId]
      CrossZone: true
      Subnets: !Ref 'PublicVpcSubnets'
      Listeners:
      - LoadBalancerPort: '80'
        InstancePort: '9000'
        Protocol: HTTP
      - LoadBalancerPort: '443'
        InstancePort: '9000'
        Protocol: HTTPS
        SSLCertificateId: !Ref 'CertificateArn'
      HealthCheck:
        Target: HTTP:9000/management/healthcheck
        HealthyThreshold: '2'
        UnhealthyThreshold: '2'
        Interval: '10'
        Timeout: '5'
      Tags:
      - Key: Stage
        Value: !Ref 'Stage'
      - Key: Stack
        Value: flexible
      - Key: App
        Value: tag-manager
  TagManagerCloudfrontCachePolicy:
    Type: "AWS::CloudFront::CachePolicy"
    Properties:
      CachePolicyConfig:
        DefaultTTL: 0
        MaxTTL: 3600
        MinTTL: 0
        Name: !Sub "tag-manager-cache-policy-${Stage}"
        ParametersInCacheKeyAndForwardedToOrigin:
          CookiesConfig:
            CookieBehavior: "all"
          EnableAcceptEncodingBrotli: true
          EnableAcceptEncodingGzip: true
          HeadersConfig:
            HeaderBehavior: "whitelist"
            Headers:
              - "Host"
              - "Origin"
              - "Access-Control-Request-Headers"
              - "Access-Control-Request-Method"
              - "X-Gu-Tools-HMAC-Token"
              - "X-Gu-Tools-HMAC-Date"
              - "X-Gu-Tools-Service-Name"
          QueryStringsConfig:
            QueryStringBehavior: "all"
  TagManagerCloudFrontDistribution:
    Type: "AWS::CloudFront::Distribution"
    Properties:
      DistributionConfig:
        Aliases:
          - !FindInMap [Config, !Ref 'Stage', DNSName]
        DefaultCacheBehavior:
          AllowedMethods:
            - "GET"
            - "HEAD"
            - "OPTIONS"
            - "PUT"
            - "PATCH"
            - "POST"
            - "DELETE"
          CachePolicyId: !Ref 'TagManagerCloudfrontCachePolicy'
          Compress: true
          TargetOriginId: !Ref 'TagManagerLoadBalancer'
          ViewerProtocolPolicy: "redirect-to-https"
        Enabled: true
        HttpVersion: "http2"
        IPV6Enabled: true
        Origins:
          - CustomOriginConfig:
              OriginProtocolPolicy: "https-only"
              OriginSSLProtocols:
                - "TLSv1.2"
            DomainName: !GetAtt TagManagerLoadBalancer.DNSName
            Id: !Ref 'TagManagerLoadBalancer'
        ViewerCertificate:
          AcmCertificateArn: !Sub '{{resolve:ssm:/${Stage}/flexible/tagmanager/cloudFrontCertificateArn}}'
          MinimumProtocolVersion: "TLSv1.2_2021"
          SslSupportMethod: "sni-only"
      Tags:
        - Key: "gu:repo"
          Value: "guardian/tagmanager"
        - Key: Stack
          Value: flexible
        - Key: Stage
          Value: !Ref 'Stage'
        - Key: App
          Value: tag-manager
  AutoscalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      AvailabilityZones: !GetAZs ''
      VPCZoneIdentifier: !Ref 'PrivateVpcSubnets'
      LaunchConfigurationName: !Ref 'TagManagerLaunchConfig'
      MinSize: !FindInMap [Config, !Ref 'Stage', MinSize]
      MaxSize: !FindInMap [Config, !Ref 'Stage', MaxSize]
      DesiredCapacity: !FindInMap [Config, !Ref 'Stage', DesiredCapacity]
      HealthCheckType: ELB
      HealthCheckGracePeriod: 300
      LoadBalancerNames:
      - !Ref 'TagManagerLoadBalancer'
      Tags:
      - Key: Stage
        Value: !Ref 'Stage'
        PropagateAtLaunch: 'true'
      - Key: Stack
        Value: flexible
        PropagateAtLaunch: 'true'
      - Key: App
        Value: tag-manager
        PropagateAtLaunch: 'true'
      - Key: LogKinesisStreamName
        Value: !Ref 'LoggingStreamName'
        PropagateAtLaunch: 'true'
      - Key: SystemdUnit
        Value: tag-manager.service
        PropagateAtLaunch: 'true'
  TagManagerDNSRecord: 
    Type: Guardian::DNS::RecordSet
    Properties:
      Name: !FindInMap [Config, !Ref 'Stage', DNSName]
      RecordType: CNAME
      ResourceRecords:
      - Fn::Join:
          - ""
          - - !GetAtt TagManagerCloudFrontDistribution.DomainName
            - "."
      Stage: !Ref Stage
      TTL: 3600
  TagManagerLaunchConfig:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      ImageId: !Ref 'AMI'
      SecurityGroups:
      - !Ref 'AppServerSecurityGroup'
      - !Ref 'VulnerabilityScanningSecurityGroup'
      InstanceType: !FindInMap [Config, !Ref 'Stage', InstanceType]
      IamInstanceProfile: !Ref 'TagManagerInstanceProfile'
      MetadataOptions:
        HttpTokens: required
      UserData:
        'Fn::Base64': !Sub |
          #!/bin/bash -ev
          aws s3 cp s3://composer-dist/flexible/${Stage}/tag-manager/tag-manager.deb /tmp
          dpkg -i /tmp/tag-manager.deb

  AssumeCapiPreviewRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: assume-capi-preview-role
      PolicyDocument:
        Statement:
        - Effect: Allow
          Action: sts:AssumeRole
          Resource:
            Ref: CapiPreviewRole
      Roles:
      - Ref: TagManagerRole
  Tagmanager5XXAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      TreatMissingData: notBreaching
      AlarmDescription: Tag manager is returning 5x responses
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: '10'
      EvaluationPeriods: '3'
      Metrics:
        - Expression: backend5XX + elb5XX
          Id: sum
          Label: "Count of Backend AND ELB 5XX"
          ReturnData: true
        - Id: backend5XX
          ReturnData: false
          Label: "backend5XX"
          MetricStat:
            Metric:
              MetricName: HTTPCode_Backend_5XX
              Namespace: AWS/ELB
              Dimensions:
              - Name: LoadBalancerName
                Value: !Ref 'TagManagerLoadBalancer'
            Period: 300
            Stat: Sum
            Unit: Count
        - Id: elb5XX
          ReturnData: false
          Label: "elb5XX"
          MetricStat:
            Metric:
              MetricName: HTTPCode_ELB_5XX
              Namespace: AWS/ELB
              Dimensions:
              - Name: LoadBalancerName
                Value: !Ref 'TagManagerLoadBalancer'
            Period: 300
            Stat: Sum
            Unit: Count
      AlarmActions:
        - !Ref 'AlertTopicArn'
      OKActions:
        - !Ref 'AlertTopicArn'
