AWSTemplateFormatVersion: 2010-09-09

######## METADATA ########

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      -
        Label:
          default: "VPC Configuration"
        Parameters:
          - VPCId
          - Subnets
      -
        Label:
          default: "RDS Configuration"
        Parameters:
          - RDSInstanceType
          - DatabaseName
          - DatabaseUsername
          - DatabaseStorage
      -
        Label:
          default: "EC2 Configuration"
        Parameters:
          - InstanceType
          - KeyName
          - SSHLocation
          - AllowAPIAccessFrom
      -
        Label:
          default: "Passwords"
        Parameters:
          - TarPassword
          - ConfirmTarPassword

######## PARAMETERS ########

Parameters:
  VPCId:
    Default: ''
    Type: AWS::EC2::VPC::Id
    Description: "VPC Id of Virtual Private Cloud (VPC)"
  Subnets:
    Default: ''
    Type: 'List<AWS::EC2::Subnet::Id>'
    Description: "Select 2 'Public' Subnets belonging to above VPC. Subnets should be in above VPC"
  DatabaseName:
    Default: devmpc
    Description: "Database name for MPC. This is the name of mysql Database created inside RDS"
    Type: String
    MinLength: '1'
    MaxLength: '64'
    AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*'
    ConstraintDescription: must begin with a letter and contain only alphanumeric characters.
  DatabaseUsername:
    Default: admin
    Description: "The database admin account username"
    Type: String
    MinLength: '1'
    MaxLength: '16'
    AllowedPattern: '[a-zA-Z][a-zA-Z0-9]*'
    ConstraintDescription: must begin with a letter and contain only alphanumeric characters.
  TarPassword:
    NoEcho: 'true'
    Description: "Password for the zip file in your S3 bucket"
    Type: String
    MinLength: '5'
    MaxLength: '32'
    AllowedPattern: '[a-zA-Z0-9]+'
    ConstraintDescription: must contain only alphanumeric characters
  ConfirmTarPassword:
    NoEcho: 'true'
    Description: "Re-enter the password from the above step"
    Type: String
    MinLength: '5'
    MaxLength: '32'
    AllowedPattern: '[a-zA-Z0-9]+'
    ConstraintDescription: must contain only alphanumeric characters
  DatabaseStorage:
    Default: '20'
    Description: "The size of the database (GB), must be atleast 20GB"
    Type: Number
    MinValue: '20'
    MaxValue: '512'
    ConstraintDescription: must be between 20 and 512GB
  RDSInstanceType:
    Description: "RDS(Database) Instance Type, db.t3.medium recommended"
    Type: String
    Default: db.t3.medium
    AllowedValues: [db.t2.medium, db.t2.large, db.t3.medium, db.t3.large]
    ConstraintDescription: must select a valid database instance type.
  InstanceType:
    Description: "EC2 instance type, c5.large recommended"
    Type: String
    Default: c5.large
    AllowedValues: [t3.medium, m5.large, c5.large, r5.large]
    ConstraintDescription: must be a valid EC2 instance type.
  KeyName:
    Description: "Select an existing AWS EC2 KeyPair to enable SSH access to the EC2. Please create a new SSH key pair in case no SSH key is present in your account"
    Type: AWS::EC2::KeyPair::KeyName
    ConstraintDescription: must be the name of an existing EC2 KeyPair.
  SSHLocation:
    Description: "The IP address range that can be used to SSH to the EC2 instances"
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 0.0.0.0/0
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
  AllowAPIAccessFrom:
    Description: "Whitelist IP address that will have access to SDK API (port 8081)"
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 0.0.0.0/0
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.

######## RULES ########

Rules:
  SubnetsInVPC:
    Assertions:
      - Assert:
          'Fn::EachMemberEquals':
            - 'Fn::ValueOf':
                - Subnets
                - VpcId
            - Ref: VPCId
        AssertDescription: "Important: All subnets must be public and in the selected VPC"
  MatchTarPasswords:
    Assertions:
      - Assert:
          Fn::Equals:
            - Ref: 'TarPassword'
            - Ref: 'ConfirmTarPassword'
        AssertDescription: 'Tar Passwords do not match'

######## MAPPINGS ########

