AWSTemplateFormatVersion: '2010-09-09'
Description: Country-specific landing pages with CloudFront Functions and a private S3 origin
Resources:
  ContentBucket:
    Type: AWS::S3::Bucket
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      OwnershipControls:
        Rules:
          - ObjectOwnership: BucketOwnerEnforced
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256

  OriginAccessControl:
    Type: AWS::CloudFront::OriginAccessControl
    Properties:
      OriginAccessControlConfig:
        Name: !Sub '${AWS::StackName}-oac'
        OriginAccessControlOriginType: s3
        SigningBehavior: always
        SigningProtocol: sigv4

  CountryCachePolicy:
    Type: AWS::CloudFront::CachePolicy
    Properties:
      CachePolicyConfig:
        Name: !Sub '${AWS::StackName}-country'
        MinTTL: 0
        DefaultTTL: 300
        MaxTTL: 3600
        ParametersInCacheKeyAndForwardedToOrigin:
          EnableAcceptEncodingGzip: true
          EnableAcceptEncodingBrotli: true
          HeadersConfig:
            HeaderBehavior: whitelist
            Headers:
              - CloudFront-Viewer-Country
          CookiesConfig:
            CookieBehavior: none
          QueryStringsConfig:
            QueryStringBehavior: none

  CountryFunction:
    Type: AWS::CloudFront::Function
    Properties:
      Name: !Sub '${AWS::StackName}-route'
      AutoPublish: true
      FunctionConfig:
        Comment: Rewrite the landing page to a country-specific S3 key
        Runtime: cloudfront-js-2.0
      FunctionCode: |
        function handler(event) {
            var request = event.request;
            if (request.uri !== '/' && request.uri !== '/index.html') {
                return {
                    statusCode: 404,
                    statusDescription: 'Not Found',
                    headers: {
                        'cache-control': {value: 'no-store'},
                        'content-type': {value: 'text/plain; charset=utf-8'}
                    },
                    body: 'Not Found'
                };
            }
            var header = request.headers['cloudfront-viewer-country'];
            var country = header ? header.value : '';
            var variant = 'default';
            if (country === 'US') variant = 'us';
            else if (country === 'JP') variant = 'jp';
            else if (country === 'HK') variant = 'hk';
            request.uri = '/_geo/' + variant + '/index.html';
            return request;
        }

  Distribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Enabled: true
        Comment: Country content hands-on
        HttpVersion: http2
        IPV6Enabled: true
        PriceClass: PriceClass_All
        Origins:
          - Id: private-s3
            DomainName: !GetAtt ContentBucket.RegionalDomainName
            OriginAccessControlId: !GetAtt OriginAccessControl.Id
            S3OriginConfig:
              OriginAccessIdentity: ''
        DefaultCacheBehavior:
          TargetOriginId: private-s3
          ViewerProtocolPolicy: redirect-to-https
          AllowedMethods: [GET, HEAD]
          CachedMethods: [GET, HEAD]
          Compress: true
          CachePolicyId: !Ref CountryCachePolicy
          FunctionAssociations:
            - EventType: viewer-request
              FunctionARN: !GetAtt CountryFunction.FunctionARN
        ViewerCertificate:
          CloudFrontDefaultCertificate: true

  ContentBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref ContentBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: AllowThisDistribution
            Effect: Allow
            Principal:
              Service: cloudfront.amazonaws.com
            Action: s3:GetObject
            Resource: !Sub '${ContentBucket.Arn}/*'
            Condition:
              StringEquals:
                AWS:SourceArn: !Sub 'arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${Distribution}'
          - Sid: DenyInsecureTransport
            Effect: Deny
            Principal: '*'
            Action: s3:*
            Resource:
              - !GetAtt ContentBucket.Arn
              - !Sub '${ContentBucket.Arn}/*'
            Condition:
              Bool:
                aws:SecureTransport: 'false'

Outputs:
  BucketName:
    Value: !Ref ContentBucket
  DistributionId:
    Value: !Ref Distribution
  DomainName:
    Value: !GetAtt Distribution.DomainName
  FunctionName:
    Value: !Sub '${AWS::StackName}-route'
