AWSTemplateFormatVersion: 2010-09-09
Description: Infrastructure for hosting a static web site using CloudFront.
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Domain Parameters"
        Parameters:
          - HostedZoneId
          - DomainName
          - AppendIndexHtml
      - Label:
          default: "User for Programmatic Access"
        Parameters:
          - NewUserName
    ParameterLabels:
      HostedZoneId:
        default: "Hosted Zone ID"
      DomainName:
        default: "Domain Name"
      AppendIndexHtml:
        default: "Append index.html"
      NewUserName:
        default: "User Name"
Parameters:
  HostedZoneId:
    Type: AWS::Route53::HostedZone::Id
    Description: >
      Existing Hosted Zone ID. 
      If this list is empty then you have not created a hosted zone yet.
      A hosted zone can be created in AWS Route53 once you have your domain registered.
  DomainName:
    Type: String
    Description: >
      Full domain name which you want to use for hosting the website.
      For example, 'blog.your-awesome-site.org' or 'wiki.organisation.com'
    AllowedPattern: ^\w[-\w]+(?:[.\w][-\w]+)+$
  NewUserName:
    Type: String
    Default: creator
    Description: >
      Provide a name for the AWS user which will be created for updating the site from the command line. 
      This name must be unique in your account.
    AllowedPattern: ^[@.,\=\-\*\+\w]+$
  AppendIndexHtml:
    Type: String
    Default: Disabled
    AllowedValues:
      - Enabled
      - Disabled
    Description: >
      If enabled all requests ending with "/" will be appended with "index.html". This is useful if you are using a
      blog generator software that generates "pretty" links. For example, "blog.community.org/welcome-home/" will be
      rewritten to "blog.community.org/welcome-home/index.html".

Conditions:
  CreateRewriteFunction: !Equals
    - !Ref AppendIndexHtml
    - Enabled

Resources:
  SiteBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Join ["-", !Split [".", !Ref DomainName]]
  SiteBucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref SiteBucket
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Action: s3:GetObject
            Effect: Allow
            Resource: !Sub "arn:aws:s3:::${SiteBucket}/*"
            Principal:
              CanonicalUser: !GetAtt "CloudFrontOAI.S3CanonicalUserId"
  SiteDNSRecord:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneId: !Ref HostedZoneId
      Name: !Sub "${DomainName}"
      Type: A
      AliasTarget:
        DNSName: !GetAtt CloudFrontDistribution.DomainName
        HostedZoneId: Z2FDTNDATAQYW2

  Certificate:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: !Sub "${DomainName}"
      DomainValidationOptions:
        - DomainName: !Sub "${DomainName}"
          HostedZoneId: !Ref HostedZoneId
      ValidationMethod: DNS

  RewriteFunction:
    Type: AWS::CloudFront::Function
    Condition: CreateRewriteFunction
    Properties:
      Name: !Sub "append-index-to-${SiteBucket}"
      AutoPublish: true
      FunctionCode: |
        function handler(event) {
           if (event.request.uri.endsWith('/')) {
           event.request.uri += 'index.html';
           }
           else if (!event.request.uri.includes('.')) {
           event.request.uri += '/index.html';
           }
           return event.request;
         }
      FunctionConfig:
        Comment: append "index.html" to URL ending with "/"
        Runtime: cloudfront-js-1.0

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Aliases:
          - !Sub "${DomainName}"
        Comment: !Sub "Distribution for ${DomainName}"
        DefaultRootObject: "index.html"
        Enabled: true
        IPV6Enabled: true
        HttpVersion: http2
        DefaultCacheBehavior:
          DefaultTTL: 300
          ForwardedValues:
            QueryString: false
          TargetOriginId: static-website-bucket
          ViewerProtocolPolicy: redirect-to-https
          FunctionAssociations: !If
            - CreateRewriteFunction
            - - EventType: viewer-request
                FunctionARN: !GetAtt RewriteFunction.FunctionARN
            - !Ref AWS::NoValue
        Origins:
          - Id: static-website-bucket
            DomainName: !GetAtt "SiteBucket.RegionalDomainName"
            S3OriginConfig:
              OriginAccessIdentity: !Sub "origin-access-identity/cloudfront/${CloudFrontOAI}"
        ViewerCertificate:
          AcmCertificateArn: !Ref Certificate
          SslSupportMethod: sni-only
        Logging: !Ref AWS::NoValue

  CloudFrontOAI:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: "Access Site Bucket"

  NewUser:
    Type: AWS::IAM::User
    Properties:
      UserName: !Ref NewUserName
      Policies:
        - PolicyName: !Sub "${NewUserName}-can-access-${DomainName}"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "s3:PutObject"
                  - "s3:GetObject"
                  - "s3:ListBucket"
                  - "s3:DeleteObject"
                Resource:
                  - !Sub "arn:aws:s3:::${SiteBucket}"
                  - !Sub "arn:aws:s3:::${SiteBucket}/*"

  UserAccessKey:
    Type: AWS::IAM::AccessKey
    Properties:
      Serial: 1
      Status: Active
      UserName: !Ref NewUser

Outputs:
  BucketName:
    Value: !Ref SiteBucket
    Description: Name of the bucket for storing the website
  AccessKey:
    Value: !Ref UserAccessKey
    Description: Access Key required to update the website programmatically using AWS CLI
  SecretKey:
    Value: !GetAtt UserAccessKey.SecretAccessKey
    Description: >
      Secret Access Key which together with AccessKey provide programmatic access to the bucket
      that stores the web site.
