Migrating This Website To S3

Setting up the TLS certificate

We use Cloudformation for this, and make a separate template. The reason being that the certificate has to be created in the us-east-1 region rather then the region I want to keep the rest of the resources in.

I deploy the stack with:

aws --region us-east-1 cloudformation create-stack \
    --stack-name hiredgnu-certificate \
    --template-body file://certificate.yml

The template is:

AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  DomainName:
    Type: String
    Description: The domain name for the website
    Default: hiredgnu.net

Resources:
  Certificate:
    Type: 'AWS::CertificateManager::Certificate'
    Properties:
      DomainName: !Ref DomainName
      SubjectAlternativeNames:
        - !Sub "*.${DomainName}"
      DomainValidationOptions:
        - DomainName: !Ref DomainName
          HostedZoneId: Z2B00DRLLVN6P9
      ValidationMethod: DNS

Outputs:
  CertificateArn:
    Description: Issued SSL certificate Arn
    Value: !Ref Certificate

Setting up the S3 Bucket, CloudFront Distribution, and DNS records

I deploy the stack with:

aws --region eu-west-1 cloudformation create-stack \
    --stack-name hiredgnu-s3-web \
    --template-body file://s3.yml

The template is:

AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  DomainName:
    Type: String
    Description: The domain name for the website
    Default: hiredgnu.net

  FQDN:
    Type: String
    Description: The fqdn name for the website
    Default: hiredgnu.net

  CertificateArn:
    Type: String
    Default: arn:aws:acm:us-east-1:MY-ACCOUNT-ID:certificate/CERTIFICATEID
    Description: The ARN of the ACM certificate created in us-east-1

Resources:
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Ref FQDN
      PublicAccessBlockConfiguration:
        BlockPublicAcls: false
        BlockPublicPolicy: false
        IgnorePublicAcls: false
        RestrictPublicBuckets: false
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: error.html
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain

  BucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      PolicyDocument:
        Id: MyPolicy
        Version: 2012-10-17
        Statement:
          - Sid: PublicReadForGetBucketObjects
            Effect: Allow
            Principal: '*'
            Action: 's3:GetObject'
            Resource: !Sub "arn:aws:s3:::${S3Bucket}/*"
      Bucket: !Ref S3Bucket

  CloudFrontDistribution:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        Aliases:
          - !Ref FQDN
        Origins:
          - Id: S3Origin
            DomainName: !Sub "${FQDN}.s3-website-eu-west-1.amazonaws.com"
            CustomOriginConfig:
              HTTPPort: 80
              HTTPSPort: 80
              OriginProtocolPolicy: http-only
        Enabled: true
        DefaultCacheBehavior:
          TargetOriginId: S3Origin
          ViewerProtocolPolicy: redirect-to-https
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          ForwardedValues:
            QueryString: false
            Cookies:
              Forward: none
        ViewerCertificate:
          AcmCertificateArn: !Ref CertificateArn
          SslSupportMethod: sni-only
        DefaultRootObject: index.html
        PriceClass: PriceClass_100

  DNSRecord:
    Type: AWS::Route53::RecordSet
    Properties:
      HostedZoneName: !Sub "${DomainName}."
      Name: !Ref FQDN
      Type: A
      AliasTarget:
        DNSName: !GetAtt CloudFrontDistribution.DomainName
        HostedZoneId: Z2FDTNDATAQYW2 # CloudFront hosted zone ID

Outputs:
  S3BucketWebsiteURL:
    Description: URL for website hosted on S3
    Value: !GetAtt S3Bucket.WebsiteURL

  S3BucketRegionalDomainName:
    Description: Regional domain name for bucket
    Value: !GetAtt S3Bucket.RegionalDomainName

  CertificateArn:
    Description: Certificate deployed in us-east-1
    Value: !Ref CertificateArn

  CloudFrontDistributionDomainName:
    Description: CloudFront Distribution Domain Name
    Value: !GetAtt CloudFrontDistribution.DomainName

  CloudFrontDistributionId:
    Description: CloudFront Distribution ID
    Value: !Ref CloudFrontDistribution

  S3BucketName:
    Description: S3 Bucket Name
    Value: !Ref S3Bucket

Deploy the site to S3 using the Pelican Makefile and AWS CLI

Make the static site from the updated RST files:

make html

Upload the generated static files:

aws s3 sync output/ s3://hiredgnu.net/ --delete

Invalidate the Cloudfront cache:

aws cloudfront create-invalidation \
    --distribution-id <DISTRIBUTIONID> \
    --paths "/*"

NOTE: There is a glaring security problem with this implementation. Can you guess what it is? See my next post for details.