AWSTemplateFormatVersion: 2010-09-09
Description: Deploys RDS Aurora for Kion.
Metadata:
  'AWS::CloudFormation::Interface':
    ParameterGroups:
      - Label:
          default: App Configuration
        Parameters:
          - Namespace
      - Label:
          default: Networking
        Parameters:
          - VPCId
          - PrivateSubnets
          - AvailabilityZones
      - Label:
          default: Database
        Parameters:
          - DatabaseUsername
          - DatabasePassword
          - InstanceType
          - NodeCount
          - Engine
          - EngineVersion
          - DeletionProtection
          - RDSEncryptionType
          - KMSKeyArn
          - DatabaseSnapshotId
          - BacktrackWindowParameter
          - PerformanceInsights
          - AutomaticMinorVersionUpgrade
      - Label:
          default: Logging
        Parameters:
          - ExportAuditLogs
          - ExportErrorLogs
          - ExportSlowQueryLogs
          - ExportGeneralLogs
    ParameterLabels:
      Namespace:
        default: Unique Namespace
      DatabaseUsername:
        default: DB Username
      DatabasePassword:
        default: DB Password
      VPCId:
        default: VPC Id
      PrivateSubnets:
        default: Subnets
      AvailabilityZones:
        default: Availability Zones
      InstanceType:
        default: Instance Type
      NodeCount:
        default: Number of Nodes
      Engine:
        default: Database Cluster Engine
      EngineVersion:
        default: Database Engine Version
      RDSEncryptionType:
        default: RDS Encryption Type
      DeletionProtection:
        default: Deletion Protection for Database Cluster
      KMSKeyArn:
        default: KMS Key ARN
      DatabaseSnapshotId:
        default: DB Snapshot ARN
      BacktrackWindowParameter:
        default: Backtrack Window
      PerformanceInsights:
        default: Enable Performance Insights
      AutomaticMinorVersionUpgrade:
        default: Enable Automatic Minor Version Upgrades
      ExportAuditLogs:
        default: Export Audit Logs
      ExportErrorLogs:
        default: Export Error Logs
      ExportSlowQueryLogs:
        default: Export Slow Query Logs
      ExportGeneralLogs:
        default: Export General Logs

