--- 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