In my previous article about Let’s Encrypt certificates Use Let’s Encrypt free certificates in Windows for Veeam Cloud Connect I explained the basics of Let’s Encrypt technology, and how to use its certificates on a Windows machine using ACMEsharp libraries with Powershell. I found out that the previous script had a problem with renewals, so I went on to fix it.
UPDATE 2019-01-29: If you are looking for complete automation using AWS Route53 DNS services, read the blog post about Version 3 of this script here.
Certificate renewal and identifier renewal
The simple renewal of the certificate itself is not enough. In fact, I found out that not just the certificate expires in 90 days, but also the domain ownership proof at LetsEncrypt expires every 30 days, so its identifier and its related challenge expire too and we have to renew them, otherwise the challenge for the certificate will be never completed. To do so, I added a new part in the code:
#################### ### SCRIPT START ### #################### ### INITIALIZATION ### # This command loads the ACMESharp module at each execution. # The script works only if you already installed the Powershell plugin. # If not, go and run # install-Module ACMESharp # in the Powershell console. # This uses Powershell Gallery if you have at least Powershell 5.0. # If you want to update ACMESharp to the latest version, run: # update-module ACMESharp # NOTE: this script has been developed with ACMESharp 0.9.1.326 Set-ExecutionPolicy unrestricted # Load ACMESharp module import-module ACMESharp # Change to the Vault folder cd C:\ProgramData\ACMESharp\sysVault ### VARIABLES ### # alias for the ACME request. # As long as you don't run more than one request per day, this is correct. # Otherwise, plan to add also hours and minutes to make your requests unique. $alias = "vcc-$(get-date -format yyyyMMdd)" # Let's Encrypt certificates expire after 90 days, so you will have many of them in the local # certificate store after some time. It's easier to identify them if we give them a unique name. # We use the date here to do so. $certname = "vcc-$(get-date -format yyyyMMdd)" # Give a name to the PFX file on disk, based on the certificate name $pfxfile = "C:\ProgramData\ACMESharp\sysVault\$certname.pfx" # Store the certificates into the Local Store of the Local Machine account $certPath = "\localMachine\my" # Configure the FQDN that the certificate needs to be binded to $domain = "cc.virtualtothecore.com" # Give a friendly name to the certificate so that it can be identified in the certificate store $friendlyname = "letsencrypt-$(get-date -format yyyyMMdd)" ### PART 1: UPDATE THE IDENTIFIER ### New-ACMEIdentifier -Dns $domain -Alias $alias Complete-ACMEChallenge $alias -ChallengeType dns-01 -Handler manual (Update-ACMEIdentifier $alias -ChallengeType dns-01).Challenges | Where-Object {$_.Type -eq "dns-01"} > challenge.txt $RRtext = Select-String challenge.txt -Pattern "RR " -CaseSensitive | select Line | Out-String # Here we grab the new TXT DNS Record and output in a message box. # In this way we have all the time to go and edit the DNS server before # we continue with the script. # add the required .NET assembly for the MessageBox Add-Type -AssemblyName System.Windows.Forms $msgBoxInput = [System.Windows.Forms.MessageBox]::Show($RRtext,'Update your DNS with this TXT record, Use CTRL+C to get the text','OK','Information') switch ($msgBoxInput) { 'OK' { Submit-ACMEChallenge $alias -ChallengeType dns-01 Update-ACMEIdentifier $alias ### PART 2: UPDATE THE CERTIFICATE ### # Generate a new certificate New-ACMECertificate ${alias} -Generate -Alias $certname # Submit the certificate request Submit-ACMECertificate $certname # Wait until the certificate is available (has a serial number) before moving on # as API work in async mode so the cert may not be immediately released. $serialnumber = $null $serialnumber = $(update-AcmeCertificate $certname).SerialNumber # Export the new Certificate to a PFX file Get-ACMECertificate $certname -ExportPkcs12 $pfxfile # Import Certificate into Certificate Store Import-PfxCertificate -CertStoreLocation cert:\localMachine\my -Exportable -FilePath $pfxfile ### PART 3: INSTALL THE CERTIFICATE INTO VEEAM CLOUD CONNECT asnp VeeamPSSnapin Connect-VBRServer -Server localhost $certificate = Get-VBRCloudGatewayCertificate -FromStore | Where {$_.SerialNumber -eq $serialnumber} Add-VBRCloudGatewayCertificate -Certificate $certificate Disconnect-VBRServer } } Return ### SCRIPT END ###
So, what we do now? In the variables, we have a new dynamic “Alias” instead of a fixed name. This is because a new identifier has to be created each time, so we use the date as a dynamic value to create a unique alias. This alias is then used to create a new identifier, pointing to the same dns record. Once executed, we receive in the output the new dns txt record:
We have to pause the script and go to our DNS server to update the Resource Record. As you may notice, the RR name is the same of the previous article, but it has a new value. This is going to change each time a new challenge is started. Once the DNS has been updated, the script can be completed. But because any try to copy the RR Value from the PowerShell interface would have required hitting ENTER to copy the highlighted text, the press of the enter button would have restarted the script, and this would lead to a failure because we have not updated the DNS record yet. So I added a different solution using these lines:
(Update-ACMEIdentifier $alias -ChallengeType dns-01).Challenges | Where-Object {$_.Type -eq “dns-01”} > challenge.txt
$RRtext = Select-String challenge.txt -Pattern “RR “ -CaseSensitive | select Line | Out-String
$msgBoxInput = [System.Windows.Forms.MessageBox]::Show($RRtext,‘Update your DNS with this TXT record, Use CTRL+C to get the text’,‘OK’,‘Information’)
The records are recorded in a text file, which is then parsed to grab the “RR ” pattern, so that only the three lines we need are shown in the popup box:
In this way, with a simple CTRL+C we can grab the text and use it to update our DNS record, and then we just go back to the script and hit OK, and the rest of the script is executed.