Mappings:
  RegionMap:
    us-east-1:
      ami: 'ami-0e487ab4e4901d12d'
    us-east-2:
      ami: 'ami-07a2731e1e01e594c'
    us-west-1:
      ami: 'ami-0817b0b6e8d63b8af'
    us-west-2:
      ami: 'ami-006f2c7501238b445'
    af-south-1:
      ami: 'ami-0e9cc72b0d70a9134'
    ap-southeast-3:
      ami: 'ami-03f3a1433fa76d564'
    ap-south-1:
      ami: 'ami-0b95f57d5bda253e9'
    ap-northeast-3:
      ami: 'ami-0f2bc885be5f0ebb3'
    ap-northeast-2:
      ami: 'ami-0801e2e1cb08462d2'
    ap-southeast-1:
      ami: 'ami-05d50a5d666b70cb5'
    ap-southeast-2:
      ami: 'ami-090d6d489cc10c0a6'
    ap-northeast-1:
      ami: 'ami-0dbc67a11d05b8486'
    ca-central-1:
      ami: 'ami-07f2f47b09bff36f9'
    eu-central-1:
      ami: 'ami-0c0f0e510d4020d77'
    eu-west-1:
      ami: 'ami-083b96c088848f597'
    eu-west-2:
      ami: 'ami-01f5055ed5ace8265'
    eu-west-3:
      ami: 'ami-0ca0d3a93aae02002'
    eu-north-1:
      ami: 'ami-06ff401588718320a'
    sa-east-1:
      ami: 'ami-0306d9b16b1df2f53'

######## RESOURCES ########

