---
AWSTemplateFormatVersion: "2010-09-09"
Description: This stack deploys the baseline infrastructure to be used for AmazonDocumentDB

Parameters:

  DocDBIdentifier:
    Type: String
    Description: The cluster identifier. This parameter is stored as a lowercase string. (the first character must be a letter. Cannot end with a hyphen or contain two consecutive hyphens.)

  DocDBUsername:
    Type: String
    Description: Username for the Amazon DocumentDB cluster (the first character must be a letter. Cannot be a reserved word for the chosen database engine.)
    MinLength: 1
    MaxLength: 63

  DocDBPassword:
    Type: String
    Description: Password for the Amazon DocumentDB cluster (this password can contain any printable ASCII character except forward slash (/), double quote ("), or the "at" symbol (@).)
    NoEcho: true
    MinLength: 8
    MaxLength: 100

  ExistingCloud9Role:
    Type: String
    Default: NO
    Description: Indicate whether AWSCloud9SSMAccessRole is already created or not (you can verify this in IAM > Roles).
    AllowedValues:
      - YES
      - NO

Conditions:
  CreateCloud9Role: !Equals [!Ref ExistingCloud9Role, NO]
  CreateCloud9InstanceProfile: !Equals [!Ref ExistingCloud9Role, NO]
  CreateCloud9Policy: !Equals [!Ref ExistingCloud9Role, YES]

Mappings:

  SubnetConfig:
    VPC:
      CIDR: '10.0.0.0/16'
    PublicOne:
      CIDR: '10.0.0.0/24'
    PublicTwo:
      CIDR: '10.0.1.0/24'
    PublicThree:
      CIDR: '10.0.2.0/24'
    PrivateOne:
      CIDR: '10.0.100.0/24'
    PrivateTwo:
      CIDR: '10.0.101.0/24'
    PrivateThree:
      CIDR: '10.0.102.0/24'

Resources:

  VPC:
    Type: AWS::EC2::VPC
    Properties:
      EnableDnsSupport: true
      EnableDnsHostnames: true
      CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR']
      Tags:
        - Key: Name
          Value: !Sub VPC-${AWS::StackName}

  PublicSubnetOne:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
         Fn::Select:
         - 0
         - Fn::GetAZs: {Ref: 'AWS::Region'}
      VpcId: !Ref VPC
      CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub PublicOne-${AWS::StackName}

  PublicSubnetTwo:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
         Fn::Select:
         - 1
         - Fn::GetAZs: {Ref: 'AWS::Region'}
      VpcId: !Ref VPC
      CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR']
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub PublicTwo-${AWS::StackName}

  PublicSubnetThree:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
         Fn::Select:
         - 2
         - Fn::GetAZs: {Ref: 'AWS::Region'}
      VpcId: !Ref VPC
      CidrBlock: !FindInMap ['SubnetConfig', 'PublicThree', 'CIDR']
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub PublicThree-${AWS::StackName}

  PrivateSubnetOne:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
         Fn::Select:
         - 0
         - Fn::GetAZs: {Ref: 'AWS::Region'}
      VpcId: !Ref VPC
      CidrBlock: !FindInMap ['SubnetConfig', 'PrivateOne', 'CIDR']
      Tags:
        - Key: Name
          Value: !Sub PrivateOne-${AWS::StackName}

  PrivateSubnetTwo:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
         Fn::Select:
         - 1
         - Fn::GetAZs: {Ref: 'AWS::Region'}
      VpcId: !Ref VPC
      CidrBlock: !FindInMap ['SubnetConfig', 'PrivateTwo', 'CIDR']
      Tags:
        - Key: Name
          Value: !Sub PrivateTwo-${AWS::StackName}

  PrivateSubnetThree:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone:
         Fn::Select:
         - 2
         - Fn::GetAZs: {Ref: 'AWS::Region'}
      VpcId: !Ref VPC
      CidrBlock: !FindInMap ['SubnetConfig', 'PrivateThree', 'CIDR']
      Tags:
        - Key: Name
          Value: !Sub PrivateThree-${AWS::StackName}

  InternetGateway:
    Type: AWS::EC2::InternetGateway
  GatewayAttachement:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: GatewayAttachement
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PublicSubnetOneRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetOne
      RouteTableId: !Ref PublicRouteTable
  PublicSubnetTwoRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetTwo
      RouteTableId: !Ref PublicRouteTable
  PublicSubnetThreeRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetThree
      RouteTableId: !Ref PublicRouteTable

  NatGatewayOneAttachment:
    Type: AWS::EC2::EIP
    DependsOn: GatewayAttachement
    Properties:
        Domain: vpc
  NatGatewayTwoAttachment:
    Type: AWS::EC2::EIP
    DependsOn: GatewayAttachement
    Properties:
        Domain: vpc
  NatGatewayThreeAttachment:
    Type: AWS::EC2::EIP
    DependsOn: GatewayAttachement
    Properties:
        Domain: vpc
  NatGatewayOne:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayOneAttachment.AllocationId
      SubnetId: !Ref PublicSubnetOne
  NatGatewayTwo:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayTwoAttachment.AllocationId
      SubnetId: !Ref PublicSubnetTwo
  NatGatewayThree:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayThreeAttachment.AllocationId
      SubnetId: !Ref PublicSubnetThree
  PrivateRouteTableOne:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  PrivateRouteOne:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTableOne
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGatewayOne
  PrivateRouteTableOneAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTableOne
      SubnetId: !Ref PrivateSubnetOne
  PrivateRouteTableTwo:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  PrivateRouteTwo:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTableTwo
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGatewayTwo
  PrivateRouteTableTwoAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTableTwo
      SubnetId: !Ref PrivateSubnetTwo
  PrivateRouteTableThree:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
  PrivateRouteThree:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTableThree
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGatewayThree
  PrivateRouteTableThreeAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTableThree
      SubnetId: !Ref PrivateSubnetThree

  SNSTopicTrigger:
    Type: AWS::SNS::Topic

  SNSTopicAlert:
    Type: AWS::SNS::Topic

  ScheduledRule:
    Type: AWS::Events::Rule
    Properties:
      Description: "Rule to trigger change stream Lambda functions"
      ScheduleExpression: "rate(2 minutes)"
      State: "DISABLED"
      Targets:
        -
          Arn:
            Ref: "SNSTopicTrigger"
          Id: "TargetSNS"
    DependsOn: SNSTopicAlert

  EventTopicPolicy:
    Type: 'AWS::SNS::TopicPolicy'
    Properties:
      PolicyDocument:
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: 'sns:Publish'
            Resource: '*'
      Topics:
        - !Ref SNSTopicTrigger

  DocDBSecret:
    Type: 'AWS::SecretsManager::Secret'
    Properties:
      Name: !Sub "DocDBSecret-${AWS::StackName}"
      Description: This secret has the credentials for the documentDB cluster
      SecretString:
        !Join
          - ''
          - - '{"username":"'
            - !Ref DocDBUsername
            - '","password":"'
            - !Ref DocDBPassword
            - '", "ssl": true}'

  SecretDocDBClusterAttachment:
    Type: AWS::SecretsManager::SecretTargetAttachment
    Properties:
      SecretId: !Ref DocDBSecret
      TargetId: !Ref DocumentDBCluster
      TargetType: AWS::DocDB::DBCluster

  LambdaChangeStreamsRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
      Policies:
        - PolicyName: PublishSNS
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - sns:Publish
                Resource: !Ref SNSTopicAlert
        - PolicyName: SecretsManager
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - secretsmanager:GetSecretValue
                Resource: !Ref DocDBSecret
        - PolicyName: S3Access
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                Resource: 'arn:aws:s3:::rds-downloads/rds-combined-ca-bundle.pem'
      Path:
        Fn::Sub: /

  AWSCloud9SSMAccessRole:
    Type: AWS::IAM::Role
    Condition: CreateCloud9Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
                - cloud9.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSCloud9SSMInstanceProfile
      RoleName: AWSCloud9SSMAccessRole
      Path: /service-role/

  AWSCloud9RolePolicyNew:
    Type: AWS::IAM::ManagedPolicy
    Condition: CreateCloud9Role
    DependsOn: AWSCloud9SSMAccessRole
    Properties:
      Description: Policy for Cloud9 Environments used in Change Stream
      Path: /service-role/
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - s3:PutObject
              - s3:GetObject
            Resource:
              !Join
                - ''
                - - !GetAtt S3BucketFunctionCode.Arn
                  - '/*'
          - Effect: Allow
            Action: 'events:EnableRule'
            Resource: !GetAtt ScheduledRule.Arn
          - Effect: Allow
            Action:
              - ec2:DescribeSubnets
              - ec2:DescribeVpcs
              - ec2:DescribeNetworkInterfaces
              - ec2:DescribeSecurityGroups
              - lambda:GetFunctionConfiguration
              - lambda:GetFunction
              - cloudformation:DescribeChangeSet
              - cloudformation:DescribeStacks
              - cloudformation:ListChangeSets
            Resource: '*'
          - Effect: Allow
            Action:
              - cloudformation:CreateStack
              - cloudformation:CreateChangeSet
              - cloudformation:ExecuteChangeSet
            Resource:
              !Join
                - ''
                - - 'arn:aws:cloudformation:'
                  - !Ref "AWS::Region"
                  - ':'
                  - !Ref "AWS::AccountId"
                  - ':stack/change-streams-function/*'
          - Effect: Allow
            Action:
              - lambda:CreateFunction
              - lambda:AddPermission
              - lambda:DeleteFunction
              - lambda:RemovePermission
            Resource:
              !Join
                - ''
                - - 'arn:aws:lambda:'
                  - !Ref "AWS::Region"
                  - ':'
                  - !Ref "AWS::AccountId"
                  - ':function:docdb-elasticsearch-replicator'
          - Effect: Allow
            Action: sns:Subscribe
            Resource: !Ref SNSTopicTrigger
          - Effect: Allow
            Action: iam:PassRole
            Resource: !GetAtt LambdaChangeStreamsRole.Arn
      Roles:
        - AWSCloud9SSMAccessRole

  AWSCloud9RolePolicyOld:
    Type: AWS::IAM::ManagedPolicy
    Condition: CreateCloud9Policy
    Properties:
      Description: Policy for Cloud9 Environments used in Change Stream
      Path: /service-role/
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - s3:PutObject
              - s3:GetObject
            Resource:
              !Join
                - ''
                - - !GetAtt S3BucketFunctionCode.Arn
                  - '/*'
          - Effect: Allow
            Action: 'events:EnableRule'
            Resource: !GetAtt ScheduledRule.Arn
          - Effect: Allow
            Action:
              - ec2:DescribeSubnets
              - ec2:DescribeVpcs
              - ec2:DescribeNetworkInterfaces
              - ec2:DescribeSecurityGroups
              - lambda:GetFunctionConfiguration
              - lambda:GetFunction
              - cloudformation:DescribeChangeSet
              - cloudformation:DescribeStacks
              - cloudformation:ListChangeSets
            Resource: '*'
          - Effect: Allow
            Action:
              - cloudformation:CreateStack
              - cloudformation:CreateChangeSet
              - cloudformation:ExecuteChangeSet
            Resource:
              !Join
                - ''
                - - 'arn:aws:cloudformation:'
                  - !Ref "AWS::Region"
                  - ':'
                  - !Ref "AWS::AccountId"
                  - ':stack/change-streams-function/*'
          - Effect: Allow
            Action:
              - lambda:CreateFunction
              - lambda:AddPermission
              - lambda:DeleteFunction
              - lambda:RemovePermission
            Resource:
              !Join
                - ''
                - - 'arn:aws:lambda:'
                  - !Ref "AWS::Region"
                  - ':'
                  - !Ref "AWS::AccountId"
                  - ':function:docdb-elasticsearch-replicator'
          - Effect: Allow
            Action: sns:Subscribe
            Resource: !Ref SNSTopicTrigger
          - Effect: Allow
            Action: iam:PassRole
            Resource: !GetAtt LambdaChangeStreamsRole.Arn
      Roles:
        - AWSCloud9SSMAccessRole

  AWSCloud9SSMInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Condition: CreateCloud9InstanceProfile
    DependsOn: AWSCloud9SSMAccessRole
    Properties:
      InstanceProfileName: AWSCloud9SSMInstanceProfile
      Path: /cloud9/
      Roles:
        - AWSCloud9SSMAccessRole

  DocumentDBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Amazon DocumentDB Security Group
      VpcId: !Ref VPC
      SecurityGroupIngress:
         - IpProtocol: tcp
           CidrIp: 10.0.0.0/16
           FromPort: 27017
           ToPort: 27017

  DocumentDBSubnetGroup:
    Type: AWS::DocDB::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Subnet Group for DocumentDB
      SubnetIds:
         - !Ref PrivateSubnetOne
         - !Ref PrivateSubnetTwo
         - !Ref PrivateSubnetThree

  DocumentDBCluster:
    Type: AWS::DocDB::DBCluster
    Properties:
      DBClusterIdentifier: !Ref DocDBIdentifier
      MasterUsername: !Ref DocDBUsername
      MasterUserPassword: !Ref DocDBPassword
      DBSubnetGroupName : !Ref DocumentDBSubnetGroup
      StorageEncrypted: yes
      Tags:
        - Key: Name
          Value: !Sub DocumentDB-${AWS::StackName}
      VpcSecurityGroupIds:
        - !Ref DocumentDBSecurityGroup
    DependsOn: VPC

  DocumentDBInstanceWriter:
    Type: AWS::DocDB::DBInstance
    Properties:
      DBClusterIdentifier: !Ref DocumentDBCluster
      DBInstanceClass: db.r5.large
      Tags:
        - Key: Name
          Value: !Sub DocumentDBInstance-${AWS::StackName}

  Cloud9envNew:
    Type: AWS::Cloud9::EnvironmentEC2
    Condition: CreateCloud9InstanceProfile
    DependsOn: AWSCloud9SSMInstanceProfile
    Properties:
      ConnectionType: CONNECT_SSM
      Description: Cloud9 instance to access Cloud9
      InstanceType: m5.large
      Name: ChangeStreamsCloud9
      SubnetId: !Ref PrivateSubnetOne

  Cloud9envOld:
    Type: AWS::Cloud9::EnvironmentEC2
    Condition: CreateCloud9Policy
    Properties:
      ConnectionType: CONNECT_SSM
      Description: Cloud9 instance to access Cloud9
      InstanceType: m5.large
      Name: ChangeStreamsCloud9
      SubnetId: !Ref PrivateSubnetOne

  ElasticSearchSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Amazon DocumentDB Security Group
      VpcId: !Ref VPC
      SecurityGroupIngress:
         - IpProtocol: tcp
           CidrIp: 10.0.0.0/16
           FromPort: 443
           ToPort: 443

  ElasticsearchSLR:
    Type: 'AWS::IAM::ServiceLinkedRole'
    Properties:
      AWSServiceName: es.amazonaws.com

  ElasticsearchDomain:
    Type: AWS::Elasticsearch::Domain
    DependsOn: ElasticsearchSLR
    Properties:
      ElasticsearchVersion: "7.4"
      ElasticsearchClusterConfig:
        InstanceCount: "1"
        InstanceType: "r5.large.elasticsearch"
      EBSOptions:
        EBSEnabled: true
        Iops: 0
        VolumeSize: 20
        VolumeType: "gp2"
      AccessPolicies:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: '*'
            Action: 'es:*'
            Resource:
              !Join
                - ''
                - - 'arn:aws:es:'
                  - !Ref "AWS::Region"
                  - ':'
                  - !Ref "AWS::AccountId"
                  - ':domain/*/*'
      VPCOptions:
        SubnetIds:
          - Ref: PrivateSubnetOne
        SecurityGroupIds:
          - Ref: ElasticSearchSecurityGroup

  S3BucketFunctionCode:
    Type: AWS::S3::Bucket
    DeletionPolicy: Retain

Outputs:
  VpcId:
    Value: !Ref VPC
  PrivateSubnetOne:
    Value: !Ref PrivateSubnetOne
  PrivateSubnetTwo:
    Value: !Ref PrivateSubnetTwo
  PrivateSubnetThree:
    Value: !Ref PrivateSubnetThree
  StackName:
    Value: !Sub ${AWS::StackName}
  DocumentDBIdentifier:
    Value: !Ref DocumentDBCluster
  DocumentDBEndpoint:
    Value: !GetAtt DocumentDBCluster.Endpoint
  DocumentDBSecurityGroup:
    Value: !Ref DocumentDBSecurityGroup
  SNSTopicTrigger:
    Value: !Ref SNSTopicTrigger
  SNSTopicAlert:
    Value: !Ref SNSTopicAlert
  DocDBSecretArn:
    Value: !Ref DocDBSecret
  DocDBSecretName:
    Value: !Sub "DocDBSecret-${AWS::StackName}"
  ElasticsearchDomainArn:
    Value: !GetAtt ElasticsearchDomain.Arn
  ElasticsearchDomainEndpoint:
    Value: !GetAtt ElasticsearchDomain.DomainEndpoint
  ElasticsearchSecurityGroup:
    Value: !Ref ElasticSearchSecurityGroup
  RoleArn:
    Value: !GetAtt LambdaChangeStreamsRole.Arn
  S3BucketName:
    Value: !Ref S3BucketFunctionCode
  EventBridgeRule:
    Value: !Ref ScheduledRule
