Recently my Job for renewing Letsencrypt certificates in KeyVault has stopped working. Upon closer look I found that my Run As account certificate expired:
I navigated to certificate’s properties to renew it as described in Microsoft documentation and thouhgt I have 1 minute work to fix that but no! There is no option to renew it anymore!
I generated completely new certificate instead of expired one and renewed thumbprint in AzureRunAsConnection properties but that did not help.
Microsoft now encouraages us to use managed identities instead of certificates in Automation Account and that’s what I decided to do. I moved to Identity and created new system-managed identity that I was going to use in my certificate renewal runbooks:
Copy principal ID and in Key Vault access policy add this principal with necessary permissions to access certificates.
Next, in Azure role assignments I added permissions to access Key Vault and resource group that contains storage account for Letsencrypt challenges
Now we all done with prerequisites!
And the last thing to do before testing our runbook is to change authentication from Run As account with a certificate to system-managed identity. We need to delete
1 2 |
$connection = Get-AutomationConnection -Name AzureRunAsConnection Login-AzAccount -ServicePrincipal -Tenant $connection.TenantID -ApplicationId $connection.ApplicationID -CertificateThumbprint $connection.CertificateThumbprint |
strings (I left them commented so you may notice the difference) and replace them with
1 |
$connection = Connect-AzAccount -Identity -AccountId <principalID> |
Here are all three runbooks:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
####################################################################################### # Based on https://intelequia.com/blog/post/1012/automating-azure-application-gateway-ssl-certificate-renewals-with-let-s-encrypt-and-azure-automation # Script that renews a Let's Encrypt certificate in Azure Key Vault (that are used on 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: <Blob public path of the previously created storage account> # - Example: 'https://test.blob.core.windows.net/public' # # # Following modules are needed now: Az.Accounts, Az.Network, Az.Storage, Az.KeyVault, ACME-PS # ####################################################################################### [CmdletBinding()] param( # Domain to get the certificate for [string]$Domain, [string]$EmailAddress = "your@email.com", # Resource group name where blob storage resides [string]$STResourceGroupName = "storage-rg", # Blob storage name [string]$storageName = "storage", [string]$KeyVaultName = "KeyVault", [string]$CertNameinKeyVault ) # Ensures that no login info is saved after the runbook is done Disable-AzContextAutosave # 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 #Log in as system managed identity $connection = Connect-AzAccount -Identity -AccountId 7a99cc59-3f03-402a-a6d3-0534226f2495 $daysFromNow = (Get-Date).AddDays($ExpiresInDays) # Get all certificates from Azure Key Vault # $sslCerts = Get-AzKeyVaultCertificate -VaultName $KeyVaultName Write-Output "Trying to get a new certificate for $domain" # Create a state object and save it to the harddrive $tempFolderPath = $env:TEMP + "\" + $domain # Preparing folder for certificate renewal # Remove folder used for certificate renewal if existing if(Test-Path $tempFolderPath -PathType Container) { Get-ChildItem -Path $tempFolderPath -Recurse | Remove-Item -force -recurse Remove-Item $tempFolderPath -Force } $tempFolder = New-Item -Path $tempFolderPath -ItemType "directory" $state = New-ACMEState -Path $tempFolder $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 $tempFolder; # 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; # Create the identifier for the DNS name $identifier = New-ACMEIdentifier $domain; # Create the order object at the ACME service. $order = New-ACMEOrder $state -Identifiers $identifier; # Fetch the authorizations for that order $authZ = Get-ACMEAuthorization -State $state -Order $order; # Select a challenge to fullfill $challenge = Get-ACMEChallenge $state $authZ "http-01"; # Inspect the challenge data $challenge.Data; # Create the file requested by the challenge $fileName = $tempFolderPath + '\' + $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 "container" -Context $ctx -Blob $blobName # Signal the ACME server that the challenge is ready $challenge | Complete-ACMEChallenge $state; # 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; } # We should have a valid order now and should be able to complete it # Therefore we need a certificate key $certKey = New-ACMECertificateKey -Path "$tempFolder\$domain.key.xml"; # Complete the order - this will issue a certificate singing request Complete-ACMEOrder $state -Order $order -CertificateKey $certKey; # Now we wait until the ACME service provides the certificate url while (-not $order.CertificateUrl) { Start-Sleep -Seconds 15 $order | Update-Order $state -PassThru } # As soon as the url shows up we can create the PFX $password = ConvertTo-SecureString -String "Z4FhhB51Um2MistxghBMyJJhJdYboculrq/TQH9GEp8=" -Force -AsPlainText Export-ACMECertificate $state -Order $order -CertificateKey $certKey -Path "$tempFolder\$domain.pfx" -Password $password; # Delete blob to check DNS Remove-AzStorageBlob -Container "container" -Context $ctx -Blob $blobName ### Upload new Certificate version to KeyVault Import-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertNameinKeyVault -FilePath "$tempFolder\$domain.pfx" -Password $password Write-Output "Completed. Check the certificates in Key Vault. there should be the new one named $CertNameinKeyVault" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
####################################################################################### # Based on https://intelequia.com/blog/post/1012/automating-azure-application-gateway-ssl-certificate-renewals-with-let-s-encrypt-and-azure-automation # Script that renews a Let's Encrypt certificate in Azure Key Vault (that are used on 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: <Blob public path of the previously created storage account> # - Example: 'https://test.blob.core.windows.net/public' # # # Following modules are needed now: Az.Accounts, Az.Network, Az.Storage, Az.KeyVault, ACME-PS # ####################################################################################### [CmdletBinding()] param( # Domain to get the certificate for [string[]]$Domains = @("domain1.org","domain2.com","domain3.org"), [string]$CertNameinKeyVault = "MyMultipleSANcertificate", [string]$EmailAddress = "your@email.com", # Resource group name where blob starage resides [string]$STResourceGroupName = "storage-rg", # Blob storage name [string]$storageName = "storage", [string]$KeyVaultName = "KeyVault" ) # Ensures that no login info is saved after the runbook is done Disable-AzContextAutosave # 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 #Use managed identity $connection = Connect-AzAccount -Identity -AccountId 7a99cc59-3f03-402a-a6d3-051b276f5495 $daysFromNow = (Get-Date).AddDays($ExpiresInDays) # Get all certificates from Azure Key Vault # $sslCerts = Get-AzKeyVaultCertificate -VaultName $KeyVaultName Write-Output "Trying to get a new certificate for $domain" # Create a state object and save it to the harddrive $tempFolderPath = $env:TEMP + "\" + $domain # Preparing folder for certificate renewal # Remove folder used for certificate renewal if existing if(Test-Path $tempFolderPath -PathType Container) { Get-ChildItem -Path $tempFolderPath -Recurse | Remove-Item -force -recurse Remove-Item $tempFolderPath -Force } $tempFolder = New-Item -Path $tempFolderPath -ItemType "directory" $state = New-ACMEState -Path $tempFolder $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 $tempFolder; # 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; # Create the identifier for the DNS name # $identifier = New-ACMEIdentifier $domain; # Create the order object at the ACME service. # $order = New-ACMEOrder $state -Identifiers $identifier; $dnsIdentifiers = $Domains | ForEach-Object { New-ACMEIdentifier $_ }; # Create a new order $order = New-ACMEOrder -State $state -Identifiers $dnsIdentifiers; # Fetch the authorizations for that order $authZ = @(Get-ACMEAuthorization -State $state -Order $order); foreach($auth in $authZ) { # Select a challenge to fullfill $challenge = Get-ACMEChallenge $state $auth "http-01"; # Inspect the challenge data $challenge.Data; # Create the file requested by the challenge $fileName = $tempFolderPath + '\' + $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 "container" -Context $ctx -Blob $blobName # Signal the ACME server that the challenge is ready $challenge | Complete-ACMEChallenge $state; } # 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; } # Should the order get invalid, use Get-ACMEAuthorizationError to list error details. if($order.Status -ieq ("invalid")) { $order | Get-ACMEAuthorizationError -State $AcmeStateDir; throw "Order was invalid"; } # We should have a valid order now and should be able to complete it # Therefore we need a certificate key $certKey = New-ACMECertificateKey -Path "$tempFolder\$domain.key.xml"; # Complete the order - this will issue a certificate singing request Complete-ACMEOrder $state -Order $order -CertificateKey $certKey; # Now we wait until the ACME service provides the certificate url while (-not $order.CertificateUrl) { Start-Sleep -Seconds 15 $order | Update-Order $state -PassThru } # As soon as the url shows up we can create the PFX $password = ConvertTo-SecureString -String "Z4FAhB51sm2MistxghB2yJJhJdYboculrq/TQH9GEp8=" -Force -AsPlainText Export-ACMECertificate $state -Order $order -CertificateKey $certKey -Path "$tempFolder\$domain.pfx" -Password $password; # Delete blob to check DNS Remove-AzStorageBlob -Container "container" -Context $ctx -Blob $blobName ### Upload new Certificate version to KeyVault Import-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $CertNameinKeyVault -FilePath "$tempFolder\$domain.pfx" -Password $password Write-Output "Completed. Check the certificates in Key Vault. there should be the new one named $CertNameinKeyVault" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
####################################################################################### # Based on https://intelequia.com/blog/post/1012/automating-azure-application-gateway-ssl-certificate-renewals-with-let-s-encrypt-and-azure-automation # Script that renews a Let's Encrypt certificate in Azure Key Vault (that are used on 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: <Blob public path of the previously created storage account> # - Example: 'https://test.blob.core.windows.net/public' # # # Following modules are needed now: Az.Accounts, Az.Network, Az.Storage, Az.KeyVault, ACME-PS # ####################################################################################### [CmdletBinding()] param( $ExpiresInDays = 15, [string]$EmailAddress = "your@email.com", [string]$STResourceGroupName = "storage-rg", [string]$storageName = "storage", [string]$KeyVaultName = "KeyVault" ) # Ensures that no login info is saved after the runbook is done Disable-AzContextAutosave # 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 #Use managed identity $connection = Connect-AzAccount -Identity -AccountId 7a99cc59-3f03-412a-a6d3-054b726g0130 $daysFromNow = (Get-Date).AddDays($ExpiresInDays) # Get all certificates from Azure Key Vault $sslCerts = Get-AzKeyVaultCertificate -VaultName $KeyVaultName Write-Output "Check for certificates that expire in $ExpiresInDays days (on $daysFromNow)" $sslCerts | ForEach-Object { $cert = Get-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $_.name # Save ID of the current certificate versions, that will be used for disabling later $certificateOldVersion = $cert.Version # Check if the certificate corresponds naming agreement (starts with 'LetsEncrypt' and whether it is expiring) if ($cert.Name -like "LetsEncrypt-*" -and $cert.Expires -le $daysFromNow) { # Extract domain name from the Subject $domain = $cert.Certificate.Subject.Replace("CN=", "") $AGOldCertName = $cert.Name Write-Output "Start renewing of the certificate: $AGOldCertName for $domain" # Create a state object and save it to the harddrive $tempFolderPath = $env:TEMP + "\" + $domain # Preparing folder for certificate renewal # Remove folder used for certificate renewal if existing if(Test-Path $tempFolderPath -PathType Container) { Get-ChildItem -Path $tempFolderPath -Recurse | Remove-Item -force -recurse Remove-Item $tempFolderPath -Force } $tempFolder = New-Item -Path $tempFolderPath -ItemType "directory" $state = New-ACMEState -Path $tempFolder $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 $tempFolder; # 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; # Create the identifier for the DNS name $identifier = New-ACMEIdentifier $domain; # Create the order object at the ACME service. $order = New-ACMEOrder $state -Identifiers $identifier; # Fetch the authorizations for that order $authZ = Get-ACMEAuthorization -State $state -Order $order; # Select a challenge to fullfill $challenge = Get-ACMEChallenge $state $authZ "http-01"; # Inspect the challenge data $challenge.Data; # Create the file requested by the challenge $fileName = $tempFolderPath + '\' + $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 "container" -Context $ctx -Blob $blobName # Signal the ACME server that the challenge is ready $challenge | Complete-ACMEChallenge $state; # 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; } # We should have a valid order now and should be able to complete it # Therefore we need a certificate key $certKey = New-ACMECertificateKey -Path "$tempFolder\$domain.key.xml"; # Complete the order - this will issue a certificate singing request Complete-ACMEOrder $state -Order $order -CertificateKey $certKey; # Now we wait until the ACME service provides the certificate url while (-not $order.CertificateUrl) { Start-Sleep -Seconds 15 $order | Update-Order $state -PassThru } # As soon as the url shows up we can create the PFX $password = ConvertTo-SecureString -String "Z4FAhB51Um222364gwaMyJJhJdYboculrq/TQH9GEp8=" -Force -AsPlainText Export-ACMECertificate $state -Order $order -CertificateKey $certKey -Path "$tempFolder\$domain.pfx" -Password $password; # Delete blob to check DNS Remove-AzStorageBlob -Container "container" -Context $ctx -Blob $blobName ### Upload new Certificate version to KeyVault Import-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $AGOldCertName -FilePath "$tempFolder\$domain.pfx" -Password $password # Disable older certificate version Update-AzKeyVaultCertificate -VaultName $KeyVaultName -Name $AGOldCertName -Version $certificateOldVersion -Enable $false Write-Output "Older version ID: '$certificateOldVersion' for certificate $AGOldCertName is disabled" Write-Output "Completed renewing of the certificate: $AGOldCertName for $domain. New certificate version is uploaded." } else { Write-Output "$cert.Name expires in $cert.Expires days and does not need a renewal yet" } } Write-Output "Completed" |
Good luck!