---
Parameters:
  EnvironmentName:
    Type: String
    Description: Environment name that joins all the stacks

  AppMeshMeshName:
    Type: String
    Description: Name of mesh

  EnvoyImageName:
    Type: String
    Description: The image to use for the Envoy container

  ColorTellerImageName:
    Description: The name for the color teller image
    Type: String

Metadata:
  cfn-lint:
    config:
      ignore_checks:
        - E3012
        - E3002
Resources:

  ColorTellerServiceDiscoveryRecord:
    Type: 'AWS::ServiceDiscovery::Service'
    Properties:
      Name: "colorteller"
      DnsConfig:
        NamespaceId:
          'Fn::ImportValue': !Sub "${EnvironmentName}:ECSServiceDiscoveryNamespace"
        DnsRecords:
          - Type: A
            TTL: 300
      HealthCheckCustomConfig:
        FailureThreshold: 1

  ColorTellerTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - 'FARGATE'
      Family: 'colorteller'
      NetworkMode: 'awsvpc'
      Cpu: 256
      Memory: 512
      TaskRoleArn:
        'Fn::ImportValue': !Sub "${EnvironmentName}:TaskIamRoleArn"
      ExecutionRoleArn: 
        'Fn::ImportValue': !Sub "${EnvironmentName}:TaskExecutionIamRoleArn"
      ProxyConfiguration:
        Type: 'APPMESH'
        ContainerName: 'envoy'
        ProxyConfigurationProperties:
          - Name: 'IgnoredUID'
            Value: '1337'
          - Name: 'ProxyIngressPort'
            Value: '15000'
          - Name: 'ProxyEgressPort'
            Value: '15001'
          - Name: 'AppPorts'
            Value: '9080'
          - Name: 'EgressIgnoredIPs'
            Value: '169.254.170.2,169.254.169.254'
      ContainerDefinitions:
        - Name: 'app'
          Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ColorTellerImageName}'
          Essential: true
          DependsOn:
            - ContainerName: 'envoy'
              Condition: 'HEALTHY'
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group:
                Fn::ImportValue: !Sub "${EnvironmentName}:ECSServiceLogGroup"
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'colorteller'
          PortMappings:
            - ContainerPort: 9080
              Protocol: 'http'
          Environment:
            - Name: 'PORT'
              Value: 9080
            - Name: 'COLOR'
              Value: 'yellow'
        - Name: envoy
          Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EnvoyImageName}'
          Essential: true
          User: '1337'
          Ulimits:
            - Name: "nofile"
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: 9901
              Protocol: 'tcp'
          HealthCheck:
            Command:
              - 'CMD-SHELL'
              - 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE'
            Interval: 5
            Timeout: 2
            Retries: 3
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group:
                Fn::ImportValue: !Sub "${EnvironmentName}:ECSServiceLogGroup"
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'colorteller'
          Environment:
            - Name: 'APPMESH_RESOURCE_ARN'
              Value: !Sub 'mesh/${AppMeshMeshName}/virtualNode/colorteller-vn'
            - Name: AWS_REGION
              Value: !Ref 'AWS::Region'
            - Name: ENVOY_LOG_LEVEL
              Value: warning
          # Set secret environment variable for the container
          Secrets:
            - Name: CertSecret
              ValueFrom: {'Fn::ImportValue': !Sub "${EnvironmentName}:SecretCertArn"}

  ColorTellerService:
    Type: 'AWS::ECS::Service'
    Properties:
      Cluster:
        'Fn::ImportValue': !Sub "${EnvironmentName}:ECSCluster"
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DesiredCount: 1
      LaunchType: FARGATE
      ServiceRegistries:
        - RegistryArn:
            'Fn::GetAtt': ColorTellerServiceDiscoveryRecord.Arn
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - 'Fn::ImportValue': !Sub "${EnvironmentName}:ECSServiceSecurityGroup"
          Subnets: 
            - 'Fn::ImportValue': !Sub "${EnvironmentName}:PrivateSubnet1"
            - 'Fn::ImportValue': !Sub "${EnvironmentName}:PrivateSubnet2"
      TaskDefinition: { Ref: ColorTellerTaskDefinition }

  GatewayServiceDiscoveryRecord:
    Type: 'AWS::ServiceDiscovery::Service'
    Properties:
      Name: "gateway"
      DnsConfig:
        NamespaceId:
          'Fn::ImportValue': !Sub "${EnvironmentName}:ECSServiceDiscoveryNamespace"
        DnsRecords:
          - Type: A
            TTL: 300
      HealthCheckCustomConfig:
        FailureThreshold: 1

  GatewayTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      RequiresCompatibilities:
        - 'FARGATE'
      Family: 'gateway'
      NetworkMode: 'awsvpc'
      Cpu: 256
      Memory: 512
      TaskRoleArn:
        'Fn::ImportValue': !Sub "${EnvironmentName}:TaskIamRoleArn"
      ExecutionRoleArn:
        'Fn::ImportValue': !Sub "${EnvironmentName}:TaskExecutionIamRoleArn"
      ContainerDefinitions:
        - Name: envoy
          Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EnvoyImageName}'
          Essential: true
          User: '1337'
          Ulimits:
            - Name: "nofile"
              HardLimit: 15000
              SoftLimit: 15000
          PortMappings:
            - ContainerPort: 9080
              Protocol: 'tcp'
            - ContainerPort: 9901
              Protocol: 'tcp'
          HealthCheck:
            Command:
              - 'CMD-SHELL'
              - 'curl -s http://localhost:9901/server_info | grep state | grep -q LIVE'
            Interval: 5
            Timeout: 2
            Retries: 3
          LogConfiguration:
            LogDriver: 'awslogs'
            Options:
              awslogs-group:
                Fn::ImportValue: !Sub "${EnvironmentName}:ECSServiceLogGroup"
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: 'gateway'
          Environment:
            - Name: 'APPMESH_RESOURCE_ARN'
              Value: !Sub 'mesh/${AppMeshMeshName}/virtualGateway/gateway-vgw'
            - Name: AWS_REGION
              Value: !Ref 'AWS::Region'
            - Name: ENVOY_LOG_LEVEL
              Value: warning
            
          # Set secret environment variable for the container
          Secrets:
            - Name: CertSecret
              ValueFrom: {'Fn::ImportValue': !Sub "${EnvironmentName}:SecretCertArn"}

  GatewayService:
    Type: 'AWS::ECS::Service'
    DependsOn:
      - PublicLoadBalancer
    Properties:
      Cluster:
        'Fn::ImportValue': !Sub "${EnvironmentName}:ECSCluster"
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DesiredCount: 1
      LaunchType: FARGATE
      ServiceRegistries:
        - RegistryArn:
            'Fn::GetAtt': GatewayServiceDiscoveryRecord.Arn
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
            - 'Fn::ImportValue': !Sub "${EnvironmentName}:ECSServiceSecurityGroup"
          Subnets: 
            - 'Fn::ImportValue': !Sub "${EnvironmentName}:PrivateSubnet1"
            - 'Fn::ImportValue': !Sub "${EnvironmentName}:PrivateSubnet2"
      LoadBalancers:
        - ContainerName: envoy
          ContainerPort: 9080
          TargetGroupArn: !Ref WebTargetGroup
      TaskDefinition: { Ref: GatewayTaskDefinition }

  # public NLB for gateway
  PublicLoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      Type: network
      Subnets:
        - { 'Fn::ImportValue': !Sub "${EnvironmentName}:PublicSubnet1" }  
        - { 'Fn::ImportValue': !Sub "${EnvironmentName}:PublicSubnet2" }  

  WebTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    DependsOn:
      - PublicLoadBalancer
    Properties:
      Protocol: TCP
      TargetType: ip
      Name: !Sub "${EnvironmentName}-web2"
      Port: 80
      TargetGroupAttributes:
        - Key: deregistration_delay.timeout_seconds
          Value: 120
      VpcId:
        'Fn::ImportValue': !Sub "${EnvironmentName}:VPC"
  
  PublicLoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - TargetGroupArn: !Ref WebTargetGroup
          Type: 'forward'
      LoadBalancerArn: !Ref 'PublicLoadBalancer'
      Port: 80
      Protocol: TCP

  CertExpirationEvent:
    Type: AWS::Events::Rule
    Properties: 
      Description: "AWS ACM Certificate Expiration Event for Color Teller and Color Gateway"
      EventPattern: 
        source:
          - aws.acm
        resources:
          - 
            'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorTellerEndpointCertArn"
          - 
            'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorGatewayEndpointCertArn"
        detail-type:
          - ACM Certificate Approaching Expiration
      State: "ENABLED"
      Targets: 
        - 
          Arn: !GetAtt  CertRotateFunction.Arn
          Id: "TargetFunctionV1"
  
  PermissionForEventsToInvokeLambda: 
    Type: AWS::Lambda::Permission
    Properties: 
      FunctionName: !Ref CertRotateFunction
      Action: "lambda:InvokeFunction"
      Principal: "events.amazonaws.com"
      SourceArn: !GetAtt CertExpirationEvent.Arn
      SourceAccount: !Ref 'AWS::AccountId'

  CertRotateRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: [lambda.amazonaws.com]
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
      Policies:
        - PolicyName: acm-secretsmanager-access
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: 
                  - acm:ExportCertificate
                  - acm:RenewCertificate
                Resource: 
                  - 
                    'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorGatewayEndpointCertArn"
                  - 
                    'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorTellerEndpointCertArn"
              - Effect: Allow
                Action:
                  - secretsmanager:GetRandomPassword
                Resource: '*'
              - Effect: Allow
                Action: secretsmanager:PutSecretValue
                Resource: {'Fn::ImportValue': !Sub "${EnvironmentName}:SecretCertArn"}
              - Effect: Allow
                Action: ecs:UpdateService
                Resource:
                  - !Ref GatewayService
                  - !Ref ColorTellerService
              

  CertRotateFunction:
    Type: AWS::Lambda::Function
    Properties:
      Description: Initial function to populate secrets manager from ACM
      Handler: index.lambda_handler
      Role: !GetAtt CertRotateRole.Arn
      Runtime: python3.8
      Timeout: 900
      Environment:
        Variables:
          COLOR_GATEWAY_ACM_ARN: {'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorGatewayEndpointCertArn"}
          COLOR_TELLER_ACM_PCA_ARN: {'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorTellerRootCAArn"} 
          COLOR_GATEWAY_ACM_PCA_ARN: {'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorGatewayRootCAArn"} 
          COLOR_TELLER_ACM_ARN: {'Fn::ImportValue': !Sub "${EnvironmentName}:AcmPcaColorTellerEndpointCertArn"} 
          CLUSTER: {'Fn::ImportValue': !Sub "${EnvironmentName}:ECSCluster"}
          SVC_TELLER: !Ref GatewayService
          SVC_GATEWAY: !Ref ColorTellerService
          SECRET: {'Fn::ImportValue': !Sub "${EnvironmentName}:SecretCertArn"}
      Code:
        ZipFile: |
          import json
          import boto3
          import base64
          import time
          import os
          sm = boto3.client('secretsmanager')
          cm = boto3.client('acm')
          ecs = boto3.client('ecs')
          ecs_cluster = os.environ['CLUSTER']
          color_gateway_svc = os.environ['SVC_GATEWAY']
          color_teller_svc = os.environ['SVC_TELLER']
          gate_cm = os.environ['COLOR_GATEWAY_ACM_ARN']
          teller_cm = os.environ['COLOR_TELLER_ACM_ARN']
          secret = os.environ['SECRET']
          def lambda_handler(event, context):
            print (json.dumps(event))
          
            cm.renew_certificate(CertificateArn=teller_cm)
            cm.renew_certificate(CertificateArn=gate_cm)
            time.sleep(5) # allow time for acm to renew cert from acm-pca
            passphrase = sm.get_random_password(ExcludePunctuation=True)['RandomPassword']
            passphrase_enc = base64.b64encode(passphrase.encode('utf-8'))
            cm.export_certificate(CertificateArn=teller_cm, Passphrase=passphrase_enc)
            gate_rsp = cm.export_certificate(CertificateArn=gate_cm, Passphrase=passphrase_enc)
            sm_value={}
            sm_value['GatewayCertificate']=gate_rsp['Certificate']
            sm_value['GatewayCertificateChain']=gate_rsp['CertificateChain']
            sm_value['GatewayPrivateKey']=gate_rsp['PrivateKey']
            sm_value['Passphrase']=passphrase
            sm.put_secret_value(SecretId=secret, SecretString=json.dumps(sm_value))
            ecs.update_service(
              cluster=ecs_cluster,
              service=color_teller_svc,
              forceNewDeployment=True)
            ecs.update_service(
              cluster=ecs_cluster,
              service=color_gateway_svc,
              forceNewDeployment=True)
            

Outputs: 

  ColorAppEndpoint:
    Description: Public endpoint for Color App service
    Value: !Join ['', ['http://', !GetAtt 'PublicLoadBalancer.DNSName']]

