Conditions: IsFailoverRegion: !Not - !Equals - !Ref 'PrimaryRegionName' - !Ref 'AWS::Region' IsPrimaryRegion: !Equals - !Ref 'PrimaryRegionName' - !Ref 'AWS::Region' Metadata: AWS::CloudFormation::Interface: ParameterGroups: [] ParameterLabels: {} Comments: '' CreatedBy: Carter Meyers (AWS) Description: This application deploys a Global RDS Aurora cluster. LastUpdated: February 20, 2023 Version: v1.09 Outputs: CustomCloudFrontCdnFqdnExport: Export: Name: !Join - '' - - !Ref 'MainStackName' - -CustomCloudFrontDashboardEndpoint Value: !Join - . - - dashboard - !Ref 'PublicFqdn' DefaultCloudFrontCdnFqdnExport: Export: Name: !Join - '' - - !Ref 'MainStackName' - -DefaultCloudFrontDashboardEndpoint Value: !GetAtt 'CloudFrontDistribution.DomainName' Parameters: CodeDownloadUrl: Default: https://codeload.github.com/aws-samples/amazon-aurora-postgresql-fast-failover-demo/zip/refs/heads/main Description: The URL from which the supporting codebase can be downloaded. This codebase is used to deploy the demo dashboard. Type: String DatabaseAdminPassword: Description: The password to be used for the RDS Aurora admin account. NoEcho: true Type: String DatabaseAdminUsername: Description: The username to be used for the RDS Aurora admin account. Type: String FailoverDatabaseSubnetZoneACidr: Default: 10.10.10.0/24 Description: The CIDR range you wish to use for your primary database subnet. Type: String FailoverDatabaseSubnetZoneBCidr: Default: 10.10.13.0/24 Description: The CIDR range you wish to use for your failover database subnet. Type: String FailoverPrivateSubnetZoneACidr: Default: 10.10.9.0/24 Description: The CIDR range you wish to use for your primary private subnet. Type: String FailoverPrivateSubnetZoneBCidr: Default: 10.10.12.0/24 Description: The CIDR range you wish to use for your failover private subnet. Type: String FailoverPublicSubnetZoneACidr: Default: 10.10.8.0/24 Description: The CIDR range you wish to use for your primary public subnet. Type: String FailoverPublicSubnetZoneBCidr: Default: 10.10.11.0/24 Description: The CIDR range you wish to use for your failover public subnet. Type: String FailoverRegionName: Default: us-east-2 Description: The name of the failover region (e.g., us-east-1). You may choose any AWS Region that supports the required services. The primary and failover regions must be different. Type: String FailoverVpcCidr: Default: 10.10.8.0/21 Description: The CIDR range you wish to use for your VPC. Type: String MainStackName: Type: String PrimaryDatabaseSubnetZoneACidr: Default: 10.10.2.0/24 Description: The CIDR range you wish to use for your primary database subnet. Type: String PrimaryDatabaseSubnetZoneBCidr: Default: 10.10.5.0/24 Description: The CIDR range you wish to use for your failover database subnet. Type: String PrimaryPrivateSubnetZoneACidr: Default: 10.10.1.0/24 Description: The CIDR range you wish to use for your primary private subnet. Type: String PrimaryPrivateSubnetZoneBCidr: Default: 10.10.4.0/24 Description: The CIDR range you wish to use for your failover private subnet. Type: String PrimaryPublicSubnetZoneACidr: Default: 10.10.0.0/24 Description: The CIDR range you wish to use for your primary public subnet. Type: String PrimaryPublicSubnetZoneBCidr: Default: 10.10.3.0/24 Description: The CIDR range you wish to use for your failover public subnet. Type: String PrimaryRegionName: Default: us-east-1 Description: The name of the primary region (e.g., us-east-1). You may choose any AWS Region that supports the required services. The primary and failover regions must be different. Type: String PrimaryVpcCidr: Default: 10.10.0.0/21 Description: The CIDR range you wish to use for your VPC. Type: String PrivateHostedZoneId: Type: String PublicFqdn: Description: >- The FQDN to be used by this application (e.g., multi-region-aurora.example.com). An Amazon ACM Certificate will be issued for this FQDN and attached to an Amazon ALB. This FQDN should NOT have a DNS record currently defined in the corresponding Route 53 Hosted Zone. Type: String PublicHostedZoneId: Description: The ID of the public Route 53 Hosted Zone corresponding to the public Service FQDN. Type: String Resources: Bucket: Properties: AccessControl: Private BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true VersioningConfiguration: Status: Suspended Type: AWS::S3::Bucket BucketPolicy: DependsOn: - Bucket - CloudFrontAccessIdentity Properties: Bucket: !Ref 'Bucket' PolicyDocument: Statement: - Action: - s3:GetObject Effect: Allow Principal: AWS: !Join - '' - - 'arn:' - !Ref 'AWS::Partition' - ':iam::cloudfront:user/CloudFront Origin Access Identity ' - !Ref 'CloudFrontAccessIdentity' Resource: - !Join - '' - - !GetAtt 'Bucket.Arn' - /* Type: AWS::S3::BucketPolicy CloudFrontAccessIdentity: Properties: CloudFrontOriginAccessIdentityConfig: Comment: Dashboard Type: AWS::CloudFront::CloudFrontOriginAccessIdentity CloudFrontDistribution: DependsOn: - TlsCertificate - ResponseHeadersPolicy - CloudFrontAccessIdentity Properties: DistributionConfig: Aliases: - !Join - . - - dashboard - !Ref 'PublicFqdn' Comment: !Join - '' - - 'Dashboard Controller for ' - !Ref 'MainStackName' DefaultCacheBehavior: CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad Compress: true ForwardedValues: QueryString: true ResponseHeadersPolicyId: !Ref 'ResponseHeadersPolicy' TargetOriginId: dashboard-bucket ViewerProtocolPolicy: redirect-to-https Enabled: true HttpVersion: http2 IPV6Enabled: true Origins: - DomainName: !GetAtt 'Bucket.DomainName' Id: dashboard-bucket S3OriginConfig: OriginAccessIdentity: !Join - '' - - origin-access-identity/cloudfront/ - !Ref 'CloudFrontAccessIdentity' ViewerCertificate: AcmCertificateArn: !Ref 'TlsCertificate' MinimumProtocolVersion: TLSv1.1_2016 SslSupportMethod: sni-only Type: AWS::CloudFront::Distribution DashboardCodeDeployer: DependsOn: - DashboardCodeDeployerRole Properties: Architectures: - x86_64 Code: ZipFile: "import sys,subprocess\nsubprocess.call('pip install cfnresponse -t /tmp/ --no-cache-dir'.split(),stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)\nsys.path.insert(1,'/tmp/')\nimport\ \ os,json,glob,boto3,shutil,urllib3,zipfile,mimetypes,cfnresponse\nfrom collections import defaultdict\nfrom botocore.exceptions import ClientError as boto3_client_error\nhttp=urllib3.PoolManager()\n\ try:from urllib2 import HTTPError,build_opener,HTTPHandler,Request\nexcept ImportError:from urllib.error import HTTPError;from urllib.request import build_opener,HTTPHandler,Request\n'\\n -\ \ CodeBucketName | str\\n - CodeDownloadUrl | str\\n'\ndef handler(event,context):\n\tU='VersionId';T='Key';S='/dashboard/';R=False;Q='RequestType';J='CodeBucketName';D=context;A=event;print(json.dumps(A));C=A['ResourceProperties']['Properties'];E=boto3.client('s3');F={}\n\ \tif A[Q]in['Create','Update']:\n\t\tG='/tmp/dashboard_code.zip';K=G.replace('.zip','');'\\n Download the codebase\\n ';V=urllib3.PoolManager();L=V.request('GET',C['CodeDownloadUrl'],preload_content=R)\n\ \t\tif L.status!=200:return R\n\t\twith L as W,open(G,'wb')as X:shutil.copyfileobj(W,X)\n\t\t'\\n Unzip the downloaded code\\n '\n\t\twith zipfile.ZipFile(G,'r')as Y:Y.extractall(K)\n\ \t\t'\\n For each file in the local code directory\\n '\n\t\tfor B in glob.iglob(K+'**/**',recursive=True):\n\t\t\t\"\\n If it's one of the dashboard files and\ \ it's a file, not a directory, we'll upload it to S3\\n \"\n\t\t\tif S in B and os.path.isfile(B):\n\t\t\t\ttry:Z=B.split(S)[1];E.upload_file(B,C[J],Z,ExtraArgs={'ContentType':mimetypes.guess_type(B)[0]})\n\ \t\t\t\texcept boto3_client_error as H:print('Failed to Upload Dashboard File: '+str(H));return cfnresponse.send(A,D,cfnresponse.FAILED,F)\n\telif A[Q]in['Delete']:\n\t\t\"\\n Here,\ \ we'll delete all objects, versions, and delete markers from the bucket.\\n \";a=E.get_paginator('list_object_versions');I=[]\n\t\tfor M in a.paginate(Bucket=C[J]):\n\t\t\tfor N in ['Versions','DeleteMarkers']:\n\ \t\t\t\tif N in M:\n\t\t\t\t\tfor O in M[N]:I.append({T:O[T],U:O[U]})\n\t\tfor P in range(0,len(I),1000):\n\t\t\ttry:b=E.delete_objects(Bucket=C[J],Delete={'Objects':I[P:P+1000],'Quiet':True})\n\ \t\t\texcept boto3_client_error as H:print('Failed to Delete S3 Objects: '+str(H));return cfnresponse.send(A,D,cfnresponse.FAILED,F)\n\treturn cfnresponse.send(A,D,cfnresponse.SUCCESS,F)" Description: Downloads dashboard code from a remote repo and deploys it to Amazon S3 Environment: Variables: CODE_BUCKET_NAME: !Ref 'Bucket' CODE_DOWNLOAD_URL: !Ref 'CodeDownloadUrl' Handler: index.handler Layers: - !Join - '' - - '{{resolve:ssm:/' - !Ref 'MainStackName' - /RegionalLambdaLayerVersionArn}} MemorySize: 128 Role: !GetAtt 'DashboardCodeDeployerRole.Arn' Runtime: python3.9 Timeout: 30 TracingConfig: Mode: PassThrough Type: AWS::Lambda::Function DashboardCodeDeployerLogGroup: DeletionPolicy: Delete DependsOn: - DashboardCodeDeployer Properties: LogGroupName: !Join - '' - - /aws/lambda/ - !Ref 'DashboardCodeDeployer' RetentionInDays: 30 Type: AWS::Logs::LogGroup DashboardCodeDeployerResource: DependsOn: - Bucket - DashboardCodeDeployer - DashboardCodeDeployerLogGroup Properties: Properties: CodeBucketName: !Ref 'Bucket' CodeDownloadUrl: !Ref 'CodeDownloadUrl' ServiceToken: !GetAtt 'DashboardCodeDeployer.Arn' Type: Custom::DeployDashboardCode Version: '1.0' Type: AWS::CloudFormation::CustomResource DashboardCodeDeployerRole: DependsOn: [] Properties: AssumeRolePolicyDocument: Statement: - Action: - sts:AssumeRole Effect: Allow Principal: Service: - lambda.amazonaws.com ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole MaxSessionDuration: 3600 Policies: - PolicyDocument: Statement: - Action: - s3:PutObject - s3:ListBucket - s3:DeleteObject - s3:ListBucketVersions - s3:DeleteObjectVersion Effect: Allow Resource: - !GetAtt 'Bucket.Arn' - !Join - '' - - !GetAtt 'Bucket.Arn' - /* Sid: PutS3Objects PolicyName: main-policy Type: AWS::IAM::Role DnsRecord: DependsOn: - CloudFrontDistribution Properties: HostedZoneId: !Ref 'PublicHostedZoneId' RecordSets: - AliasTarget: DNSName: !GetAtt 'CloudFrontDistribution.DomainName' HostedZoneId: Z2FDTNDATAQYW2 Name: !Join - . - - dashboard - !Ref 'PublicFqdn' Type: A Type: AWS::Route53::RecordSetGroup ResponseHeadersPolicy: Properties: ResponseHeadersPolicyConfig: CorsConfig: AccessControlAllowCredentials: false AccessControlAllowHeaders: Items: - '*' AccessControlAllowMethods: Items: - GET - POST - OPTIONS AccessControlAllowOrigins: Items: - '*' AccessControlExposeHeaders: Items: - '*' OriginOverride: false Name: !Join - '' - - !Ref 'MainStackName' - -CORS-With-Preflight Type: AWS::CloudFront::ResponseHeadersPolicy TlsCertificate: Properties: DomainName: !Join - . - - dashboard - !Ref 'PublicFqdn' DomainValidationOptions: - DomainName: !Join - . - - dashboard - !Ref 'PublicFqdn' HostedZoneId: !Ref 'PublicHostedZoneId' ValidationMethod: DNS Type: AWS::CertificateManager::Certificate