Parameters:
  Namespace:
    Description: >-
      Unique namespace for Kion in this AWS account. It's recommended
      to leave this as the default unless directed to change it.
    Type: String
    Default: kion
    MinLength: '1'
  DatabaseUsername:
    AllowedPattern: '[a-zA-Z0-9]+'
    ConstraintDescription: must be between 1 to 16 alphanumeric characters.
    Description: >-
      Database admin account user name, between 1 to 16 alphanumeric characters.
      This value is case-sensitive.
    MaxLength: '16'
    MinLength: '1'
    Type: String
  DatabasePassword:
    AllowedPattern: '^[^/"@ '']+$'
    ConstraintDescription: must be between 8 to 41 characters excluding / " @ ' or a space.
    Description: >-
      Database admin account password, between 8 to 41 characters. This value is
      case-sensitive.
    MaxLength: '41'
    MinLength: '8'
    NoEcho: 'true'
    Type: String
  VPCId:
    Type: 'AWS::EC2::VPC::Id'
    Description: VPC for the database.
    MinLength: '1'
  PrivateSubnets:
    Type: 'List<AWS::EC2::Subnet::Id>'
    Description: >-
      Subnets for the database. These must exist in the VPC specified above.
      Must choose at least 2 subnets in different availability zones.
    MinLength: '1'
  AvailabilityZones:
    Type: 'List<AWS::EC2::AvailabilityZone::Name>'
    Description: >-
      Availability zones for the database. Must choose at least 2 availability
      zones that are available in the subnets above.
    MinLength: '1'
  InstanceType:
    Type: String
    Description: >-
      Instance type for the database. It's recommended not to use anything
      smaller than a db.t3.medium. Use a db.r3.large in a dedicated tenancy VPC.
    Default: db.r5.large
    MinLength: '1'
  NodeCount:
    Type: Number
    Default: 1
    Description: >-
      Number of database nodes. It's recommended to match the number of private
      subnets for high-availability.
    AllowedValues:
      - 1
      - 2
      - 3
  Engine:
    Type: String
    Default: aurora-mysql
    Description: >-
      Database Cluster Engine. The name of the database engine to be used for
      this DB cluster.
    AllowedValues:
      - aurora-mysql
  EngineVersion:
    Type: String
    Default: 8.0.mysql_aurora.3.08.2
    Description: >-
      Database Engine Version. If you have an existing cluster and it is
      running Aurora MySQL 1.23 or higher, any upgrade to Aurora MySQL version
      2.x must be to Aurora MySQL 2.09 or higher. Ensure that the selected
      version is available in your region and partition. Some older versions
      may be deprecated or unavailable.
    AllowedValues:
      - 8.0.mysql_aurora.3.06.0
      - 8.0.mysql_aurora.3.06.1
      - 8.0.mysql_aurora.3.07.0
      - 8.0.mysql_aurora.3.07.1
      - 8.0.mysql_aurora.3.08.0
      - 8.0.mysql_aurora.3.08.1
      - 8.0.mysql_aurora.3.08.2
      - 8.0.mysql_aurora.3.09.0
      - 8.0.mysql_aurora.3.10.0
  DeletionProtection:
    Type: String
    Default: 'true'
    Description: >-
      Enable Deletion Protection on your Database Cluster.
    AllowedValues:
      - 'false'
      - 'true'
  RDSEncryptionType:
    Type: String
    Default: Default KMS Key
    Description: >-
      Select RDS encryption type from the provided list. If you select 'Existing KMS
      Key' you will need to provide the ARN for the 'KMSKeyArn' parameter.
    AllowedValues:
      - Default KMS Key
      - Existing KMS Key
      - New KMS Key
  KMSKeyArn:
    Type: String
    Description: >-
      (Optional) Enter the ARN for the KMS Key if you have selected 'Existing
      KMS Key' for the RDS Encryption Type parameter.
  DatabaseSnapshotId:
    Type: String
    Description: >-
      (Optional) Snapshot to create database from. Only use this if you already
      had Kion installed and are performing a migration or a recovery.
      Once this value is set, it cannot be changed or data loss may occur.
  BacktrackWindowParameter:
    Type: Number
    Default: 0
    Description: >-
      (Optional) Target backtrack window in seconds. To disable backtrack, set
      this value to 0. Max value is 259,200 (72 hours). If you want to enable
      backtrack, you must set this on the initial deploy of the template. Not
      available in GovCloud, HighSide, or unsupported regions.
      See AWS documentation for more the list of supported regions.
    MinValue: 0
    MaxValue: 259200
  PerformanceInsights:
    Type: String
    Default: 'false'
    AllowedValues:
      - 'false'
      - 'true'
    Description: >-
      (Optional) Enable performance insights on the database instances. This provides
      performance data on your RDS instances, including which statements are contributing
      to load. This setting incurs an additional cost, see https://aws.amazon.com/rds/performance-insights/
      for more information. Performance Insights is not available on t2 or t3 instances,
      and is only available in AWS Commercial regions.
  AutomaticMinorVersionUpgrade:
    Type: String
    Default: 'false'
    AllowedValues:
      - 'true'
      - 'false'
    Description: >-
      If true, the database will automatically apply minor version upgrades during the maintenance window.
  ExportAuditLogs:
    Type: String
    Default: 'false'
    AllowedValues:
      - 'true'
      - 'false'
    Description: >-
      If true, the database will export audit logs to CloudWatch Logs.
  ExportErrorLogs:
    Type: String
    Default: 'false'
    AllowedValues:
      - 'true'
      - 'false'
    Description: >-
      If true, the database will export error logs to CloudWatch Logs.
  ExportSlowQueryLogs:
    Type: String
    Default: 'false'
    AllowedValues:
      - 'true'
      - 'false'
    Description: >-
      If true, the database will export slow query logs to CloudWatch Logs.
  ExportGeneralLogs:
    Type: String
    Default: 'false'
    AllowedValues:
      - 'true'
      - 'false'
    Description: >-
      If true, the database will export general logs to CloudWatch Logs.

