AWSTemplateFormatVersion: 2010-09-09 Parameters: # Must specify parameters by passing `--parameters` flag into cloudformation # e.g. # ... --parameters ParameterKey=DomainName,ParameterValue="example.com" DomainName: Type: String Description: '(sub)Domain name e.g. (foo.)example.com' CertificateArn: Type: String Description: 'Certificate ARN (must be in `us-east-1`)' S3BucketName: Type: String Description: 'S3 Bucket name' CreateRoute53: Type: String Default: 'false' AllowedValues: - 'true' - 'false' Description: 'Indicates whether to create DNS entry or not' LogCloudfront: Type: String Default: 'false' AllowedValues: - 'true' - 'false' Description: 'Creates logging for cloufront if true' Mappings: RegionMap: # see https://docs.aws.amazon.com/general/latest/gr/rande.html#cf_region # there is only one endpoint 'us-east-1': ZoneId: Z2FDTNDATAQYW2 Conditions: CreateRoute53Cond: !Equals [ !Ref CreateRoute53, 'true' ] LogCloudfrontCond: !Equals [ !Ref LogCloudfront, 'true' ] Resources: S3StorageBucket: Type: AWS::S3::Bucket Properties: AccessControl: Private BucketName: !Ref S3BucketName MetricsConfigurations: - Id: EntireBucket VersioningConfiguration: Status: Suspended # Delete S3 bucket on stack deletion. # Bucket must be empty, otherwise deletion will fail DeletionPolicy: Delete CFIdentityAccess: Type: AWS::CloudFront::CloudFrontOriginAccessIdentity Properties: CloudFrontOriginAccessIdentityConfig: Comment: !Ref DomainName S3StorageBucketPolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref S3BucketName PolicyDocument: Statement: - Action: - "s3:GetObject" Effect: "Allow" Resource: !Join ['', ['arn:aws:s3:::', !Ref S3BucketName, '/*']] Principal: CanonicalUser: !GetAtt CFIdentityAccess.S3CanonicalUserId Sid: "Grant CloudFront Origin Identity to serve content" # Prevent 403 error codes for missing content # ...or can replace CustomErrorResponses.ErrorCode with 403 - Action: - "s3:ListBucket" Effect: "Allow" Resource: !Join ['', ['arn:aws:s3:::', !Ref S3BucketName]] Principal: CanonicalUser: !GetAtt CFIdentityAccess.S3CanonicalUserId Sid: "Grant CloudFront Origin Identity to list bucket" S3LogBucket: Type: AWS::S3::Bucket Condition: LogCloudfrontCond Properties: AccessControl: Private BucketName: !Join ['-', [!Ref S3BucketName, 'accesslog']] VersioningConfiguration: Status: Suspended # Delete S3 bucket on stack deletion # Bucket must be empty, otherwise deletion will fail DeletionPolicy: Delete CloudfrontCDN: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: IPV6Enabled: true Tags: - Key: 'website' Value: Ref: DomainName DistributionConfig: Origins: - DomainName: !GetAtt S3StorageBucket.DomainName Id: CdnOrigin # Make S3 bucket only accessible through CF S3OriginConfig: OriginAccessIdentity: !Join ['/', ['origin-access-identity/cloudfront', !Ref CFIdentityAccess]] CustomErrorResponses: # Default 404 page - ErrorCode: 404 ResponseCode: 404 ResponsePagePath: '/404' # Default root item filename in s3 DefaultRootObject: 'index' DefaultCacheBehavior: ViewerProtocolPolicy: redirect-to-https TargetOriginId: CdnOrigin ForwardedValues: QueryString : true Cookies: Forward : none Enabled: true # alternate domain CNAMEs Aliases: - !Ref DomainName - !Join ['', ['www.', !Ref DomainName]] ViewerCertificate: AcmCertificateArn: !Ref CertificateArn # Options: {`sni-only`, `vip`}. `sni-only` will incur no # additional charges SslSupportMethod: sni-only Logging: Fn::If: - LogCloudfrontCond - Bucket: !GetAtt S3LogBucket.DomainName - Ref: AWS::NoValue Comment: !Ref DomainName R53HostedZone: Type: AWS::Route53::HostedZone Condition: CreateRoute53Cond Properties: Name: !Ref DomainName R53RecordSet: Type: AWS::Route53::RecordSet Condition: CreateRoute53Cond Properties: Type: A AliasTarget: DNSName: !GetAtt CloudfrontCDN.DomainName # There is only one endpoint HostedZoneId: !FindInMap [RegionMap, 'us-east-1', ZoneId] EvaluateTargetHealth: false HostedZoneId: !Ref R53HostedZone Comment: 'Redirect to cloudfront' Name: !Ref DomainName Outputs: AWSDomainURL: Value: !Join ['', ['https://', !GetAtt S3StorageBucket.DomainName]] Description: 'S3 Domain URL' CloudFrontURL: Value: !GetAtt CloudfrontCDN.DomainName Description: 'Cloudfront url'