AWSTemplateFormatVersion: "2010-09-09" Description: Multi-tenant Amazon Aurora MySQL with RDS Proxy and an optional load test (uksb-1rkqqff0c). Metadata: Version: 0.0.9 AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Region configuration Parameters: - AvailabilityZones - Label: default: Main configuration Parameters: - DBReaderInstanceClass - DBWriterInstanceClass - PerformanceInsightsRetentionPeriod - LambdaRuntimeEnv - Environment - EnableFlowLogs - Label: default: Load test configuration Parameters: - CreateLoadTest - UsersToCreate - LocustAmiId - LocustInstanceType - LocustVersion - LocustSecondaryInstanceCapacity - ApiEndpointType - CIDRRange ParameterLabels: AvailabilityZones: default: Availability Zones DBReaderInstanceClass: default: Database Reader Instance Class DBWriterInstanceClass: default: Database Writer Instance Class PerformanceInsightsRetentionPeriod: default: Performance Insights Retention Period LambdaRuntimeEnv: default: Lambda Runtime Environment Environment: default: Infrastructure Environment CreateLoadTest: default: Create Load Test Stack UsersToCreate: default: Number of Tenants to Create LocustAmiId: default: Latest Amazon Linux AMI LocustInstanceType: default: Locust Instance Type LocustVersion: default: Locust App Version LocustSecondaryInstanceCapacity: default: Locust Worker Instances ApiEndpointType: default: API Endpoint Type CIDRRange: default: ISP/Public IPv4 EnableFlowLogs: default: Flow Logs Parameters: CreateLoadTest: Description: If False, this creates a Proxy VPC and accompanying reosurces. If True, this additionally creates a Load Test VPC, No Proxy VPC, and accompanying reosurces, in order to run a load test and compare metrics between the Proxy and No Proxy VPCs. Type: String AllowedValues: - false - true Default: true ConstraintDescription: Must specify 'true' or 'false' UsersToCreate: Description: The number of tenants to create in the Proxy and No Proxy VPC Aurora clusters. Each tenant has their own dedicated database containing dummy data. Allowed values are 1-200. Type: String Default: 200 AllowedPattern: ^(20[0]|1[0-9][0-9]|[1-9]?[1-9])$ ConstraintDescription: This parameter must be in the range 1-200 AvailabilityZones: Description: The list of Availability Zones to use for the subnets in the VPCs. Select two Availability Zones from the list. Type: List DBReaderInstanceClass: Description: The database instance class for the Proxy and No Proxy VPC Amazon Aurora Replicas, for example db.m5.large. Type: String Default: db.r5.large MinLength: 6 DBWriterInstanceClass: Description: The database instance class for the Proxy and No Proxy VPC Amazon Aurora Writer, for example db.m5.large. Type: String Default: db.t3.medium MinLength: 6 PerformanceInsightsRetentionPeriod: Description: The amount of time, in days, to retain RDS Performance Insights data. Valid values are 7 and 731 (2 years). Type: String AllowedValues: - 7 - 731 Default: 7 LambdaRuntimeEnv: Description: The runtime for the Lambda access function. Valid values are (Python, Nodejs). Type: String AllowedValues: - Python - Nodejs Default: Nodejs LocustAmiId: Description: The latest Amazon Linux AMI from AWS Systems Manager Parameter Store. Type: AWS::SSM::Parameter::Value Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 LocustInstanceType: Description: The Amazon EC2 instance type used in the Load Test VPC that runs Locust. Type: String Default: c5.large MinLength: 6 LocustVersion: Description: The Locust version to deploy. Type: String Default: latest MinLength: 1 LocustSecondaryInstanceCapacity: Description: The number of secondary Amazon EC2s for the Load Test Cluster. Allowed values are 2-20. Type: String Default: 2 AllowedPattern: ^(20|[2-9]|1[0-9])$ ConstraintDescription: This parameter must be in the range 2-20 MinLength: 1 ApiEndpointType: Description: The Amazon API Gateway endpoint type. Valid values are (EDGE, REGIONAL, PRIVATE). Type: String AllowedValues: - EDGE - REGIONAL - PRIVATE Default: PRIVATE CIDRRange: Description: The CIDR block or your IP address that you will use to connect to the Locust Dashboard (e.g. 192.168.192.168/32). This limits the CIDR range from which the Locust dashboard can be accessed. You can use an open CIDR range (e.g. 0.0.0.0/0) to access from anywhere, but this is not recommended. Type: String AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$ ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/0-32 MinLength: 1 Environment: Description: The type of environment with which to tag your infrastructure. Valid values are DEV (development), TEST (test), or PROD (production). Type: String AllowedValues: - DEV - TEST - PROD Default: DEV EnableFlowLogs: Description: Creates an optional CloudWatch Logs group to send the VPC flow logs to. Flow Logs incur additional costs. Set to "false" to disable. Type: String AllowedValues: - true - false Default: false Mappings: StackMap: ProxyMap: VPCCidr: 10.0.0.0/16 PublicSubnet1Cidr: 10.0.0.0/24 PublicSubnet2Cidr: 10.0.1.0/24 PrivateSubnet1Cidr: 10.0.2.0/24 PrivateSubnet2Cidr: 10.0.3.0/24 ResourceTags: rds-proxy CreateRDSProxy: true NoProxyMap: VPCCidr: 10.1.0.0/16 PublicSubnet1Cidr: 10.1.0.0/24 PublicSubnet2Cidr: 10.1.1.0/24 PrivateSubnet1Cidr: 10.1.2.0/24 PrivateSubnet2Cidr: 10.1.3.0/24 ResourceTags: rds-no-proxy CreateRDSProxy: false LoadTestMap: VPCCidr: 10.2.0.0/16 PublicSubnet1Cidr: 10.2.0.0/24 PublicSubnet2Cidr: 10.2.1.0/24 PrivateSubnet1Cidr: 10.2.2.0/24 PrivateSubnet2Cidr: 10.2.3.0/24 ResourceTags: rds-load-test Conditions: IsLoadTest: !Equals [!Ref CreateLoadTest, true] IsPython: !Equals [!Ref LambdaRuntimeEnv, Python] Resources: ProxyVPCStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: vpc.template Parameters: AvailabilityZones: !Join [',', !Ref AvailabilityZones] VPCCidr: !FindInMap [StackMap, ProxyMap, VPCCidr] PublicSubnet1Cidr: !FindInMap [StackMap, ProxyMap, PublicSubnet1Cidr] PublicSubnet2Cidr: !FindInMap [StackMap, ProxyMap, PublicSubnet2Cidr] PrivateSubnet1Cidr: !FindInMap [StackMap, ProxyMap, PrivateSubnet1Cidr] PrivateSubnet2Cidr: !FindInMap [StackMap, ProxyMap, PrivateSubnet2Cidr] ResourceTags: !FindInMap [StackMap, ProxyMap, ResourceTags] EnableFlowLogs: !Ref EnableFlowLogs Tags: - Key: Project Value: !FindInMap [StackMap, ProxyMap, ResourceTags] - Key: Environment Value: !Ref Environment NoProxyVPCStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: vpc.template Parameters: AvailabilityZones: !Join - ',' - !Ref AvailabilityZones VPCCidr: !FindInMap [StackMap, NoProxyMap, VPCCidr] PublicSubnet1Cidr: !FindInMap [StackMap, NoProxyMap, PublicSubnet1Cidr] PublicSubnet2Cidr: !FindInMap [StackMap, NoProxyMap, PublicSubnet2Cidr] PrivateSubnet1Cidr: !FindInMap [StackMap, NoProxyMap, PrivateSubnet1Cidr] PrivateSubnet2Cidr: !FindInMap [StackMap, NoProxyMap, PrivateSubnet2Cidr] ResourceTags: !FindInMap [StackMap, NoProxyMap, ResourceTags] EnableFlowLogs: !Ref EnableFlowLogs Tags: - Key: Project Value: !FindInMap [StackMap, NoProxyMap, ResourceTags] - Key: Environment Value: !Ref Environment Condition: IsLoadTest LoadTestVPCStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: vpc.template Parameters: AvailabilityZones: !Join - ',' - !Ref AvailabilityZones VPCCidr: !FindInMap [StackMap, LoadTestMap, VPCCidr] PublicSubnet1Cidr: !FindInMap [StackMap, LoadTestMap, PublicSubnet1Cidr] PublicSubnet2Cidr: !FindInMap [StackMap, LoadTestMap, PublicSubnet2Cidr] PrivateSubnet1Cidr: !FindInMap [StackMap, LoadTestMap, PrivateSubnet1Cidr] PrivateSubnet2Cidr: !FindInMap [StackMap, LoadTestMap, PrivateSubnet2Cidr] ResourceTags: !FindInMap [StackMap, LoadTestMap, ResourceTags] EnableFlowLogs: !Ref EnableFlowLogs Tags: - Key: Project Value: !FindInMap [StackMap, LoadTestMap, ResourceTags] - Key: Environment Value: !Ref Environment Condition: IsLoadTest ProxyRDSStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: rds.template Parameters: AvailabilityZones: !Join - ',' - !Ref AvailabilityZones VpcID: !GetAtt ProxyVPCStack.Outputs.VpcID PrivateSubnet1: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet1 PrivateSubnet2: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet2 DBReaderInstanceClass: !Ref DBReaderInstanceClass DBWriterInstanceClass: !Ref DBWriterInstanceClass PerformanceInsightsRetentionPeriod: !Ref PerformanceInsightsRetentionPeriod CreateRDSProxy: !FindInMap [StackMap, ProxyMap, CreateRDSProxy] LayerVersionArn: !GetAtt ProxyLayerVersionStack.Outputs.PythonLambdaLayer Tags: - Key: Project Value: !FindInMap [StackMap, ProxyMap, ResourceTags] - Key: Environment Value: !Ref Environment NoProxyRDSStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: rds.template Parameters: AvailabilityZones: !Join - ',' - !Ref AvailabilityZones VpcID: !GetAtt NoProxyVPCStack.Outputs.VpcID PrivateSubnet1: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet1 PrivateSubnet2: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet2 DBReaderInstanceClass: !Ref DBReaderInstanceClass DBWriterInstanceClass: !Ref DBWriterInstanceClass PerformanceInsightsRetentionPeriod: !Ref PerformanceInsightsRetentionPeriod CreateRDSProxy: !FindInMap [StackMap, NoProxyMap, CreateRDSProxy] LayerVersionArn: !GetAtt ProxyLayerVersionStack.Outputs.PythonLambdaLayer Tags: - Key: Project Value: !FindInMap [StackMap, NoProxyMap, ResourceTags] - Key: Environment Value: !Ref Environment Condition: IsLoadTest ProxyLayerVersionStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: lambda-layer.template Parameters: LambdaRuntimeEnv: !Ref LambdaRuntimeEnv Tags: - Key: Project Value: !FindInMap [StackMap, ProxyMap, ResourceTags] - Key: Environment Value: !Ref Environment NoProxyLayerVersionStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: lambda-layer.template Parameters: LambdaRuntimeEnv: !Ref LambdaRuntimeEnv Tags: - Key: Project Value: !FindInMap [StackMap, NoProxyMap, ResourceTags] - Key: Environment Value: !Ref Environment Condition: IsLoadTest ProxyAccessStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: access-function.template Parameters: VpcID: !GetAtt ProxyVPCStack.Outputs.VpcID PrivateSubnet1: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet1 PrivateSubnet2: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet2 LambdaRuntimeEnv: !Ref LambdaRuntimeEnv LayerVersionArn: !If - IsPython - !GetAtt ProxyLayerVersionStack.Outputs.PythonLambdaLayer - !GetAtt ProxyLayerVersionStack.Outputs.NodeJsLambdaLayer Endpoint: !GetAtt ProxyRDSStack.Outputs.ProxyEndpoint RDSSecurityGroup: !GetAtt ProxyRDSStack.Outputs.RDSSecurityGroup ProxySecurityGroup: !GetAtt ProxyRDSStack.Outputs.ProxySecurityGroup CreateRDSProxy: !FindInMap [StackMap, ProxyMap, CreateRDSProxy] ClusterEndpointResourceId: !GetAtt ProxyRDSStack.Outputs.ClusterEndpointResourceId SSLCertURL: https://www.amazontrust.com/repository/AmazonRootCA1.pem Tags: - Key: Project Value: !FindInMap [StackMap, ProxyMap, ResourceTags] - Key: Environment Value: !Ref Environment NoProxyAccessStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: access-function.template Parameters: VpcID: !GetAtt NoProxyVPCStack.Outputs.VpcID PrivateSubnet1: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet1 PrivateSubnet2: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet2 LambdaRuntimeEnv: !Ref LambdaRuntimeEnv LayerVersionArn: !If - IsPython - !GetAtt NoProxyLayerVersionStack.Outputs.PythonLambdaLayer - !GetAtt NoProxyLayerVersionStack.Outputs.NodeJsLambdaLayer Endpoint: !GetAtt NoProxyRDSStack.Outputs.ClusterReaderEndpoint RDSSecurityGroup: !GetAtt NoProxyRDSStack.Outputs.RDSSecurityGroup ProxySecurityGroup: NA CreateRDSProxy: !FindInMap [StackMap, NoProxyMap, CreateRDSProxy] ClusterEndpointResourceId: !GetAtt NoProxyRDSStack.Outputs.ClusterEndpointResourceId SSLCertURL: !Sub https://truststore.pki.rds.amazonaws.com/${AWS::Region}/${AWS::Region}-bundle.pem Tags: - Key: Project Value: !FindInMap [StackMap, NoProxyMap, ResourceTags] - Key: Environment Value: !Ref Environment Condition: IsLoadTest ProxyAddUsersStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: add-users.template Parameters: VpcID: !GetAtt ProxyVPCStack.Outputs.VpcID PrivateSubnet1: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet1 PrivateSubnet2: !GetAtt ProxyVPCStack.Outputs.PrivateSubnet2 LayerVersionArn: !GetAtt ProxyLayerVersionStack.Outputs.PythonLambdaLayer Endpoint: !GetAtt ProxyRDSStack.Outputs.ClusterEndpoint RDSSecurityGroup: !GetAtt ProxyRDSStack.Outputs.RDSSecurityGroup CreateRDSProxy: !FindInMap [StackMap, ProxyMap, CreateRDSProxy] ProxySecurityGroup: !GetAtt ProxyRDSStack.Outputs.ProxySecurityGroup DBSecretArn: !GetAtt ProxyRDSStack.Outputs.DBSecret DBProxyName: !GetAtt ProxyRDSStack.Outputs.DBProxyName DBProxyArn: !GetAtt ProxyRDSStack.Outputs.DBProxyArn UsersToCreate: !Ref UsersToCreate Tags: - Key: Project Value: !FindInMap [StackMap, ProxyMap, ResourceTags] - Key: Environment Value: !Ref Environment Condition: IsLoadTest NoProxyAddUsersStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: add-users.template Parameters: VpcID: !GetAtt NoProxyVPCStack.Outputs.VpcID PrivateSubnet1: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet1 PrivateSubnet2: !GetAtt NoProxyVPCStack.Outputs.PrivateSubnet2 LayerVersionArn: !GetAtt NoProxyLayerVersionStack.Outputs.PythonLambdaLayer Endpoint: !GetAtt NoProxyRDSStack.Outputs.ClusterEndpoint RDSSecurityGroup: !GetAtt NoProxyRDSStack.Outputs.RDSSecurityGroup ProxySecurityGroup: NA CreateRDSProxy: !FindInMap [StackMap, NoProxyMap, CreateRDSProxy] DBSecretArn: !GetAtt NoProxyRDSStack.Outputs.DBSecret DBProxyName: NA DBProxyArn: NA UsersToCreate: !Ref UsersToCreate Tags: - Key: Project Value: !FindInMap [StackMap, NoProxyMap, ResourceTags] - Key: Environment Value: !Ref Environment Condition: IsLoadTest DashboardStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: dashboard.template Parameters: ProxyAccessLambda: !If - IsPython - !GetAtt ProxyAccessStack.Outputs.PythonFunction - !GetAtt ProxyAccessStack.Outputs.NodeJsFunction NoProxyAccessLambda: !If - IsPython - !GetAtt NoProxyAccessStack.Outputs.PythonFunction - !GetAtt NoProxyAccessStack.Outputs.NodeJsFunction ProxyClusterName: !GetAtt ProxyRDSStack.Outputs.DBClusterName NoProxyClusterName: !GetAtt NoProxyRDSStack.Outputs.DBClusterName DBProxyName: !GetAtt ProxyRDSStack.Outputs.DBProxyName ApiGatewayName: !GetAtt ApiStack.Outputs.ApiGatewayName Tags: - Key: Project Value: !FindInMap [StackMap, LoadTestMap, ResourceTags] - Key: Environment Value: !Ref Environment Condition: IsLoadTest LocustStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: ec2.template Parameters: VpcCidrBlock: !GetAtt LoadTestVPCStack.Outputs.VpcCidrBlock VpcID: !GetAtt LoadTestVPCStack.Outputs.VpcID SubnetID: !GetAtt LoadTestVPCStack.Outputs.PublicSubnet1 InstanceAMI: !Ref LocustAmiId InstanceType: !Ref LocustInstanceType LocustVersion: !Ref LocustVersion SecondaryInstanceCapacity: !Ref LocustSecondaryInstanceCapacity CIDRRange: !Ref CIDRRange UsersToCreate: !Ref UsersToCreate VPCEndpointSecurityGroup: !GetAtt ApiStack.Outputs.VPCEndpointSecurityGroup Tags: - Key: Project Value: !FindInMap [StackMap, LoadTestMap, ResourceTags] - Key: Environment Value: !Ref Environment Condition: IsLoadTest ApiStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: api.template Parameters: VpcCidrBlock: !GetAtt LoadTestVPCStack.Outputs.VpcCidrBlock VpcID: !GetAtt LoadTestVPCStack.Outputs.VpcID PrivateSubnet1: !GetAtt LoadTestVPCStack.Outputs.PrivateSubnet1 PrivateSubnet2: !GetAtt LoadTestVPCStack.Outputs.PrivateSubnet2 ProxyAccessLambda: !If - IsPython - !GetAtt ProxyAccessStack.Outputs.PythonFunction - !GetAtt ProxyAccessStack.Outputs.NodeJsFunction NoProxyAccessLambda: !If - IsPython - !GetAtt NoProxyAccessStack.Outputs.PythonFunction - !GetAtt NoProxyAccessStack.Outputs.NodeJsFunction ApiEndpointType: !Ref ApiEndpointType Tags: - Key: Project Value: !FindInMap [StackMap, LoadTestMap, ResourceTags] - Key: Environment Value: !Ref Environment Condition: IsLoadTest Outputs: LocustAddress: Description: The address of the Locust primary instance. Value: !GetAtt LocustStack.Outputs.LocustAddress Condition: IsLoadTest APIGatewayURL: Description: URL of the API Gateway. Value: !GetAtt ApiStack.Outputs.ApiGatewayURL Condition: IsLoadTest ProxyLambdaName: Description: Proxy tenant data access lambda function. Value: !If - IsPython - !GetAtt ProxyAccessStack.Outputs.PythonFunction - !GetAtt ProxyAccessStack.Outputs.NodeJsFunction NoProxyLambdaName: Description: NoProxy tenant data access lambda function. Value: !If - IsPython - !GetAtt NoProxyAccessStack.Outputs.PythonFunction - !GetAtt NoProxyAccessStack.Outputs.NodeJsFunction Condition: IsLoadTest ProxyAuroraReplica1: Description: Proxy Aurora replica 1. Value: !GetAtt ProxyRDSStack.Outputs.AuroraReplicaName1 NoProxyAuroraReplica1: Description: NoProxy Aurora replica 1. Value: !GetAtt NoProxyRDSStack.Outputs.AuroraReplicaName1 Condition: IsLoadTest ProxyAuroraReplica2: Description: Proxy Aurora replica 2. Value: !GetAtt ProxyRDSStack.Outputs.AuroraReplicaName2 NoProxyAuroraReplica2: Description: NoProxy Aurora replica 2. Value: !GetAtt NoProxyRDSStack.Outputs.AuroraReplicaName2 Condition: IsLoadTest DashboardName: Description: CLoudWatch dashboard. Value: !GetAtt DashboardStack.Outputs.LoadTestDashboard Condition: IsLoadTest APIGatewayLogGroupName: Description: The CloudWatch Group for load test API Gateway. Value: !GetAtt ApiStack.Outputs.APIGatewayLogGroupName Condition: IsLoadTest