Conditions:
  UseDatabaseSnapshotId: !Not
    - !Equals
      - !Ref DatabaseSnapshotId
      - ''
  UseTwoNode: !Or
    - !Equals
      - !Ref NodeCount
      - 2
    - !Equals
      - !Ref NodeCount
      - 3
  UseThreeNode: !Equals
    - !Ref NodeCount
    - 3
  CreateNewKMSKey: !Equals
    - !Ref RDSEncryptionType
    - New KMS Key
  ExistingKMSKeyArn: !Not
    - !Equals
      - !Ref KMSKeyArn
      - ''
  UseExistingKMSKey: !Equals
    - !Ref RDSEncryptionType
    - Existing KMS Key
  GovCloud: !Equals
    - !Ref 'AWS::Partition'
    - aws-us-gov
  HighSide: !Or
    - !Equals
      - !Ref 'AWS::Partition'
      - aws-iso
    - !Equals
      - !Ref 'AWS::Partition'
      - aws-iso-b
  EnablePerformanceInsights: !And
    - !Not
      - !Equals
        - !Ref 'AWS::Partition'
        - aws-us-gov
    - !Equals
      - !Ref PerformanceInsights
      - 'true'
  IsUnsupportedBacktrackRegion1: !Or
    - !Equals [ !Ref "AWS::Region", "af-south-1" ]       # Africa (Cape Town)
    - !Equals [ !Ref "AWS::Region", "ap-east-1" ]        # Asia Pacific (Hong Kong)
    - !Equals [ !Ref "AWS::Region", "ap-southeast-3" ]   # Asia Pacific (Jakarta)
    - !Equals [ !Ref "AWS::Region", "ap-southeast-4" ]   # Asia Pacific (Melbourne)
    - !Equals [ !Ref "AWS::Region", "ap-south-2" ]       # Asia Pacific (Hyderabad)
    - !Equals [ !Ref "AWS::Region", "ap-southeast-5" ]   # Asia Pacific (Malaysia)
    - !Equals [ !Ref "AWS::Region", "ca-west-1" ]        # Canada West (Calgary)
    - !Equals [ !Ref "AWS::Region", "cn-north-1" ]       # China (Beijing)
    - !Equals [ !Ref "AWS::Region", "cn-northwest-1" ]   # China (Ningxia)
    - !Equals [ !Ref "AWS::Region", "eu-south-1" ]       # Europe (Milan)
  IsUnsupportedBacktrackRegion2: !Or
    - !Equals [ !Ref "AWS::Region", "eu-south-2" ]       # Europe (Spain)
    - !Equals [ !Ref "AWS::Region", "eu-north-1" ]       # Europe (Stockholm)
    - !Equals [ !Ref "AWS::Region", "eu-central-2" ]     # Europe (Zurich)
    - !Equals [ !Ref "AWS::Region", "eu-central-3" ]     # Europe (Germany - Berlin)
    - !Equals [ !Ref "AWS::Region", "il-central-1" ]     # Israel (Tel Aviv)
    - !Equals [ !Ref "AWS::Region", "me-south-1" ]       # Middle East (Bahrain)
    - !Equals [ !Ref "AWS::Region", "me-central-1" ]     # Middle East (UAE)
    - !Equals [ !Ref "AWS::Region", "sa-east-1" ]        # South America (São Paulo)
  IsUnsupportedBacktrackRegion: !Or
    - !Condition IsUnsupportedBacktrackRegion1
    - !Condition IsUnsupportedBacktrackRegion2
  BacktrackCondition: !And
    - !Not
      - !Condition GovCloud
    - !Not
      - !Condition HighSide
    - !Not
      - !Condition IsUnsupportedBacktrackRegion
  EnableCloudWatchLogging: !Or
    - !Equals
      - !Ref ExportAuditLogs
      - 'true'
    - !Equals
      - !Ref ExportErrorLogs
      - 'true'
    - !Equals
      - !Ref ExportSlowQueryLogs
      - 'true'
    - !Equals
      - !Ref ExportGeneralLogs
      - 'true'
  EnableAuditLogExport: !Equals [!Ref ExportAuditLogs, 'true']
  EnableErrorLogExport: !Equals [!Ref ExportErrorLogs, 'true']
  EnableSlowQueryLogExport: !Equals [!Ref ExportSlowQueryLogs, 'true']
  EnableGeneralLogExport: !Equals [!Ref ExportGeneralLogs, 'true']

