Nick Padon
Home About
An annoying, low-level use of AWS CloudFormation
Serving a website with SSL, S3, CloudFront, and a Route53 custom domain

The past: 2015

Deploying a website can be instructive and fun - but it does require some infrastructure knowledge. Before accruing the layers of useless web infrastructure knowledge that I currently have, I developed a WordPress site for my fledgling consulting business in 2015. Amazingly, about 35% of the entire web has also used WordPress - so I was....not the first person to do this. I set up a relatively cheaply hosted virtual machine via BlueHost, which was only a little painful - and I was soon happily FTPing all the WordPress config files onto this server and serving my site. WordPress as a content management system was functional but moderately annoying, particularly as I was a nerd and wanted to write all of my own code - so I need to routinely change their config files - but it worked. And, after a few years of website dereliction due my full-time management consulting job, the site died a quiet death.

The present: 2020

I have accrued more useless (and some useful) web infrastructure knowledge. I also started to look into AWS, and, in particular, their CloudFormation infrastructure-as-code offering. My vision was to setup my entire site via a CloudFormation template so that I could...endlessly repeat the pleasure of setting up a site. I also think that near-static sites served via S3 buckets are a cost-effective way to get content out.

So I learned about S3. And IAM. And CloudFront. And CloudFormation. And Route53. There's actually quite a large amount of conceptual building that one has to do before even constructing an architecture:

  • S3 (Simple Storage Service) - A near-infinitely scalable and available object store
  • CloudFront - A content distribution service, leveraging Amazon's huge network
  • IAM (Identity and Access Management) - Amazon's complicated but customizable authentication and authorization framework for AWS
  • Route53 - AWS DNS services
  • CloudFormation - A template-based deployment system for many (all?) of Amazon's service offerings

After all the reading, the vision was:

You can checkout all the code here. But I'll walk through it in some excruciating detail below.

1. Set up an S3 bucket

Resources: # creates a bucket named according to the BucketName Parameters with default web config s3hosting: Type: AWS::S3::Bucket Properties: BucketName: !Ref BucketName AccessControl: PublicRead WebsiteConfiguration: IndexDocument: index.html ErrorDocument: error.html

This is moderately self-explanatory but the key is that the bucket is in "website configuration" mode and will serve up pages as if it gets the right request.

2. Make sure everyone can see the bucket

# creates a policy for the named bucket that allows all get requests openpolicy: Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref BucketName PolicyDocument: Statement: - Action: - 's3:GetObject' Effect: Allow Resource: !Join ['',['arn:aws:s3:::', !Ref BucketName, '/*']] Principal: '*'

This opens up the bucket to the entire world.

3. Set up a certificate.

# creates a cert for ..com mycert: Type: AWS::CertificateManager::Certificate Properties: DomainName: !Ref ApexDomainName DomainValidationOptions: - DomainName: !Ref ApexDomainName HostedZoneId: !Ref route53HostedZoneID ValidationMethod: DNS SubjectAlternativeNames: - !Join ['.',[!Ref SubDomain, !Ref ApexDomainName]]

Wait. Why are we setting up a certificate right now?

Well. One needs a certificate to reference in your CloudFront distribution if you want your CDN to be delivering content with HTTPS, which of course I do. This certificate was...super annoying. I needed to ensure that the same IAM role that had created the Hosted Zone as was requesting the certificate. CloudFormation edits the DNS records in the Hosted Zone and you need to have the right permissions to do so. But of course when I set up the Hosted Zone originally, I had used my root account because I'm an idiot - so I had to delete it with my specially scoped account. But then all the DNS server references in the Hosted Zone changed...but they didn't automatically update on my combined domain name / hosted zone. So I had to update them. My certificate validations were hanging for a long time while I figured that one out.

So then you've got a certificate from the all-powerful Amazon Certificate Authority, which is great.

4. Create a CloudFront distribution

This also required a lot of reading. I suggest you do the same.

webcdn: Type: AWS::CloudFront::Distribution Properties: DistributionConfig: Comment: CDN for S3-backed website DefaultRootObject: index.html Enabled: true PriceClass: PriceClass_100 DefaultCacheBehavior: TargetOriginId: only-origin ViewerProtocolPolicy: redirect-to-https ForwardedValues: QueryString: true Origins: - CustomOriginConfig: OriginProtocolPolicy: http-only DomainName: !Join ['.', [!Ref BucketName, 's3-website', !Ref 'AWS::Region', 'amazonaws.com']] Id: only-origin Aliases: - !Join ['.',[!Ref SubDomain, !Ref ApexDomainName]] #this has to match the certificate ViewerCertificate: AcmCertificateArn: !Ref mycert MinimumProtocolVersion: TLSv1.1_2016 SslSupportMethod: sni-only

I probably created this dev stack like 10 times.

5. Point your Route 53 record to your CloudFront distribution.

dnsentries: Type: AWS::Route53::RecordSet Properties: HostedZoneId: !Ref route53HostedZoneID Name: !Join ['.',[!Ref SubDomain, !Ref ApexDomainName]] Type: A AliasTarget: HostedZoneId: Z2FDTNDATAQYW2 #hard-coded for all cloud front distributions DNSName: !GetAtt webcdn.DomainName

This was pretty straightforward. Interesting that you can use an "A" record. And now, you see the page in all of its SSL S3 glory: here.

Ta-da. Feel free to email if you have questions.