If you’re looking for a simple command to do this, look no further, there’s a tl;dr below!
If you use Cloudflare to run your website, chances are you use them to handle your website’s DNS records too. Which is great. However, say you want to add SSL support to your website without using the one built into Cloudflare (it doesn’t always work best for us).
This is do-able. LetsEncrypt has been around for a while, and recently it began to support wildcard SSL – essentially a way of saying you own the whole of
*.insanityradio.com instead of just
Great! To generate a wildcard certificate, you have to prove that you own your domain, and the only completely fair way to do this is by using DNS records, as these are the glue that make up your website. But there’s a caveat, and what most online tutorials will tell you to do opens up a HUGE security vulnerability. Not kidding.
Before we delve deeper into a solution, let’s have a quick recap so we understand exactly what we’re trying to achieve.
How does LetsEncrypt work?
LetsEncrypt will give you a SSL certificate if you can prove that you have enough access to a fully-qualified-domain-name. Often, proving you control the content on the website is enough. But to get a wildcard certificate and prove you own the whole domain, you need to prove you can control the website’s DNS records.
How does it prove you have enough access to the domain? It uses a mechanism called challenge and response. We can nicely sum it up in 5 simple steps.
- We ask LetsEncrypt to validate that we own
- LetsEncrypt sends us a challenge text for this domain – think of it like a 2-factor authentication token – it changes every time.
- We put this challenge text up on the website, and tell LetsEncrypt to go and check it.
- LetsEncrypt tells us that they’ve been able to verify it.
- We ask LetsEncrypt to generate a SSL certificate for the domain(s) we’ve just validated.
Traditionally, LetsEncrypt would just prove we control the content on the website. If you remember having to upload those
google_xaohdksjahdsakjdhas.html files to prove to Google Analytics that you own the site, it’s similar, but just highly automated.
Let’s go a step further. If we want to prove we own
[everything].insanityradio.com, for this to work we’d have to scan every possible domain for this code. Because verifying one doesn’t imply we own all of
insanityradio.com (think – if we owned
myblog.wordpress.com and tried to verify
wordpress.com, that wouldn’t make sense). However, as there’s infinite potential for domains, this obviously isn’t possible – we can’t do those 5 steps infinite times.
The best solution? Prove we own
insanityradio.com‘s DNS, and hence physically control all of these sites underneath it.
This works exactly the same as above, but instead of shoving a file on the web server, we have to change its DNS records.
Enter Cloudflare – our DNS provider.
For this to work best, we need to automate changing these records. Unless you want to, every 60 days, do this whole process by hand. The answer seems immediately obvious, and is well documented everywhere:
Just feed your Cloudflare API key and email into the LetsEncrypt automation script, and let it handle everything.
But hang on – there’s a security hole!
Say someone hacks your web server somehow. Traditionally, this is probably not the end of the world.
But hang on, we’ve saved our Cloudflare API key on the server. When the attacker steals this, it’s game over. They have full control over our Cloudflare account, and the worst bit is Cloudflare won’t email us to tell us someone’s using our key.
Use your imagination to guess what an attacker could do with this access. They could:
- Redirect your email traffic and intercept it, reset your passwords, and permanently steal your domain.
- Add malicious code to your website without tampering with the web server
- Disable your Web Application Firewall
- Rack up a huge bill by using billable Cloudflare features
What do now?
Cloudflare doesn’t let you generate a specific key for this, you have to feed the script your entire account’s.
However, there’s a better solution. It’s not perfect, but it completely mitigates the risk of the above.
Say we have a second domain we don’t care much about –
insanityradio.co.uk. Nothing runs on it, but we keep it registered because #brand. If you don’t, you can simply register a new one – the name itself doesn’t matter, it could be anything. We’re going to set up this secondary domain so we can use it to verify we own
What?!? Yup. That’s a thing.
Let’s sign up for a new CloudFlare account (avoid using an email on the secondary domain), and set up our secondary domain with it. As we don’t use this domain for much, it’s less of a concern if an attacker gains access to it. They can’t really do much except generate SSL certificates for our main website – if they do, they don’t have the means to use them.
How does this work?
To validate DNS (step 3), we normally create a
_acme-challege.insanityradio.com DNS record. This contains the challenge response from step 2.
Instead of creating a record with text, we can create an alias record (a DNS CNAME) that tells LetsEncrypt to look at another domain for this response.
For instance, to verify `insanityradio.com`, we can set up the following:
insanityradio.com CNAME _acme-challenge.insanityradio.com.insanityradio.co.uk
The domain name doesn’t need to be as huge as it is – in fact, we can set the record to any domain. However, if you’re verifying multiple sites in one go, you can’t reuse the target because DNS is slow. It’s best practice to use something unique per domain, and the best way to do that is just to use the domain itself.
The Take Aways
Some valuable lessons have been learnt.
- Avoid giving scripts full access to your domain’s DNS.
- Cloudflare’s API keys are extremely dangerous
tl;dr (aka the code)
- Sign up for a new Cloudflare account with a secondary domain and find its API keys
_acme-challenge.insanityradio.comDNS CNAME records pointing to
_acme-challenge.insanityradio.com.insanityradio.co.uk(that’s a big boy). If you want to verify a second level domain like
*.cor.insanityradio.com, add a DNS record like
_acme-challenge.cor.insanityradio.comDNS CNAME records pointing to
$ curl https://get.acme.sh | sh
- Run the following to generate a certificate:
export CF_Key="my cf api key" export CF_Email="[email protected]" acme.sh --issue \ -d insanityradio.com --challenge-alias insanityradio.com.insanityradio.co.uk \ -d *.insanityradio.com --challenge-alias insanityradio.com.insanityradio.co.uk \ -d *.cor.insanityradio.com --challenge-alias cor.insanityradio.com.insanityradio.co.uk \ --dns dns_cf
- Add the following cronjob, to ensure your certificates are auto-renewed close to expiry. Make sure to replace `user` with whatever you want.
0 0 * * * /home/user/.acme.sh/acme.sh --cron --home /home/user/.acme.sh > /dev/null