AWSTemplateFormatVersion: 2010-09-09
Description: gateway
Parameters:
  AMI:
    Description: AMI to use in the autoscaling group
    Type: String
  App:
    Description: Application name
    Default: identity-gateway
    Type: String
  IdapiBaseUrl:
    Description: The base url for idapi
    Type: AWS::SSM::Parameter::Value<String>
  SignInPageUrl:
    Description: Sign in page URL used as a fallback if auth redirect retrieval fails.
    Type: AWS::SSM::Parameter::Value<String>
  OauthBaseUrl:
    Description: The base url for social sign-in with google or apple
    Type: AWS::SSM::Parameter::Value<String>
  OktaOrgUrl:
    Description: The org url for interacting with okta APIs
    Type: AWS::SSM::Parameter::Value<String>
  MembersDataApiUrl:
    Description: The url for members-data-api
    Type: AWS::SSM::Parameter::Value<String>
  RedisHost:
    Description: The Redis server URL, used for rate limiting.
    Type: AWS::SSM::Parameter::Value<String>
  CertificateArn:
    Description: ARN of certificate for loadbalancer use.
    Type: String
  KeyName:
    Description: The EC2 Key Pair to allow SSH access to the instances
    Type: AWS::EC2::KeyPair::KeyName
  AlarmEmailAddress:
    Description: Email address to send CloudWatch alerts
    Type: String
  PrivateVpcSubnets:
    Description: Private VPC Subnet for the EC2 instances.
    Type: List<AWS::EC2::Subnet::Id>
  PublicVpcSubnets:
    Description: Public VPC Subnet for the ELB
    Type: List<AWS::EC2::Subnet::Id>
  Stack:
    Description: tag
    Default: identity
    Type: String
  Stage:
    Description: Deployment environment
    Default: CODE
    Type: String
    AllowedValues:
      - CODE
      - PROD
  SsmManagedPolicyArn:
    Description: The ARN for the ssm iam role
    Type: String
  VpcId:
    Description: VPC id of VPC for this service to use
    Type: String
  KinesisStream:
    Description: Kinesis Stream name for logging
    Type: AWS::SSM::Parameter::Value<String>
    Default: /account/services/logging.stream/name
  LoadBalancerLogsS3Bucket:
    Description: S3 Bucket to write ELB access logs to
    Type: String
  IdentityArtifactBucket:
    Description: S3 Bucket to read identity artifacts from
    Type: String
  IdentityConfigBucket:
    Description: S3 Bucket to read identity config from
    Type: String
Mappings:
  StageVariables:
    CODE:
      BaseUri: profile.code.dev-theguardian.com
      DefaultReturnUri: https://m.code.dev-theguardian.com
      MaxInstances: 2
      MinInstances: 1
      LatencyAlarmThreshold: 5
      LatencyAlarmPeriod: 1200
    PROD:
      BaseUri: profile.theguardian.com
      DefaultReturnUri: https://theguardian.com
      MaxInstances: 12
      MinInstances: 3
      LatencyAlarmThreshold: 0.5
      LatencyAlarmPeriod: 60
    AlarmPriorities:
      P1: 'CRITICAL Respond Immediately'
      P2: 'URGENT 9-5'
      P3: 'MODERATE'
Conditions:
  IsProd: !Equals [!Ref Stage, PROD]
  NotIsProd: !Not [!Equals [!Ref Stage, PROD]]
