Inhalt

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.

/zertifikatsmanagement-mit-azure-automation-und-lets-encrypt/images/DNSValidationSubzone-1024x278.png

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.

/zertifikatsmanagement-mit-azure-automation-und-lets-encrypt/images/DNSValidationSeperateZone-1024x288.png

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

/zertifikatsmanagement-mit-azure-automation-und-lets-encrypt/images/EnvironmentVariables.gif

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.

/zertifikatsmanagement-mit-azure-automation-und-lets-encrypt/images/DNSZone.gif

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

/zertifikatsmanagement-mit-azure-automation-und-lets-encrypt/images/AzADApplication.png

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

/zertifikatsmanagement-mit-azure-automation-und-lets-encrypt/images/AzAppCredential.png

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

/zertifikatsmanagement-mit-azure-automation-und-lets-encrypt/images/AzAutomationCert.png

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

/zertifikatsmanagement-mit-azure-automation-und-lets-encrypt/images/AzureRunAsConnection.png

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.