Outputs:
  Endpoint:
    Description: Database endpoint.
    Value: !GetAtt
      - DatabaseCluster
      - Endpoint.Address
  SecurityGroup:
    Description: Database security group.
    Value: !GetAtt
      - InternalSG
      - GroupId

Resources:

  KMSKey:
    Type: 'AWS::KMS::Key'
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Condition: CreateNewKMSKey
    Properties:
      Description: KMS key used for RDS generated by Kion
      Enabled: true
      EnableKeyRotation: true
      KeyUsage: ENCRYPT_DECRYPT
      KeyPolicy:
        Version: 2012-10-17
        Id: kion-rds-key
        Statement:
          - Sid: Enable IAM User permissions
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:${AWS::Partition}:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: >-
                Allow access through RDS for all principals in the account that
                are authorized to use RDS
            Effect: Allow
            Principal:
              Service: rds.amazonaws.com
            Action:
              - 'kms:Encrypt'
              - 'kms:Decrypt'
              - 'kms:ReEncrypt*'
              - 'kms:GenerateDataKey*'
              - 'kms:CreateGrant'
              - 'kms:ListGrants'
              - 'kms:DescribeKey'
            Resource: '*'
            Condition:
              StringEquals:
                'kms:CallerAccount': !Ref 'AWS::AccountId'
                'kms:ViaService': !Sub 'rds.${AWS::Region}.${AWS::URLSuffix}'

  KMSKeyAlias:
    Type: 'AWS::KMS::Alias'
    UpdateReplacePolicy: Retain
    DeletionPolicy: Retain
    Condition: CreateNewKMSKey
    Properties:
      AliasName: !Sub 'alias/${Namespace}/Rds'
      TargetKeyId: !Ref KMSKey

  DatabaseSubnetGroup:
    Type: 'AWS::RDS::DBSubnetGroup'
    Properties:
      DBSubnetGroupDescription: CloudFormation managed DB subnet group.
      SubnetIds: !Ref PrivateSubnets

  DatabaseCluster:
    Metadata:
      cfn-lint:
        config:
          ignore_checks:
            - W1011 # Use dynamic references over parameters for secrets
    Type: 'AWS::RDS::DBCluster'
    Properties:
      AutoMinorVersionUpgrade: !Ref AutomaticMinorVersionUpgrade
      Engine: !Ref Engine
      EngineVersion: !Ref EngineVersion
      MasterUsername: !If
        - UseDatabaseSnapshotId
        - !Ref 'AWS::NoValue'
        - !Ref DatabaseUsername
      MasterUserPassword: !If
        - UseDatabaseSnapshotId
        - !Ref 'AWS::NoValue'
        - !Ref DatabasePassword
      StorageEncrypted: true
      DeletionProtection: !Ref DeletionProtection
      KmsKeyId: !If
        - CreateNewKMSKey
        - !Ref KMSKey
        - !If
          - UseExistingKMSKey
          - !If
            - ExistingKMSKeyArn
            - !Ref KMSKeyArn
            - !Ref 'AWS::NoValue'
          - !Ref 'AWS::NoValue'
      BacktrackWindow: !If
        - BacktrackCondition
        - !Ref BacktrackWindowParameter
        - !Ref 'AWS::NoValue'
      BackupRetentionPeriod: 30
      PreferredBackupWindow: '01:00-02:00'
      PreferredMaintenanceWindow: 'mon:03:00-mon:04:00'
      DBSubnetGroupName: !Ref DatabaseSubnetGroup
      VpcSecurityGroupIds:
        - !Ref InternalSG
      SnapshotIdentifier: !If
        - UseDatabaseSnapshotId
        - !Ref DatabaseSnapshotId
        - !Ref 'AWS::NoValue'
      EnableCloudwatchLogsExports: !If
        - EnableCloudWatchLogging
        - - !If [EnableAuditLogExport, 'audit', !Ref 'AWS::NoValue']
          - !If [EnableErrorLogExport, 'error', !Ref 'AWS::NoValue']
          - !If [EnableSlowQueryLogExport, 'slowquery', !Ref 'AWS::NoValue']
          - !If [EnableGeneralLogExport, 'general', !Ref 'AWS::NoValue']
        - !Ref 'AWS::NoValue'

  DatabasePrimaryInstance:
    Type: 'AWS::RDS::DBInstance'
    Properties:
      AutoMinorVersionUpgrade: !Ref AutomaticMinorVersionUpgrade
      Engine: !Ref Engine
      EngineVersion: !Ref EngineVersion
      DBClusterIdentifier: !Ref DatabaseCluster
      DBInstanceClass: !Ref InstanceType
      DBSubnetGroupName: !Ref DatabaseSubnetGroup
      EnablePerformanceInsights: !If [EnablePerformanceInsights, 'true', 'false']
      AvailabilityZone: !Select
        - '0'
        - !Ref AvailabilityZones

  DatabaseSecondInstance:
    Type: 'AWS::RDS::DBInstance'
    Condition: UseTwoNode
    Properties:
      AutoMinorVersionUpgrade: !Ref AutomaticMinorVersionUpgrade
      Engine: !Ref Engine
      EngineVersion: !Ref EngineVersion
      DBClusterIdentifier: !Ref DatabaseCluster
      DBInstanceClass: !Ref InstanceType
      DBSubnetGroupName: !Ref DatabaseSubnetGroup
      EnablePerformanceInsights: !If [EnablePerformanceInsights, 'true', 'false']
      AvailabilityZone: !Select
        - '1'
        - !Ref AvailabilityZones

  DatabaseThirdInstance:
    Type: 'AWS::RDS::DBInstance'
    Condition: UseThreeNode
    Properties:
      AutoMinorVersionUpgrade: !Ref AutomaticMinorVersionUpgrade
      Engine: !Ref Engine
      EngineVersion: !Ref EngineVersion
      DBClusterIdentifier: !Ref DatabaseCluster
      DBInstanceClass: !Ref InstanceType
      DBSubnetGroupName: !Ref DatabaseSubnetGroup
      EnablePerformanceInsights: !If [EnablePerformanceInsights, 'true', 'false']
      AvailabilityZone: !Select
        - '2'
        - !Ref AvailabilityZones

  InternalSG:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      VpcId: !Ref VPCId
      GroupDescription: Allow internal traffic.
      SecurityGroupIngress: []
      Tags:
        - Key: Name
          Value: !Sub '${Namespace}-rds'

  InternalSGIngress:
    Type: 'AWS::EC2::SecurityGroupIngress'
    Properties:
      GroupId: !Ref InternalSG
      IpProtocol: '-1'
      SourceSecurityGroupId: !Ref InternalSG
