To generate and automate free SSL certificate on application gateway of load balancer, you need to go through below steps:
- Create a storage account
- Create a public container and two folders inside ".well-known / acme-challenge" to upload tokens
- Create Automation account and then create "Run As Account"
- Create runbook with the large script we have already
- Install "ACME-PS" and "ACMESharp" modules from Gallery of Automation Account
- Inside Application Gateway, Rules and create a path based rule for port 443
Please watch video to understand it properly:
Script for runbook:
#######################################################################################
# Script that renews a Let's Encrypt certificate for an Azure Application Gateway
# Pre-requirements:
# - Have a storage account in which the folder path has been created:
# '/.well-known/acme-challenge/', to put here the Let's Encrypt DNS check files
# - Add "Path-based" rule in the Application Gateway with this configuration:
# - Path: '/.well-known/acme-challenge/*'
# - Check the configure redirection option
# - Choose redirection type: permanent
# - Choose redirection target: External site
# - Target URL:
# - Example: 'https://storageaccount.blob.core.windows.net/public'
# - For execution on Azure Automation: Install "ACME-PS" and "ACMESharp" modules from Gallery of Automation Account
#
#######################################################################################
Param(
[string]$domain
)
$AGName = 'Website-LB';
$EmailAddress = 'test@test.com';
$STResourceGroupName = 'Test';
$AGResourceGroupName = 'Test';
$storageName = 'storageaccount';
$AGOldCertName = 'certificate';
# Ensures that no login info is saved after the runbook is done
Disable-AzContextAutosave
Import-Module ACME-PS
# Log in as the service principal from the Runbook
$connection = Get-AutomationConnection -Name AzureRunAsConnection
Login-AzAccount -ServicePrincipal -Tenant $connection.TenantID -ApplicationId $connection.ApplicationID -CertificateThumbprint $connection.CertificateThumbprint
# Create a state object and save it to the harddrive
$state = New-ACMEState -Path $env:TEMP
$serviceName = 'LetsEncrypt'
# Fetch the service directory and save it in the state
Get-ACMEServiceDirectory $state -ServiceName $serviceName -PassThru;
# Get the first anti-replay nonce
New-ACMENonce $state;
# Create an account key. The state will make sure it's stored.
New-ACMEAccountKey $state -PassThru;
# Register the account key with the acme service. The account key will automatically be read from the state
New-ACMEAccount $state -EmailAddresses $EmailAddress -AcceptTOS;
# Load an state object to have service directory and account keys available
$state = Get-ACMEState -Path $env:TEMP;
# It might be neccessary to acquire a new nonce, so we'll just do it for the sake of the example.
New-ACMENonce $state -PassThru;
$dnsList = @($domain,"www.$domain");
# Create the identifier for the DNS name
$identifiers = @();
foreach ($dns in $dnsList) { $identifiers += New-ACMEIdentifier $dns }
# Create the order object at the ACME service.
$order = New-ACMEOrder $state -Identifiers $identifiers;
# Fetch the authorizations for that order
$authZ = Get-ACMEAuthorization -State $state -Order $order;
foreach ($dns in $dnsList) {
for ($i=0; $i -lt $authZ.Length; $i++) {
if ( $authZ[$i].Identifier.value -eq $dns ) {
# Select a challenge to fullfill
$challenge = Get-ACMEChallenge $state $authZ[$i] "http-01";
# Inspect the challenge data
$challenge.Data;
# Create the file requested by the challenge
$fileName = $env:TMP + '\' + $challenge.Token;
Set-Content -Path $fileName -Value $challenge.Data.Content -NoNewline;
$blobName = ".well-known/acme-challenge/" + $challenge.Token
$storageAccount = Get-AzStorageAccount -ResourceGroupName $STResourceGroupName -Name $storageName
$ctx = $storageAccount.Context
Set-AzStorageBlobContent -File $fileName -Container "public" -Context $ctx -Blob $blobName -Force
# Signal the ACME server that the challenge is ready
$challenge | Complete-ACMEChallenge $state;
}
}
}
Write-Output "wait begins";
# Wait a little bit and update the order, until we see the states
while($order.Status -notin ("ready","invalid")) {
Start-Sleep -Seconds 10;
$order | Update-ACMEOrder $state -PassThru;
}
Write-Output "wait ends";
if ($order.Status -eq "ready" )
{
Write-Output "order is now ready";
# We should have a valid order now and should be able to complete it
# Therefore we need a certificate key
$certKey = New-ACMECertificateKey -Path "$env:TEMP\$domain.key.xml";
# Complete the order - this will issue a certificate singing request
Complete-ACMEOrder $state -Order $order -CertificateKey $certKey;
Write-Output "wait for certificate ";
# Now we wait until the ACME service provides the certificate url
while(-not $order.CertificateUrl) {
Start-Sleep -Seconds 15
$order | Update-Order $state -PassThru
}
Write-Output "wait for certificate ends";
# As soon as the url shows up we can create the PFX
$password = ConvertTo-SecureString -String "password" -Force -AsPlainText
Export-ACMECertificate $state -Order $order -CertificateKey $certKey -Path "$env:TEMP\$domain.pfx" -Password $password;
# Delete blob to check DNS
Remove-AzStorageBlob -Container "public" -Context $ctx -Blob $blobName
### RENEW APPLICATION GATEWAY CERTIFICATE ###
$appgw = Get-AzApplicationGateway -ResourceGroupName $AGResourceGroupName -Name $AGName
Set-AzApplicationGatewaySSLCertificate -Name $AGOldCertName -ApplicationGateway $appgw -CertificateFile "$env:TEMP\$domain.pfx" -Password $password
Set-AzApplicationGateway -ApplicationGateway $appgw
} else {
Write-Output $order;
throw "The Order was invalid";
}