Creating and renewing your website certificates from Let's Encrypt

OK, *phew*, I've now managed to both create, let my customers run for a 60+ days, and renewed their Let's Encrypt certificates on a number of domains. Time to share my learnings!

First off - a big thank you to Let's Encrypt, it's community of users and, finally, to Ryan Bolger for creating and maintaining his Posh-ACME github repository.

Using the before mentioned repository, I was able to create a reusable powershell script that issues orders for certificates, stores these certificates in a keyvault that my customer organization owns and, finally, renewing those certificates, updating the contents of the customer's keyvault. Let's get to it!

My script starts off with a param declaration, letting you run it either for test or prod:

param([switch]$ForProduction)

Discussing the fictional domain name blog.article.com, running the script for test would issue certificates for
  • dev.blog.article.com
  • test.blog.article.com
  • itest.blog.article.com
  • stest.blog.article.com
... whereas running the script for prod (.\issue-or-renew-certificates-for-blog.article.com.ps1 -ForProduction) would issue certificates for
  • prod.blog.article.com
  • stage.blog.article.com
  • atest.blog.article.com
The convention we use, is that the stage environment (which we have traditionally called atest), should be production like, why we also have place the applicable DNS Zones in our production subscription.

After the param declaration, I include a number of scripts, declaring common functionality that follows our naming conventions:

. .\_ResourceNameFunction.ps1
. .\_Set-AzureSubscription.ps1

The first script, _ResourceNameFunction.ps1, looks like this:



Given a resource in test, we expect the equivalent resource in our production subscription to have a Prod moniker in the place of Test; ex. My-Resource-Group-Test would be named My-Resource-Group-Prod in production.

The second script, _Set-AzureSubscription.ps1, lets us switch back and forth between our test and prod subscriptions:



With these two scripts loaded into our current context, we install Ryan's Posh-ACME module for our current user (done once, so commented out), followed by a declaration of a contact e-mail and a login to our Azure subscription:



Now, we need to instance and instance of SHA256, so that we can compute the expected hash from the key authorization that Let's encrypt hands us:



With the above bits in place, we can start talking to Let's encrypt and to our Azure instance. Before we start asking for new certificates, we need to create an account. This is only done once, however, why the line is commented out:

Set-PAServer LE_PROD #LE_STAGE
#New-PAAccount -AcceptTOS -Contact $CONTACT

OK! Which environments are we working with?



Common to all environments, is the DNS suffix and the DNS entry name. In our organization, we are also using a particular keyvault to store secrets that belongs to our shared infrastructure:

$DnsSuffix = "blog.article.com"
$RecordSetName = "_acme-challenge"
$VaultName = "the-shared-operational-keyvault"

It's time to loop! We need to act on all environments, so ...

$Environments | ForEach-Object -Process {

Here, I found it particularly useful to assign the loop variable to a local one, letting me manually step through each line in the loop while testing (or nervous), instead of running it all at once:

$EnvironmentName = $_

We're then putting together a fully qualified domain name and issue a new order from let's encrypt (which is the first step of the certification dance). Then, given the order, we extract its authorizations (shared secrets):



From the authorizations, we then create our bespoke DNS records, in order to prove to Let's encrypt's authorization servers, that we are in control of the web resource in question (and not trying to take over someone else's):



Time to use one of our helper functions! Regardless of whichever subscription is active, switch to the one indicated by running the script (which defaults to test):

Set-AzureSubscription

Next helper function! We now need to create those TXT DNS entries in our DNS Zones, which we are hosting in a particular resource group:



Though the shared service resource group exist in both our production and our test subscription, the shared infrastructure keyvault lives in production only. Let's switch to that:

Set-AzureSubscription prod

After updating our DNS records, we are now ready to ask Let's encrypt to double-check that we were able to do what it asked us to do:



If we want to follow-up on what happened above, we can issue the following commands (which I've left in the script, albeit commented-out):

#Get-PAOrder -Refresh | Get-PAAuthorizations
#(Get-PAOrder -Refresh | Get-PAAuthorizations).challenges.error | fl

At this point, we've created our DNS records, asked Let's encrypt to make sure we own the said domain. Next up, we need to save our certificates, so that we can associate them with our web sites. Another loop!



Passwords are a hassle. Let's auto-generate one:

$PlainTextPfxPassword = [System.Guid]::NewGuid().ToString("n")

"Could you please issue us a certificate, Let's encrypt? We accept your terms and conditions and you can contact us if you want. The domain in question is this here and we'd like to use this password, please":



Excellent! We have a certificate! Let's put it (and the password) into the keyvault!



Now, repeat that process for the other authorizations. Then, clean up that TXT record that fullfilled its purpose:



Then, repeat the above processes for the rest of the environments. Et voila! We're done!

What did we just create? Something like this:



OK... "but what about the web sites?", the astute reader might ask.

The relationship between the web sites and their DNS zones is depicted below:



In order for the web sites to successfully identify as member of this domain, we install the wildcard certificates for each DNS Zone into their respective site. Now, this blog post is long ... so I will stop here. If you'd like the continuation in a new post please ask for it. :-) 

Comments

Popular posts from this blog

Auto Mapper and Record Types - will they blend?

Unit testing your Azure functions - part 2: Queues and Blobs

Testing WCF services with user credentials and binary endpoints