Zertifikatsmanagement mit Azure Automation und Let's Encrypt
Das Projekt Let’s Encrypt hat die Internet Landschaft nachhaltig geprägt. Kostenlose SSL-Zertifikate für Jedermann lassen sich automatisiert erstellen und durch Let’s Encrypt signieren. Durch die breite Akzeptanz durch Browser Hersteller und Cross-Signierung sind die Zertifikate fast überall gültig.
Eine Automatisierungslösung ist durch die kurze Laufzeit von drei Monaten jedoch zwingend erforderlich. Mittels PowerShell in Azure Automation lässt sich ein Workflow erstellen der sich um die Zertifikats-Erneuerung und sichere zentrale Speicherung kümmert. Dieser Workflow ist die optimale Grundlage um die Zertifikate an den eigentlichen Service weiter zu verteilen.
Die Grundlage für eine solche Automatisierungslösung bietet AzAutomation-PoshACME
AzAutomation-PoshACME baut auf mehreren bestehenden Komponenten auf und nutzt diese für einen komplett automatisierten Zertifikatsworkflow.
- Let’s Encrypt
Signiert die erstellten Zertifikate - Azure Automation
Als Basis der Automatisierung in einer server-less Umgebung - Posh-ACME
Die unglaublich tolle Implementierung des ACME Protokolls von Ryan Bolger @rmbolger - Azure DNS
Ohne einen DNS Service der API Unterstützung anbietet ist dieses Vorhaben nicht zu realisieren.
DNS Infrastruktur
Leider gibt es in der sogenannten Enterprise IT immer noch sehr viele Gründe warum eine DNS Zone nicht per API geändert werden kann.
Diese Gründe reichen von organisatorischen Hürden wer die Zone verwaltet, bis hin zu technischen Problemen, wenn der DNS Provider leider keine API anbietet.
Um diese Querelen von Anfang an aus dem Weg zu gehen, hat AzAutomation-PoshACME volle Unterstützung für CNAME Redirection eingebaut.
CNAME Redirection
Unter CNAME Redirection versteht man bei der ACME Validierung das Umleiten der Validierungsanfrage in eine andere DNS Zone. Dies kann eine Subzone des bestehenden DNS Servers sein oder eine komplett andere DNS Zone.
Subzone
In diesem Beispiel wurde unterhalb der DNS Zone “cloudbrothers.info” eine zusätzliche Subzone “levalidation.cloudbrothers.info” angelegt. Der Nameserver Record für diese Zone wurde auf den Azure DNS geändert.
Für die Domain “test.cloudbrothers.info” soll ein Zertifikat erstellt werden. Bei der Validierung wird Let’s Encrypt prüfen ob die Challenge im DNS TXT Record “_acme-challenge.test.cloudbrothers.info” korrekt ist.
Der DNS Eintrag wird mittels eines CNAME Records umgeleitet auf “_acme-challenge.test.cloudbrothers.info.levalidation.cloudbrothers.info”, einem Eintrag in der von Azure verwalteten Subzone.
Dieses Verhalten ermöglicht es für einzelne Records Zertifikate auszustellen ohne die gesamte Root DNS Zone des Unternehmens unter die Kontrolle des Zertifikatsaustellenden zu geben. Jedoch muss für jedes Domäne die ein Zertifkat erhalten soll manuell ein CNAME Record für die ACME Challenge angelegt werden. Dies ist jedoch eine einmalige Aktion.
Andere DNS Zone
In diesem Fall wird nicht eine Subzone unter die Kontrolle des Azure DNS gegeben, sondern eine komplett eigene Domäne.
Die Funktionsweise ist dieselbe, jedoch wird so verhindert das unterhalb der Root Zone des Unternehmens weitere DNS Einträge erstellt werden können.
Azure Komponenten
Die Azure Komponenten beschränken sich auf ein Minimum:
- Resource Group
- DNS Zone
- Storage Account
- Azure Automation Account
- Runbooks
Da für die Automatisierung Azure Automation genutzt wird, sind keine eigenen Compute Ressourcen notwendig
Aufbau der Umgebung
Alle notwendigen Ressourcen können über das Git Repository des Projekts bezogen werden. https://github.com/f-bader/AzAutomation-PoshACME
git clone https://github.com/f-bader/AzAutomation-PoshACME
cd .\AzAutomation-PoshACME\
code .
Hinweis:
Aktuell wird aufgrund der genutzten signierten Zertifikate ausschließlich Windows PowerShell unterstützt!
Deploy Ressources
Das Skript “DeployRessources.ps1” enthält alle notwendigen Kommandos um die notwendigen Ressourcen zu erstellen. Das Skript hat einen Workshop Character und ist Schritt für Schritt zu auszuführen und nicht in einem Rutsch.
Umgebungsvariablen
Im oberen Bereich müssen die Umgebungsvariablen für deine Umgebung angepasst werden.
- ResourceGroupName
Der Name der zu erstellenden Resource Group - Location
In welcher Azure Region die Ressourcen erstellt werden sollen. Der Standard ist West Europa - DNSZoneRootDomain
Welche DNS Zone soll für die Validierung genutzt werden. Für diese Zone wird eine Azure DNS Zone angelegt - MailContact
An welche E-Mail Adresse soll Let’s Encrypt Informationen bei ablaufenden Zertifikaten senden. - BlobStorageName
Der Name des Storage Accounts. Er darf nur Kleinbuchstaben und Zahlen enthalten und muss Weltweit eindeutig sein. - AutomationAccountName
Der Name des Azure Automation Accounts. Standard ist “LetsEncryptAutomation” - PfxPass
Welches Kennwort soll für die exportierten PFX Dateien genutzt werden? Dieser Wert wird verschlüsselt im Azure Automation Account hinterlegt.
Aufbau der Umgebung
Nachdem die Umgebungsvariablen initialisiert sind und der Block PowerShell Code mit F8 ausgeführt wurde ist es an der Zeit sich mit Azure zu verbinden.
Connect-AzAccount
Der nächste Befehl erstellt die notwendige Resource Group
# Create resource group
$ResourceGroup = New-AzResourceGroup -Name $ResourceGroupName -Location $Location
Im nächsten Schritt wird die DNS Zone angelegt.
#region Create DNS Zone
$DNSZone = New-AzDnsZone -Name $DNSZoneRootDomain -ResourceGroupName $ResourceGroupName
# Retrieve DNS server names for the NS records
$DNSZone | Select-Object -ExpandProperty NameServers
# Add those to your custom DNS zone
#endregion
Dabei ist wichtig, dass die DNS Nameserver für diese Zone beim Registrar der Zone oder in der Subzone hinterlegt werden. Nur so weiß Let’s Encrypt das Azure DNS diese Zone verwaltet.
Insgesamt werden vier Nameserver ausgegeben
Für die Speicherung der Zertifikate wird nun ein Storage Account angelegt und der Zugriff auf HTTPS only beschränkt. Außerdem wird ein SASToken für den späteren Zugriff auf den Storage Account aus Azure Automation erstellt.
#region BLOB Storage to store the Posh-ACME configuration data
New-AzStorageAccount -Name $BlobStorageName -ResourceGroupName $ResourceGroupName -Location $Location -Kind StorageV2 -SkuName Standard_LRS -EnableHttpsTrafficOnly $true
$storageAccountKey = Get-AzStorageAccountKey -Name $BlobStorageName -ResourceGroupName $ResourceGroupName | Where-Object KeyName -eq "key1" | Select-Object -ExpandProperty Value
$storageContext = New-AzStorageContext -StorageAccountName $BlobStorageName -StorageAccountKey $storageAccountKey
New-AzStorageContainer -Name "posh-acme" -Context $storageContext
#SAS Token for blob access
$SASToken = New-AzStorageContainerSASToken -Name "posh-acme" -Permission rwdl -Context $storageContext -ExpiryTime (Get-Date).AddYears(5) -StartTime (Get-Date)
#endregion
Damit die Runbooks des Automation Account später auch die DNS Zone verwalten erstellt das Skript eine Entra ID (Azure AD) Applikation und einen Service Principal.
#region Create a service principal without any permissions assigned
$application = New-AzADApplication -DisplayName "Let's Encrypt Certificate Automation" -IdentifierUris "http://localhost"
$spPrincipal = New-AzADServicePrincipal -ApplicationId $application.ApplicationId -Role $null -Scope $null
$spCredential = New-AzADSpCredential -ServicePrincipalObject $spPrincipal -EndDate (Get-Date).AddYears(5)
#endregion
Im Azure Portal wird diese Applikation als “Let’s Encrypt Certificate Automation” geführt
Der Service Principal erhält die Rolle “DNS Zone Contributor” auf die erstellte DNS Zone.
#region Grant service principal "DNS Zone Contributor" permissions to DNS Zone
New-AzRoleAssignment -ObjectId $spPrincipal.Id -ResourceGroupName $ResourceGroupName -ResourceName $DNSZoneRootDomain -ResourceType "Microsoft.Network/dnszones" -RoleDefinitionName "DNS Zone Contributor"
#endregion
Der Automation Account wird mit diesem Befehl erstellt
#region Create automation account
New-AzAutomationAccount -ResourceGroupName $ResourceGroupName -Name $AutomationAccountName -Location $Location
#endregion
Für die Authentifizierung von Azure Automation an Azure erstellt das Skript ein selbst signiertes Zertifikat und speichert es im Kontext des aktuell angemeldeten Benutzer.
#region Create certificate for Azure Automation Run As Account
$CertificateName = $AutomationAccountName + $CertificateAssetName
$param = @{
"DnsName" = $certificateName
"CertStoreLocation" = "cert:\CurrentUser\My"
"KeyExportPolicy" = "Exportable"
"Provider" = "Microsoft Enhanced RSA and AES Cryptographic Provider"
"NotAfter" = (Get-Date).AddMonths($selfSignedCertNoOfMonthsUntilExpired)
"HashAlgorithm" = "SHA256"
}
$Cert = New-SelfSignedCertificate @param
#endregion
Dieses Zertifikat wird als PFX (Private + Public Key) exportiert.
#region Export certificate to temp folder
$selfSignedCertPlainPassword = $PfxPass
$CertPassword = ConvertTo-SecureString $selfSignedCertPlainPassword -AsPlainText -Force
$PfxCertPath = Join-Path $env:TEMP ($CertificateName + ".pfx")
Export-PfxCertificate -Cert ("Cert:\CurrentUser\my\" + $Cert.Thumbprint) -FilePath $PfxCertPath -Password $CertPassword -Force | Write-Verbose
#endregion
Der public Teil des Zertifikats wird als Teil einer Entra ID (Azure AD) Application Credential in der Applikation “Let’s Encrypt Certificate Automation” für die Authentifizierung hinterlegt.
#region Create Application Credential to use for authentication of RunAs Account
$PfxCert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($PfxCertPath, $selfSignedCertPlainPassword)
$param = @{
"ApplicationId" = $application.ApplicationId
"CertValue" = ( [System.Convert]::ToBase64String($PfxCert.GetRawCertData()) )
"StartDate" = $PfxCert.NotBefore
"EndDate" = $PfxCert.NotAfter
}
$applicationCredential = New-AzADAppCredential @param
#endregion
Jetzt wird der private Schlüssel als Automation Certificate im Azure Automation Account hinterlegt
#region Add certificate to automation account
$param = @{
"ResourceGroupName" = $ResourceGroupName
"AutomationAccountName" = $AutomationAccountName
"Name" = $CertificateAssetName
"Path" = $PfxCertPath
"Password" = $CertPassword
"Exportable" = $false
}
$AutomationCertificate = New-AzAutomationCertificate @param
#endregion
Mit diesem Zertifikat kann sich Azure Automation gegenüber Azure authentifizieren
Für die vereinfachte Anmeldung innerhalb der Runbooks wird eine Azure Automation Connection erstellt. Diese enthält alle notwendigen Informationen für die Anmeldung.
Das sind die Application Id, die TenantId, der Zertifikats-Thumbprint und die SubscriptionId.
#region Add Run As Account Connection to automation account
$SubscriptionInformation = Get-AzContext | Select-Object -ExpandProperty Subscription
$ConnectionFieldValues = @{
"ApplicationId" = $application.ApplicationId
"TenantId" = $SubscriptionInformation.TenantId
"CertificateThumbprint" = $AutomationCertificate.Thumbprint
"SubscriptionId" = $SubscriptionInformation.SubscriptionId
}
$param = @{
"ResourceGroupName" = $ResourceGroupName
"AutomationAccountName" = $AutomationAccountName
"Name" = $ConnectionAssetName
"ConnectionTypeName" = $ConnectionTypeName
"ConnectionFieldValues" = $connectionFieldValues
}
New-AzAutomationConnection @param
#endregion
Die nächste Code Region, nicht hier abgebildet, installiert die notwendigen Module in den Azure Automation Account.
- Az.Accounts
- Az.Resources
- Az.Storage
- Posh-ACME
# Coderegion - Deploy the necessary module, this will take a while
Wenn gewünscht kann der folgende Code in ein Runbook kopiert werden und damit der Verbindungsaufbau und die Modulverfügbar geprüft werden.
$connection = Get-AutomationConnection -Name 'AzureRunAsConnection'
Connect-AzAccount -ServicePrincipal -Tenant $connection.TenantID -ApplicationId $connection.ApplicationID -CertificateThumbprint $connection.CertificateThumbprint
Get-AzResource
Get-Module -ListAvailable
Damit die Runbooks später auch die definierten Standardwerte nutzen können werden diese in Azure Automation Variablen gespeichert.
- PAServer
Dieser Wert definiert welche Let’s Encrypt Umgebung genutzt werden soll. Der Standard ist die Staging Umgebung (LE_STAGE)
Wenn gültige Zertifikate ausgestellt werden sollen, muss dieser Wert auf “LE_PROD” geändert werden! - ACMEContact
Der definierte Standard E-Mail Kontakt - StorageContainerSASToken
Der verschlüsselte Wert für den Zugriff auf den Storage Account - BlobStorageName
Der Name des Blob Storage - PfxPass
Das verschlüsselte Passwort für die PFX Dateien - WriteLock
Standard ist “$false”. Diese Variable verhindert das mehr als ein Runbook schreibenden Zugriff auf die Konfigurationsdaten erhalten.
# Coderegion - Set variables
Die letzte Code Region kopiert alle Runbooks aus dem Unterordner “runbooks” in den Azure Automation Account. Unbedingt darauf achten das die PowerShell Session im richtigen Ordner ist.
#region Deploy Runbooks to Azure Automation account
$Runbooks = Get-ChildItem .\runbooks -Filter *.ps1
foreach ($Runbook in $Runbooks) {
$param = @{
"Path" = $Runbook.FullName
"Name" = $Runbook.BaseName
"Type" = "PowerShell"
"Published" = $true
"ResourceGroupName" = $ResourceGroupName
"AutomationAccountName" = $AutomationAccountName
}
Import-AzAutomationRunbook @param
}
#endregion
Im nächsten Teil dieser Blogreihe werden die zwei Runbooks “New-LetsEncryptCertificate” und “Update-LetsEncryptCertificates” besprochen.