Secure hosting using SSL and AWS CloudFront
In a previous article, I looked at how we can use Let’s Encrypt to automatically create and manage SSL certificates for us. This article follows on in a similar vein, and shows how Amazon Web Services (AWS) also let us create free SSL certificates. This is an example with a static site hosted in an AWS S3 bucket, but can also be applied to any AWS website hosting mechanism (e.g. EC2 instances).
Architecture to Build
We’re going to use a standard AWS static site hosting pattern. The content is stored in an S3 bucket enabled for serving a website, then we use CloudFront to provide the route into that bucket and apply the SSL certificate. We have to apply SSL via CloudFront, since S3 doesn’t allow us to apply SSL directly itself. Then we use the AWS Certificate Manager to get the SSL certificate for us.
Finally, I use a couple of services from outside AWS. I have an e-mail service I use for sending and receiving e-mails, and I use CloudFlare for my domain name resolution.
I could use Route 53 from AWS for the domain name resolution, but it doesn’t have a free tier, so CloudFlare is better for my wallet.
Here’s what our Architecture looks like:
The S3 Bucket
I’m not going to explain how to set up web hosting from an S3 bucket, it is one of the longest-established AWS techniques and there are plenty of tutorials covering this (for example Amazon’s own docs).
The important thing to note is that after we have set this up, we get a URL we can then hit our website from. It’s based on the bucket name, indicates that it’s an S3-hosted website, indicates the region, and sits under amazonaws.com:
Get Our Certificate
Getting our certificate is fairly straightforward, but includes a number of steps to verify site ownership (which is obviously a good thing). So, I started by opening up AWS Certificate Manager and clicking on the button to request a certificate. Note that within the certificate request, I included both my root domain name and a wildcard for sub-domains:
This is followed by a confirmation screen, just for you to review your request. I actually hit an error at this point and got a message that I could not create any more certificates. I raised a ticket to AWS and got this easily sorted, but thought it’s worth mentioning here since I saw quite a few people had also encountered the same problem.
Here’s the review screen:
Then, when you submit your request, you are told how AWS is going to validate your certificate request:
As you can see, AWS sends e-mails to every standard e-mail address it can think of for your website, to allow you to confirm that you really do own the domain and want a certificate for it. The e-mail address ending globaldomainprivacy.net is the e-mail which shows in the whois lookup for the site.
If you click through to the next screen now, you will see that your request is in a pending state because you haven’t completed validation:
Now you need to open up your e-mail and select one of the e-mails which AWS has sent you. It’s simply a link you click on, which takes you to a validation confirmation page. Now if you go back to your certificates listing page, you will see that your status is now issued:
So, to summarise, we’ve got our content available in an S3 bucket and we’ve got our certificate created and ready in AWS. Our next step is to use CloudFront to bring it all together.
When you open up CloudFront and add a new entry, you get presented with a long web form. It looks a bit daunting, but mostly I left the defaults there. I’ll cover the main sections here.
We start with the origin settings, which lets CloudFront know where it will get the site from. The Origin Domain Name is the name of the S3 bucket which holds the content. It’s a dropdown and you just select your S3 bucket. Then there’s Origin Path, which is the directory within your S3 bucket where the content will be found. If you leave it blank (like me), then it will serve from the root of the S3 bucket. Then you see Origin ID, which is some identifier which defaulted to the value you see and I just left it like that:
Now on to distribution settings, where I changed the Price Class dropdown to a more limited range (I’m expecting only traffic from the US and Europe). This is also where we add in our SSL certificate we created earlier. We click on the Custom SSL Certificate radio button, and select our certificate from the dropdown below it:
Cache settings controls (obviously) the caching behaviour. I left that all as default, apart from selecting Redirect HTTP to HTTPS, to make sure everyone gets an https connection:
When we complete the entry of all these values, we end up at the CloudFront distributions dashboard, where we are looking for our status to be Deployed. Note that this can take some time to get to this status (it took many minutes for me):
The important thing to note in that dashboard is the URL which CloudFront has created for us. You can see it under Domain Name in the above dashboard. This is what we need to put in our browser to view our site.
Now we have all our pieces set up, so we can test that it’s working.
Check our Site
With unbridled optimism, I typed the URL into the browser and this is what I saw:
This was a bit of a disappointment. After speaking with my good friends Google and StackOverflow, I identified the problem. In the origin settings input earlier, AWS helpfully provided a dropdown for the Origin Domain Name which I selected. Slightly less helpfully, the value AWS put in that dropdown wasn’t actually the correct URL for my S3 bucket. What is needed in that field is the URL I originally used to view the page in the S3 step at the start of this article.
So, I opened up the origin settings again and corrected the origin:
Once this had re-deployed, I went to the CloudFront URL and saw the expected page:
I left this mistake in the article, because it seemed a common problem and I thought it might help if you were trying to do this yourself.
Let’s reflect where we are. We’ve got an S3 bucket with our content, a certificate for our domain and CloudFront providing us with a way to make an https request. You can see in the above browser screengrab, that although the protocol is showing up as https as we want, we don’t have the padlock icon to show this is secure. This is because our certificate is for our own domain name, whereas the page is being served from a cloudfront.net URL.
We therefore need to make this available from our own domain name, which is the last step in this process.
Use our Domain Name
A word of warning, we are going to start messing about with DNS settings now, which can break your website, stop you receiving e-mail, etc. I suggest you try this on a domain you don’t care about first. I’m not taking any responsibility if you bring down your core business website.
The first thing we need to do is add our domain names we want to use to the CloudFront settings. This is in the distribution settings we used earlier. You need to add your domain names into the Alternate Domain Names box. I added both my root domain and the www variant of it, since I want both to be available:
We save that and let it deploy. Now we need to turn our attention to our DNS records.
As I stated earlier, the AWS way of doing this would be to use Route 53, but I am using CloudFlare. The way you configure things to use CloudFront, is by setting a CNAME record for the domain you want to use within your DNS, and make it an alias of your CloudFront URL.
I won’t go into the details of CloudFlare here (use whatever DNS management you are comfortable with), but use this as an example for your own DNS configuration service.
I’ve set up CNAMEs for the root domain and the www prefixed domain. You can also see that I have pre-existing MX records to handle my e-mail there too:
HERE BE DRAGONS: It is not advised that you set a CNAME on your root domain (like I have done above). Internet standards say that if you set a CNAME on a domain, you cannot have any other type of record on that domain. So, if I set a CNAME on my root domain, the MX records on my root domain won’t work. If you are not sure about what you are doing, you should only set the CNAME on a subdomain (like “www”) and not the root domain.
The reason I can do this, is because CloudFlare has a feature called CNAME flattening which does some magic behind the scenes that keeps everything working. If you do this, make sure your DNS provider has a similar feature:
So, now I’ve set those CNAME entries, I can go to my domain name using https and see my web page:
As you can see, we now have a padlock icon, showing that we have everything in order.
We can also hit the root domain URL and see that is working too:
To recap, we have now built the following Infrastructure:
We have a static website, served from an S3 bucket via CloudFront, with an SSL certificate for our domain obtained via AWS’s Certificate Manager. This is working via external DNS from CloudFlare, whilst keeping our e-mail service functional.
This is a cost-effective way to run a static website, since it is all on-demand and we don’t have to keep servers running if we’re not using them. As an added bonus, this is all available in AWS’s free tier, meaning that we don’t pay for it at all (within sensible usage limits) for the first year. This gives us a good period of time to build up our site and start monetising it, ready for when we exit the free tier and have to start paying.