Resources:
  MPCSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: "dev-mpc-v52-encryptor-password"
      GenerateSecretString:
        ExcludeLowercase: false
        ExcludeNumbers: false
        ExcludePunctuation: true
        ExcludeUppercase: false
        ExcludeCharacters: '"@/\'
        IncludeSpace: false
        PasswordLength: 32
  RDSSecret:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: "dev-mpc-v52-rds-credentials"
      GenerateSecretString:
        ExcludeLowercase: false
        ExcludeNumbers: false
        ExcludePunctuation: true
        ExcludeUppercase: false
        ExcludeCharacters: '"@/\'
        IncludeSpace: false
        PasswordLength: 32
        SecretStringTemplate: !Join [ '', [ '{"username": "', !Ref DatabaseUsername, '"}' ] ]
        GenerateStringKey: 'password'
  MPCS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
          BlockPublicAcls: true
          BlockPublicPolicy: true
          IgnorePublicAcls: true
          RestrictPublicBuckets: true
  MPCKMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: ECC_SECG_P256K1 asymmetric KMS key for signing and verification
      MultiRegion: true
      KeySpec: ECC_SECG_P256K1
      KeyUsage: SIGN_VERIFY
      KeyPolicy:
        Version: '2012-10-17'
        Id: key-default-1
        Statement:
        - Sid: Enable IAM User Permissions
          Effect: Allow
          Principal:
            AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
          Action: 'kms:*'
          Resource: '*'
  MPCKMSKeyAlias:
    Type: AWS::KMS::Alias
    Properties:
      AliasName: alias/liminal-v52-kms
      TargetKeyId: !Ref 'MPCKMSKey'
  EC2KMSIAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: dev-mpc-v52-ec2-iam-role
      Description: "IAM role allowing EC2 to access KMS"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - 'sts:AssumeRole'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Effect: Allow
  EC2KMSIAMRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: dev-mpc-v52-ec2-policy
      Roles:
      - !Ref 'EC2KMSIAMRole'
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - 'kms:GetPublicKey'
              - 'kms:ListKeyPolicies'
              - 'kms:ListRetirableGrants'
              - 'kms:GetKeyPolicy'
              - 'kms:Verify'
              - 'kms:ListResourceTags'
              - 'kms:ListGrants'
              - 'kms:GetParametersForImport'
              - 'kms:DescribeCustomKeyStores'
              - 'kms:ListKeys'
              - 'kms:GetKeyRotationStatus'
              - 'kms:ListAliases'
              - 'kms:DescribeKey'
              - 'kms:Sign'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ecr:BatchCheckLayerAvailability'
              - 'ecr:BatchGetImage'
              - 'ecr:CompleteLayerUpload'
              - 'ecr:GetDownloadUrlForLayer'
              - 'ecr:InitiateLayerUpload'
              - 'ecr:PutImage'
              - 'ecr:UploadLayerPart'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ecr:GetAuthorizationToken'
            Resource: "*"
          - Effect: Allow
            Action:
              - 's3:PutObject'
            Resource: !Sub 'arn:aws:s3:::${MPCS3Bucket}/*'
          - Effect: Allow
            Action:
              - 'secretsmanager:GetSecretValue'
            Resource:
              - !Ref 'MPCSecret'
              - !Ref 'RDSSecret'
          - Effect: Allow
            Action:
              - 'ssm:DescribeAssociation'
              - 'ssm:GetDeployablePatchSnapshotForInstance'
              - 'ssm:GetDocument'
              - 'ssm:DescribeDocument'
              - 'ssm:GetManifest'
              - 'ssm:GetParameter'
              - 'ssm:GetParameters'
              - 'ssm:ListAssociations'
              - 'ssm:ListInstanceAssociations'
              - 'ssm:PutInventory'
              - 'ssm:PutComplianceItems'
              - 'ssm:PutConfigurePackageResult'
              - 'ssm:UpdateAssociationStatus'
              - 'ssm:UpdateInstanceAssociationStatus'
              - 'ssm:UpdateInstanceInformation'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ssmmessages:CreateControlChannel'
              - 'ssmmessages:CreateDataChannel'
              - 'ssmmessages:OpenControlChannel'
              - 'ssmmessages:OpenDataChannel'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ec2messages:AcknowledgeMessage'
              - 'ec2messages:DeleteMessage'
              - 'ec2messages:FailMessage'
              - 'ec2messages:GetEndpoint'
              - 'ec2messages:GetMessages'
              - 'ec2messages:SendReply'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ssm:DescribeEffectivePatchesForPatchBaseline'
            Resource: "arn:aws:ssm:*:*:patchbaseline/*"
          - Effect: Allow
            Action:
              - 'ssm:GetPatchBaseline'
            Resource: "arn:aws:ssm:*:*:patchbaseline/*"
          - Effect: Allow
            Action:
              - 'tag:GetResources'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ssm:DescribePatchBaselines'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ssm:DescribeAssociation'
              - 'ssm:GetDeployablePatchSnapshotForInstance'
              - 'ssm:GetDocument'
              - 'ssm:DescribeDocument'
              - 'ssm:GetManifest'
              - 'ssm:GetParameter'
              - 'ssm:GetParameters'
              - 'ssm:ListAssociations'
              - 'ssm:ListInstanceAssociations'
              - 'ssm:PutInventory'
              - 'ssm:PutComplianceItems'
              - 'ssm:PutConfigurePackageResult'
              - 'ssm:UpdateAssociationStatus'
              - 'ssm:UpdateInstanceAssociationStatus'
              - 'ssm:UpdateInstanceInformation'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ssmmessages:CreateControlChannel'
              - 'ssmmessages:CreateDataChannel'
              - 'ssmmessages:OpenControlChannel'
              - 'ssmmessages:OpenDataChannel'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ec2messages:AcknowledgeMessage'
              - 'ec2messages:DeleteMessage'
              - 'ec2messages:FailMessage'
              - 'ec2messages:GetEndpoint'
              - 'ec2messages:GetMessages'
              - 'ec2messages:SendReply'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'elasticfilesystem:DescribeMountTargets'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ec2:DescribeAvailabilityZones'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'logs:PutLogEvents'
              - 'logs:DescribeLogStreams'
              - 'logs:DescribeLogGroups'
              - 'logs:CreateLogStream'
              - 'logs:CreateLogGroup'
              - 'logs:PutRetentionPolicy'
            Resource: "*"
  EC2MPCInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: dev-mpc-v52-instance-profile
      Path: "/"
      Roles:
      - Ref: 'EC2KMSIAMRole'
  MPCIAMRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: dev-mpc-v52-snapshot-iam-role
      Description: "IAM role for Liminal MPC EC2 auto snapshots"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - 'sts:AssumeRole'
            Principal:
              Service:
                - 'dlm.amazonaws.com'
            Effect: Allow
            Sid: ''
  MPCIAMRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: dev-mpc-v52-snapshot-iam-policy
      Roles:
      - !Ref 'MPCIAMRole'
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - 'ec2:CreateSnapshot'
              - 'ec2:CreateSnapshots'
              - 'ec2:DeleteSnapshot'
              - 'ec2:DescribeInstances'
              - 'ec2:DescribeVolumes'
              - 'ec2:DescribeSnapshots'
            Resource: "*"
          - Effect: Allow
            Action:
              - 'ec2:CreateTags'
            Resource: "arn:aws:ec2:*::snapshot/*"
  MpcLifecyclePolicy:
    Type: AWS::DLM::LifecyclePolicy
    Properties:
      Description: Liminal MPC EC2 auto snapshot policy
      ExecutionRoleArn: !GetAtt 'MPCIAMRole.Arn'
      State: ENABLED
      PolicyDetails:
        ResourceTypes:
        - INSTANCE
        Schedules:
        - Name: dev-mpc-v52-ec2-snapshot
          CreateRule:
            Interval: 12
            IntervalUnit: HOURS
            Times:
            - '10:00'
          RetainRule:
            Count: 14
          TagsToAdd:
          - Key: type
            Value: DailySnapshot
          CopyTags: true
        TargetTags:
        - Key: Name
          Value: 'liminal-dev-mpc-v52'
  SubnetGroup:
    Type: "AWS::RDS::DBSubnetGroup"
    Properties:
      DBSubnetGroupName: "dev-mpc-v52-subnet-group"
      DBSubnetGroupDescription: "MPC Subnet Group"
      SubnetIds: !Split [',', !Join [',', !Ref Subnets]]
  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Enable MPC required access
      VpcId: !Ref 'VPCId'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp: !Ref 'SSHLocation'
      - IpProtocol: tcp
        FromPort: '8081'
        ToPort: '8081'
        CidrIp: !Ref 'AllowAPIAccessFrom'
      - IpProtocol: tcp
        FromPort: '9000'
        ToPort: '9000'
        CidrIp: '52.220.56.76/32'
  DBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Open database for access from MPC server
      VpcId: !Ref 'VPCId'
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '3306'
        ToPort: '3306'
        CidrIp: '0.0.0.0/0'
  MpcDB:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      DBName: !Ref 'DatabaseName'
      DBSubnetGroupName: !Ref 'SubnetGroup'
      DBInstanceIdentifier: 'liminal-tsm-rds'
      DBInstanceClass: !Ref 'RDSInstanceType'
      Engine: MySQL
      EngineVersion: "8.0.43"
      MasterUsername: !Ref 'DatabaseUsername'
      MasterUserPassword: !Join [ '', [ '{{resolve:secretsmanager:', !Ref RDSSecret, ':SecretString:password}}' ] ]
      Port: '3306'
      AllocatedStorage: !Ref 'DatabaseStorage'
      StorageType: 'gp2'
      BackupRetentionPeriod: '14'
      StorageEncrypted: 'true'
      PubliclyAccessible: 'false'
      MultiAZ: 'false'
      VPCSecurityGroups: 
      - Fn::GetAtt: [DBSecurityGroup, GroupId]
      Tags:
      - Key: Name
        Value: liminal-tsm-rds
  EC2Instance:
    Type: AWS::EC2::Instance
    Properties:
      UserData:
        Fn::Base64: 
          !Sub |
            #!/bin/bash
            sudo apt-get update
            cd /home/ubuntu
            export DB_HOST="${MpcDB.Endpoint.Address}"
            export DB_USER="${DatabaseUsername}"
            export DB_NAME="${DatabaseName}"
            export TAR_PASSWD="${TarPassword}"
            export REGION="${AWS::Region}"
            export BUCKET_NAME="${MPCS3Bucket}"
            rdspasswd=`aws secretsmanager get-secret-value --region "$REGION" --secret-id "${RDSSecret}" --query SecretString --output text | jq -r .password`
            sed -i 's"/tmp/tsmdb"'$DB_USER':'$rdspasswd'@tcp('$DB_HOST:3306')/'$DB_NAME'?parseTime=true"' /home/ubuntu/config/config.toml
            passwd=`aws secretsmanager get-secret-value --region "$REGION" --secret-id "${MPCSecret}" --query SecretString --output text`
            sed -i 's"ENCRYPTION_KEY"'$passwd'"' /home/ubuntu/config/config.toml
            sed -i 's"YOUR_AWS_DEFAULT_REGION"'$REGION'"' /home/ubuntu/express.sh
            /bin/bash generate-key.sh
            /bin/bash run-docker.sh
            sleep 5
            /bin/bash tsm.sh
            /bin/bash replace-ip.sh
            /bin/bash express.sh
            /usr/bin/zip --password "${TarPassword}" infrastructure-config.zip -r config
            sleep 2
            aws s3 cp /home/ubuntu/infrastructure-config.zip s3://"${MPCS3Bucket}"/
      InstanceType: !Ref 'InstanceType'
      SubnetId: !Select [0, !Ref Subnets]
      Tags:
        - Key: 'Name'
          Value: 'liminal-dev-mpc-v52'
      SecurityGroupIds: 
      - Fn::GetAtt: [InstanceSecurityGroup, GroupId] 
      KeyName: !Ref 'KeyName'
      IamInstanceProfile: !Ref 'EC2MPCInstanceProfile'
      ImageId: !FindInMap
        - RegionMap
        - !Ref 'AWS::Region'
        - ami
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeSize: 20
            VolumeType: 'gp2'
            Encrypted: 'true'
            DeleteOnTermination: 'true'
  IPAddress:
    Type: AWS::EC2::EIP
  IPAssoc:
    Type: AWS::EC2::EIPAssociation
    Properties:
      InstanceId: !Ref 'EC2Instance'
      EIP: !Ref 'IPAddress'
Outputs:
  InstanceId:
    Description: InstanceId of the newly created EC2 instance
    Value: !Ref 'EC2Instance'
  InstanceIPAddress:
    Description: IP address of the newly created EC2 instance
    Value: !Ref 'IPAddress'
  Bucket:
    Description: S3 Bucket Name
    Value: !Ref 'MPCS3Bucket'