Resources:
  AppRole:
    Type: AWS::IAM::Role
    Properties:
      Path: '/'
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - !Ref SsmManagedPolicyArn
      Policies:
        - PolicyName: root
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: s3:GetObject
                Resource:
                  - !Sub arn:aws:s3:::${IdentityArtifactBucket}/${Stage}/${App}/*
              - Effect: Allow
                Action: s3:GetObject
                Resource:
                  - !Sub arn:aws:s3:::${IdentityConfigBucket}/${Stage}/${App}/*
        - PolicyName: DevxLogs
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - ec2:DescribeTags
                  - ec2:DescribeInstances
                Resource: '*'
              - Effect: Allow
                Action:
                  - kinesis:DescribeStream
                  - kinesis:PutRecord
                  - kinesis:PutRecords
                Resource:
                  - !Sub arn:aws:kinesis:${AWS::Region}:${AWS::AccountId}:stream/${KinesisStream}
        - PolicyName: GetParameters
          PolicyDocument:
            Statement:
              - Effect: Allow
                Action:
                  - ssm:GetParameter
                Resource:
                  - !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${Stack}/${App}/${Stage}/*
        - PolicyName: PushMetrics
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: cloudwatch:PutMetricData
                Resource: '*'
        - PolicyName: DescribeASG
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - autoscaling:DescribeAutoScalingGroups
                  - autoscaling:DescribeAutoScalingInstances
                Resource: '*'
        - PolicyName: SendEmailSES
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: ses:SendEmail
                Resource: '*'
        - PolicyName: SSMTunnel
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - ec2messages:AcknowledgeMessage
                  - ec2messages:DeleteMessage
                  - ec2messages:FailMessage
                  - ec2messages:GetEndpoint
                  - ec2messages:GetMessages
                  - ec2messages:SendReply
                  - ssm:UpdateInstanceInformation
                  - ssm:ListInstanceAssociations
                  - ssm:DescribeInstanceProperties
                  - ssm:DescribeDocumentParameters
                  - ssmmessages:CreateControlChannel
                  - ssmmessages:CreateDataChannel
                  - ssmmessages:OpenControlChannel
                  - ssmmessages:OpenDataChannel
                Resource: '*'
  GithubActionsSESSendEmailsRole:
    Type: AWS::IAM::Role
    Condition: NotIsProd
    Properties:
      RoleName: GithubActionsSESSendEmailsRole
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Action: sts:AssumeRoleWithWebIdentity
            Principal:
              Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com
            Condition:
              StringLike:
                # All GitHub Actions running from the guardian/gateway repository.
                token.actions.githubusercontent.com:sub: !Sub repo:guardian/gateway:*
      Policies:
        - PolicyName: SendEmailSES
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: ses:SendEmail
                Resource: '*'
  AutoScalingGroup:
    Type: AWS::AutoScaling::AutoScalingGroup
    Properties:
      LaunchTemplate:
        LaunchTemplateId: !Ref LaunchTemplate
        Version: !GetAtt 'LaunchTemplate.LatestVersionNumber'
      HealthCheckType: ELB
      HealthCheckGracePeriod: 300
      MinSize: !FindInMap [StageVariables, !Ref Stage, MinInstances]
      MaxSize: !FindInMap [StageVariables, !Ref Stage, MaxInstances]
      TargetGroupARNs:
        - !Ref TargetGroup
      VPCZoneIdentifier: !Ref PrivateVpcSubnets
      Tags:
        - Key: Name
          Value: !Sub '${Stage}:${App}'
          PropagateAtLaunch: 'true'
        - Key: App
          Value: !Ref App
          PropagateAtLaunch: true
        - Key: Stack
          Value: !Ref Stack
          PropagateAtLaunch: true
        - Key: Stage
          Value: !Ref Stage
          PropagateAtLaunch: true
        - Key: LogKinesisStreamName
          Value: !Ref KinesisStream
          PropagateAtLaunch: true
        - Key: SystemdUnit
          Value: identity-gateway.service
          PropagateAtLaunch: true
  LaunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: !Sub '${Stage}-${Stack}-${App}'
      LaunchTemplateData:
        ImageId: !Ref AMI
        IamInstanceProfile:
          Arn: !GetAtt 'InstanceProfile.Arn'
        InstanceType: t4g.small
        KeyName: !Ref KeyName
        SecurityGroupIds:
          - !Ref InstanceSecurityGroup
        MetadataOptions:
          HttpTokens: required
        UserData:
          Fn::Base64: !Sub
            - |+
              #!/bin/bash -ev

              mkdir /etc/gu

              # Get Riff Raff deployed artefact S3
              aws s3 cp s3://${IdentityArtifactBucket}/${Stage}/${App}/${App}.zip /etc/gu
              unzip -o /etc/gu/${App}.zip -d /etc/gu

              # Get Rate Limiter configuration file
              # Try multiple times to the config file. The s3 cp command can fail when called immediately on instance startup.
              while true; do
                if \
                  aws s3 cp s3://${IdentityConfigBucket}/${Stage}/${App}/.ratelimit.json /etc/gu/.ratelimit.json
                  then break
                fi
                sleep 1
              done

              # Setup user
              groupadd identity-gateway
              useradd -r -s /usr/bin/nologin -g identity-gateway identity-gateway
              chown -R identity-gateway:identity-gateway /etc/gu

              # Setup logs
              touch /var/log/identity-gateway.log
              chown identity-gateway:identity-gateway /var/log/identity-gateway.log

              # Try multiple times to get parameter store values. The SSM command can fail when called immediately on instance startup.
              while true; do
                if \
                  IDAPI_CLIENT_ACCESS_TOKEN=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/idapi-client-access-token' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text") && \
                  APP_SECRET=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/appSecret' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text") && \
                  GOOGLE_RECAPTCHA_SITE_KEY=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/recaptcha-site-key' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text") && \
                  GOOGLE_RECAPTCHA_SECRET_KEY=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/recaptcha-secret-key' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text") && \
                  REDIS_PASSWORD=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/redis-password' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text") && \
                  ENCRYPTION_SECRET_KEY=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/encryption-secret-key' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text") && \
                  OKTA_API_TOKEN=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/okta-api-token' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text") && \
                  OKTA_CUSTOM_OAUTH_SERVER=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/oktaCustomOAuthServer' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text") && \
                  OKTA_CLIENT_ID=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/oktaClientId' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text") && \
                  OKTA_CLIENT_SECRET=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/oktaClientSecret' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text")
                  OKTA_IDP_APPLE=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/oktaIdpApple' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text")
                  OKTA_IDP_GOOGLE=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/oktaIdpGoogle' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text")
                  OKTA_GUARDIAN_USERS_ALL_GROUP_ID=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/oktaGuardianUsersAllGroupId' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text")
                  USER_BENEFITS_API_URL=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/user-benefits-api-url' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text")
                  DELETE_ACCOUNT_STEP_FUNCTION_URL=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/deleteAccountStepFunctionUrl' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text")
                  DELETE_ACCOUNT_STEP_FUNCTION_API_KEY=$(aws ssm get-parameter --name '/${Stack}/${App}/${Stage}/deleteAccountStepFunctionApiKey' --with-decryption --region eu-west-1 --query="Parameter.Value" --output="text")
                  then break
                fi

                sleep 1
              done

              # systemd setup
              cat > /etc/systemd/system/identity-gateway.service <<EOL
              [Service]
              ExecStart=/bin/sh -ec '/usr/bin/node /etc/gu/server.js 2>&1'
              Restart=always
              StandardOutput=journal
              StandardError=journal
              SyslogIdentifier=identity-gateway
              User=identity-gateway
              Group=identity-gateway
              Environment=NODE_ENV=production
              Environment=PORT=9233
              Environment=IDAPI_CLIENT_ACCESS_TOKEN=$IDAPI_CLIENT_ACCESS_TOKEN
              Environment=IDAPI_BASE_URL=${IdapiBaseUrl}
              Environment=SIGN_IN_PAGE_URL=${SignInPageUrl}
              Environment=OAUTH_BASE_URL=${OauthBaseUrl}
              Environment=BASE_URI=${BaseUri}
              Environment=DEFAULT_RETURN_URI=${DefaultReturnUri}
              Environment=STAGE=${Stage}
              Environment=IS_HTTPS=true
              Environment=APP_SECRET=$APP_SECRET
              Environment=GOOGLE_RECAPTCHA_SITE_KEY=$GOOGLE_RECAPTCHA_SITE_KEY
              Environment=GOOGLE_RECAPTCHA_SECRET_KEY=$GOOGLE_RECAPTCHA_SECRET_KEY
              Environment=ENCRYPTION_SECRET_KEY=$ENCRYPTION_SECRET_KEY
              Environment=OKTA_ORG_URL=${OktaOrgUrl}
              Environment=OKTA_API_TOKEN=$OKTA_API_TOKEN
              Environment=OKTA_CUSTOM_OAUTH_SERVER=$OKTA_CUSTOM_OAUTH_SERVER
              Environment=OKTA_CLIENT_ID=$OKTA_CLIENT_ID
              Environment=OKTA_CLIENT_SECRET=$OKTA_CLIENT_SECRET
              Environment=OKTA_IDP_APPLE=$OKTA_IDP_APPLE
              Environment=OKTA_IDP_GOOGLE=$OKTA_IDP_GOOGLE
              Environment=OKTA_GUARDIAN_USERS_ALL_GROUP_ID=$OKTA_GUARDIAN_USERS_ALL_GROUP_ID            
              Environment=REDIS_PASSWORD=$REDIS_PASSWORD
              Environment=REDIS_HOST=${RedisHost}
              Environment=REDIS_SSL_ON=true
              Environment=MEMBERS_DATA_API_URL=${MembersDataApiUrl}
              Environment=USER_BENEFITS_API_URL=$USER_BENEFITS_API_URL
              Environment=DELETE_ACCOUNT_STEP_FUNCTION_URL=$DELETE_ACCOUNT_STEP_FUNCTION_URL
              Environment=DELETE_ACCOUNT_STEP_FUNCTION_API_KEY=$DELETE_ACCOUNT_STEP_FUNCTION_API_KEY

              [Install]
              WantedBy=multi-user.target
              EOL

              systemctl enable identity-gateway
              systemctl start identity-gateway

            - BaseUri: !FindInMap [StageVariables, !Ref Stage, BaseUri]
              DefaultReturnUri:
                !FindInMap [StageVariables, !Ref Stage, DefaultReturnUri]

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: '/'
      Roles:
        - !Ref AppRole
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow SSH and HTTPS access
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 9233
          ToPort: 9233
          SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Sub '${Stack}-${App}-${Stage}'
      Subnets: !Ref PublicVpcSubnets
      SecurityGroups:
        - !Ref LoadBalancerSecurityGroup
      Tags:
        - Key: App
          Value: !Ref App
        - Key: Stack
          Value: !Ref Stack
        - Key: Stage
          Value: !Ref Stage
      LoadBalancerAttributes:
        - Key: access_logs.s3.enabled
          Value: true
        - Key: access_logs.s3.bucket
          Value: !Ref 'LoadBalancerLogsS3Bucket'
        - Key: access_logs.s3.prefix
          Value: !Sub 'ELBLogs/${Stack}/${App}/${Stage}'
  LoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      Certificates:
        - CertificateArn: !Ref CertificateArn
      DefaultActions:
        - Type: forward
          TargetGroupArn:
            Ref: TargetGroup
      LoadBalancerArn:
        Ref: LoadBalancer
      Port: 443
      Protocol: HTTPS
      SslPolicy: ELBSecurityPolicy-TLS-1-2-2017-01
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTPS traffic and egress to 9233
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 9233
          ToPort: 9233
          CidrIp: 0.0.0.0/0
  TargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${Stage}-${Stack}-${App}
      Port: 9233
      Protocol: HTTP
      VpcId:
        Ref: VpcId
      HealthCheckIntervalSeconds: 10
      HealthCheckPath: /healthcheck
      HealthCheckPort: 9233
      HealthCheckProtocol: HTTP
      HealthCheckTimeoutSeconds: 5
      HealthyThresholdCount: 2
      UnhealthyThresholdCount: 2
      Matcher:
        HttpCode: 200
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 45
    DependsOn:
      - LoadBalancer
  TopicSendEmail:
    Type: AWS::SNS::Topic
    Condition: IsProd
    Properties:
      DisplayName: SendEmailToIdentityDev
      Subscription:
        - Endpoint: !Ref 'AlarmEmailAddress'
          Protocol: email
  HighLatencyScalingAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      AlarmName: !Sub
        - '${Priority} - ${App}-${Stage} high load balancer latency scaling'
        - Priority: !FindInMap [StageVariables, AlarmPriorities, P3]
      AlarmDescription: !Sub
        - 'Scale-Up if latency is greater than ${Threshold} seconds over last ${Period} seconds'
        - Period: !FindInMap [StageVariables, !Ref 'Stage', LatencyAlarmPeriod]
          Threshold:
            !FindInMap [StageVariables, !Ref 'Stage', LatencyAlarmThreshold]
      Namespace: AWS/ApplicationELB
      MetricName: TargetResponseTime
      Statistic: Average
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Dimensions:
        - Name: LoadBalancer
          Value: !GetAtt 'LoadBalancer.LoadBalancerFullName'
      Threshold:
        !FindInMap [StageVariables, !Ref 'Stage', LatencyAlarmThreshold]
      Period: !FindInMap [StageVariables, !Ref 'Stage', LatencyAlarmPeriod]
      EvaluationPeriods: 1
      AlarmActions:
        - !Ref 'ScaleUpPolicy'
      OKActions:
        - !Ref 'ScaleDownPolicy'
  ScaleUpPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AutoScalingGroupName: !Ref 'AutoScalingGroup'
      AdjustmentType: ChangeInCapacity
      ScalingAdjustment: 1
      Cooldown: 300
  ScaleDownPolicy:
    Type: AWS::AutoScaling::ScalingPolicy
    Properties:
      AdjustmentType: ChangeInCapacity
      AutoScalingGroupName: !Ref 'AutoScalingGroup'
      Cooldown: 1800
      ScalingAdjustment: -1
  AlarmHighLatency:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      AlarmName: !Sub
        - '${Priority} - ${App}-${Stage} high load balancer latency'
        - Priority: !FindInMap [StageVariables, AlarmPriorities, P3]
      AlarmDescription: !Sub
        - 'Latency is greater than ${Threshold} seconds over ${Period} seconds for last 5 periods'
        - Period: !FindInMap [StageVariables, !Ref 'Stage', LatencyAlarmPeriod]
          Threshold:
            !FindInMap [StageVariables, !Ref 'Stage', LatencyAlarmThreshold]
      Namespace: AWS/ApplicationELB
      MetricName: TargetResponseTime
      Statistic: Average
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Dimensions:
        - Name: LoadBalancer
          Value: !GetAtt 'LoadBalancer.LoadBalancerFullName'
      Threshold:
        !FindInMap [StageVariables, !Ref 'Stage', LatencyAlarmThreshold]
      Period: !FindInMap [StageVariables, !Ref 'Stage', LatencyAlarmPeriod]
      EvaluationPeriods: 5
      AlarmActions:
        - !Ref 'TopicSendEmail'
      InsufficientDataActions:
        - !Ref 'TopicSendEmail'
  AlarmNoHealthyHosts:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      ActionsEnabled: 'true'
      AlarmName: !Sub
        - '${Priority} - ${App}-${Stage} insufficient healthy hosts'
        - Priority: !FindInMap [StageVariables, AlarmPriorities, P3]
      AlarmDescription: There are insufficient healthy hosts
      ComparisonOperator: LessThanThreshold
      EvaluationPeriods: 1
      MetricName: HealthyHostCount
      Namespace: AWS/ApplicationELB
      Period: 60
      Statistic: Average
      Threshold: !FindInMap [StageVariables, !Ref 'Stage', MinInstances]
      AlarmActions:
        - !Ref 'TopicSendEmail'
      InsufficientDataActions:
        - !Ref 'TopicSendEmail'
      OKActions:
        - !Ref 'TopicSendEmail'
      Dimensions:
        - Name: LoadBalancer
          Value: !GetAtt 'LoadBalancer.LoadBalancerFullName'
        - Name: TargetGroup
          Value: !GetAtt 'TargetGroup.TargetGroupFullName'
    DependsOn:
      - TargetGroup
      - LoadBalancer
  Alarm5XXSustained:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      ActionsEnabled: 'true'
      AlarmName: !Sub
        - '${Priority} - ${App}-${Stage} sustained 5xx errors'
        - Priority: !FindInMap [StageVariables, AlarmPriorities, P2]
      AlarmDescription: 'Sustained server errors detected'
      AlarmActions:
        - !Ref 'TopicSendEmail'
      OKActions:
        - !Ref 'TopicSendEmail'
      InsufficientDataActions:
        - !Ref 'TopicSendEmail'
      ComparisonOperator: GreaterThanOrEqualToThreshold
      Threshold: 15
      EvaluationPeriods: 5
      TreatMissingData: notBreaching
      Metrics:
        - Id: total5XXCount
          Expression: backend5XXCount + elb5XXCount
          Label: 'Count of Backend AND ELB 5XX'
        - Id: backend5XXCount
          MetricStat:
            Metric:
              Namespace: AWS/ApplicationELB
              MetricName: HTTPCode_Target_5XX_Count
              Dimensions:
                - Name: LoadBalancer
                  Value: !GetAtt LoadBalancer.LoadBalancerFullName
                - Name: TargetGroup
                  Value: !GetAtt TargetGroup.TargetGroupFullName
            Period: 60
            Stat: Sum
            Unit: Count
          ReturnData: false
        - Id: elb5XXCount
          MetricStat:
            Metric:
              Namespace: AWS/ApplicationELB
              MetricName: HTTPCode_ELB_5XX_Count
              Dimensions:
                - Name: LoadBalancer
                  Value: !GetAtt LoadBalancer.LoadBalancerFullName
            Period: 60
            Stat: Sum
            Unit: Count
          ReturnData: false
  SigninInactivityAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      AlarmName: !Sub
        - '${Priority} - ${App} ${Stage} has had no new sign-ins in the last 20 minutes'
        - Priority: !FindInMap [StageVariables, AlarmPriorities, P1]
      AlarmDescription: No one has successfully signed ins in the last 20 minutes.
      Metrics:
        - Id: totalSignInCount
          Expression: oktaSignInCount + oktaIdxSignInCount
          Label: 'Total sign-ins in Okta'
        - Id: oktaSignInCount
          MetricStat:
            Metric:
              Namespace: Gateway
              MetricName: 'OktaSignIn::Success'
              Dimensions:
                - Name: Stage
                  Value: !Ref 'Stage'
                - Name: ApiMode
                  Value: identity-gateway
            Period: 1200
            Stat: Sum
            Unit: Count
          ReturnData: false
        - Id: oktaIdxSignInCount
          MetricStat:
            Metric:
              Namespace: Gateway
              MetricName: 'OktaIdxSignIn::Success'
              Dimensions:
                - Name: Stage
                  Value: !Ref 'Stage'
                - Name: ApiMode
                  Value: identity-gateway
            Period: 1200
            Stat: Sum
            Unit: Count
          ReturnData: false
      ComparisonOperator: LessThanThreshold
      Threshold: 1
      EvaluationPeriods: 1
      AlarmActions:
        - !Ref 'TopicSendEmail'
      InsufficientDataActions:
        - !Ref 'TopicSendEmail'
  RegisterInactivityAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      AlarmName: !Sub
        - '${Priority} - ${App} ${Stage} has had no new registrations in the last hour'
        - Priority: !FindInMap [StageVariables, AlarmPriorities, P1]
      AlarmDescription: No one has successfully registered in the last hour.
      Metrics:
        - Id: totalRegistrationCount
          Expression: oktaIdxRegistrationCount + oktaRegistrationCount
          Label: 'Total registrations in Okta Classic and Okta IDX'
        - Id: oktaIdxRegistrationCount
          MetricStat:
            Metric:
              Namespace: Gateway
              MetricName: 'OktaIDXRegister::Success'
              Dimensions:
                - Name: Stage
                  Value: !Ref 'Stage'
                - Name: ApiMode
                  Value: identity-gateway
            Period: 3600
            Stat: Sum
            Unit: Count
          ReturnData: false
        - Id: oktaRegistrationCount
          MetricStat:
            Metric:
              Namespace: Gateway
              MetricName: 'OktaRegistration::Success'
              Dimensions:
                - Name: Stage
                  Value: !Ref 'Stage'
                - Name: ApiMode
                  Value: identity-gateway
            Period: 3600
            Stat: Sum
            Unit: Count
          ReturnData: false
      ComparisonOperator: LessThanThreshold
      Threshold: 1
      EvaluationPeriods: 1
      AlarmActions:
        - !Ref 'TopicSendEmail'
      InsufficientDataActions:
        - !Ref 'TopicSendEmail'
  OAuthAuthenticationCallbackInactivityAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      AlarmName: !Sub
        - '${Priority} - ${App} ${Stage} has had no success OAuth Authorization code flow callbacks for Authentication in the last 20 minutes'
        - Priority: !FindInMap [StageVariables, AlarmPriorities, P1]
      AlarmDescription: No one has successfully completed OAuth Authorization code flow callbacks for Authentication in the last 20 minutes.
      Metrics:
        - Id: totalOAuthAuthenticationCallbackCount
          Expression: oktaOAuthAuthenticationCallbackCount
          Label: 'Total OAuth Authorization Callbacks for Authentication in Okta'
        - Id: oktaOAuthAuthenticationCallbackCount
          MetricStat:
            Metric:
              Namespace: Gateway
              MetricName: 'OAuthAuthenticationCallback::Success'
              Dimensions:
                - Name: Stage
                  Value: !Ref 'Stage'
                - Name: ApiMode
                  Value: identity-gateway
            Period: 1200
            Stat: Sum
            Unit: Count
          ReturnData: false
      ComparisonOperator: LessThanThreshold
      Threshold: 1
      EvaluationPeriods: 1
      AlarmActions:
        - !Ref 'TopicSendEmail'
      InsufficientDataActions:
        - !Ref 'TopicSendEmail'
  OAuthApplicationCallbackInactivityAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      AlarmName: !Sub
        - '${Priority} - ${App} ${Stage} has had no success OAuth Authorization code flow callbacks for internal Gateway routes in the last 1 hour'
        - Priority: !FindInMap [StageVariables, AlarmPriorities, P1]
      AlarmDescription: No one has successfully completed OAuth Authorization code flow callbacks for internal Gateway routes in the last 1 hour.
      Metrics:
        - Id: totalOAuthApplicationCallbackCount
          Expression: oktaOAuthApplicationCallbackCount
          Label: 'Total OAuth Authorization Callbacks for internal Gateway routes in Okta'
        - Id: oktaOAuthApplicationCallbackCount
          MetricStat:
            Metric:
              Namespace: Gateway
              MetricName: 'OAuthApplicationCallback::Success'
              Dimensions:
                - Name: Stage
                  Value: !Ref 'Stage'
                - Name: ApiMode
                  Value: identity-gateway
            Period: 3600
            Stat: Sum
            Unit: Count
          ReturnData: false
      ComparisonOperator: LessThanThreshold
      Threshold: 1
      EvaluationPeriods: 1
      AlarmActions:
        - !Ref 'TopicSendEmail'
      InsufficientDataActions:
        - !Ref 'TopicSendEmail'
  DeletionInactivityAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      AlarmName: !Sub
        - '${Priority} - ${App} ${Stage} has had no success self service user deletion in the last 6 hours'
        - Priority: !FindInMap [StageVariables, AlarmPriorities, P2]
      AlarmDescription: No one has successfully deleted their account in the last 6 hours.
      Metrics:
        - Id: totalDeletionCount
          Expression: deleteAccountCount
          Label: 'Total self service user deletions'
        - Id: deleteAccountCount
          MetricStat:
            Metric:
              Namespace: Gateway
              MetricName: 'OAuthDeleteCallback::Success'
              Dimensions:
                - Name: Stage
                  Value: !Ref 'Stage'
                - Name: ApiMode
                  Value: identity-gateway
            Period: 21600
            Stat: Sum
            Unit: Count
          ReturnData: false
      ComparisonOperator: LessThanThreshold
      Threshold: 1
      EvaluationPeriods: 1
      AlarmActions:
        - !Ref 'TopicSendEmail'
      InsufficientDataActions:
        - !Ref 'TopicSendEmail'
  UnsubscribeAllInactivityAlarm:
    Type: AWS::CloudWatch::Alarm
    Condition: IsProd
    Properties:
      AlarmName: !Sub
        - '${Priority} - ${App} ${Stage} has had successful no unsubscribe all from email clients in the last hour'
        - Priority: !FindInMap [StageVariables, AlarmPriorities, P2]
      AlarmDescription: 'No one has successfully unsubscribed all from email clients in the last hour.'
      Metrics:
        - Id: totalUnsubscribeAllCount
          Expression: unsubscribeAllCount
          Label: 'Total unsubscribe all'
        - Id: unsubscribeAllCount
          MetricStat:
            Metric:
              Namespace: Gateway
              MetricName: 'UnsubscribeAll::Success'
              Dimensions:
                - Name: Stage
                  Value: !Ref 'Stage'
                - Name: ApiMode
                  Value: identity-gateway
            Period: 3600
            Stat: Sum
            Unit: Count
          ReturnData: false
      ComparisonOperator: LessThanThreshold
      Threshold: 1
      EvaluationPeriods: 1
      AlarmActions:
        - !Ref 'TopicSendEmail'
      InsufficientDataActions:
        - !Ref 'TopicSendEmail'
