Zum Inhalt

Die Templates testen

Jedes Modul wird auf zwei Dinge getestet: dass es funktioniert (es deployt, und die Ressourcen verhalten sich wie erwartet) und dass es konform ist (die Ressourcen, die es anlegt, verletzen die Azure-Policies der Plattform nicht). Die Tests laufen in einer pipeline, parallel, in einer eigenen Test-subscription.

Der Testablauf

flowchart TD
    a1["Terraform: Terratest"] --> a2["Integration test: deploy the example"]
    b1["Bicep: Pester"] --> b2["Integration test: deploy the example"]
    a2 --> c["Azure Policy scan of the resource group"]
    b2 --> c
    c --> d{"Any non-compliant policy?"}
    d -->|yes| e["Test fails"]
    d -->|no| f["Test passes"]
    f --> g["Resources are removed. Shared resources stay."]
  1. Der Test deployt das Beispiel eines Moduls (examples/<technology>/<module>) in eine eigene resource group.
  2. Das Werkzeug der Technologie prüft, dass das Deployment funktioniert.
  3. Ein PowerShell-Skript löst eine Policy-Auswertung auf der resource group aus, liest den Compliance-Status der Policies, die in der Test-subscription zugewiesen sind, und lässt den Test fehlschlagen, wenn eine Policy nicht konform ist.
  4. Die Ressourcen des Tests werden entfernt. Die shared resources bleiben (unten).

Compliance mit Azure Policy

Die Templates müssen die Baselines der Plattform erfüllen: den CIS Microsoft Azure Foundations Benchmark 2.0, ISO 27001:2013 und den Microsoft cloud security benchmark. Statt in einem Code-Scanner einen zweiten Regelsatz zu schreiben und zu pflegen, nutzen die Tests Azure Policy als Single Source of Truth:

  • Die Policies der Plattform werden auf Ebene der Test-subscription zugewiesen.
  • Jedes Template wird in einer eigenen resource group deployt, damit das Ergebnis zu genau einem Template gehört.
  • Nach dem Deployment startet der Test eine Policy-Auswertung und liest dann das Compliance-Ergebnis.
Gut Unabhängig von der Sprache (Bicep oder Terraform). Die Policies sind die Source of Truth. Wenig zu pflegen. Einfach umzusetzen.
Schlecht Langsamer als ein statischer Scan. Der Test deployt die Ressourcen, wertet die Policies aus und entfernt die Ressourcen wieder, was pro Modul Minuten dauert.

Code-Scanner (statische Analyse von Terraform und Bicep) wurden ausprobiert und vorerst verworfen: Sie lieferten false positives bei verschachtelten Modulen und Schleifen, übersahen einige Prüfungen und unterstützten Bicep schlecht. Sie können später ergänzt werden, falls die Testdauer für die Entwicklung zum Problem wird.

Die Policy-Prüfung

Das Skript tests/Get-PolicyResult.ps1 führt die Prüfung für beide Technologien aus. Es nimmt den Namen der resource group und eine optionale Liste von policy reference IDs, die ignoriert werden sollen:

  1. Es prüft, dass die resource group existiert, und startet einen Policy-Scan darauf (az policy state trigger-scan).
  2. Es listet die Policy-Zustände mit dem Status NonCompliant auf, mit dem Namen der Zuweisung und der policy reference ID.
  3. Es zieht die ignorierten Policies von der Zahl ab und gibt die Anzahl der nicht konformen Policies zurück.

Der Test besteht nur, wenn die Zahl 0 ist.

Ignorierte Policies

Ein Test kann eine Liste von policy reference IDs übergeben, die ignoriert werden. Die Liste bleibt kurz, und jeder Eintrag hat im Testfile einen Kommentar mit der Begründung. Die Gründe fallen in zwei Gruppen:

  • Die Policy kann vom Modul nicht ausgewertet oder behoben werden: zum Beispiel die flow-log-Policy für Network Security Groups (flow logs sind auf dieser Ebene nicht mehr möglich), die Vulnerability-Assessment-Policies für Registries und Datenbanken (sie brauchen Zeit oder einen subscription-Plan) und die Policies für ungenutzte oder nicht authentifizierte APIs auf der API-Management-Developer-SKU (Azure legt eine Echo API an).
  • Die Policy braucht mehr Zeit, als ein Test warten kann: Guest-Configuration-Policies auf virtuellen Maschinen werden später ausgewertet, und der Test kann sie nicht auslösen.

Dieselben Befunde sind für Nutzer in Bekannte Befunde dokumentiert. Eine ignorierte Policy hinzuzufügen ist eine Entscheidung, die geprüft werden muss: Begründe sie im Testfile und in den Modulhinweisen.

Werkzeuge pro Technologie

Technologie Werkzeug Warum
Terraform Terratest (eine Go-Bibliothek) Ausgereift und weit verbreitet. Führt Tests parallel aus. Flexibel und auch für andere Tests nutzbar. Erfordert etwas Go-Kenntnis, um die erste Teststruktur zu bauen.
Bicep Pester (PowerShell-Test-Framework) Wenig Aufwand, um die Ausgabe zu erfassen und einen Testbericht zu erstellen, den Azure DevOps lesen kann. Ein Skript führt die Tests parallel aus, weil Pester keine native Parallelität hat. Bekannte Schwächen: Die Gesamttestzeit im Bericht stimmt nicht, und das Log zeigt die Ausgabe erst, wenn alle Tests fertig sind.

Zwei Optionen wurden nicht gewählt: das native Framework terraform test (keine Parallelität, weniger flexibel und weniger ausgereift) und eine eigene PowerShell-Schleife mit parallelen Deployments für Bicep (flexibel, aber viel Aufwand zu bauen und zu pflegen).

So ist ein Test aufgebaut

  • Terraform: ein Go-Test pro Modul in tests/terraform/<module>_test.go, benannt Test<ModuleName>. Er läuft in den Phasen apply, policy validation und destroy. Er nutzt das Backend des Test-state-Storage mit einem Key, der auf dem Pfad des Beispiels basiert, sodass jedes Beispiel seinen eigenen state hat. Gemeinsame Helfer stehen in common.go.
  • Bicep: ein Pester-File pro Modul in tests/bicep, mit den Tests deploys und validates policies und einem Cleanup, der die resource group löscht. Ein Runner-Skript startet alle Testfiles parallel (ein thread job pro File), führt die Ergebnisse zusammen und schreibt einen JUnit-Bericht.

Das Scaffolding-Skript erzeugt den Terraform-Test aus einem Template, wenn du ein Modul anlegst (Pipelines und Skripte). Für Bicep kopierst du ein vorhandenes Testfile und passt es an.

Shared resources während der Tests

Die meisten Module brauchen dieselben Abhängigkeiten: ein virtuelles Netzwerk mit Subnet, einen Log-Analytics-workspace, einen Key Vault, ein Storage Account. Alle davon für jeden Test zu deployen, würde die Tests langsam und die Beispiele komplex machen.

Stattdessen werden die Abhängigkeiten einmal angelegt, und jeder Test nutzt sie wieder:

  1. Die shared resources sind in Terraform im Ordner dependencies/ definiert. Das Storage Account für den Terraform-state ist die einzige Ressource, die nicht von dort angelegt wird: Ein kleines Bicep-Template legt es an.
  2. Eine pipeline legt sie in ihrer ersten Phase an, falls sie noch nicht existieren.
  3. Die pipeline gibt Informationen über die shared resources als JSON-Objekt zurück und reicht sie an die folgenden Jobs weiter.
  4. Bevor ein Modul deployt wird, schreibt ein Job das JSON in eine Datei namens shared_resources.json. Das Terraform-Beispiel liest sie mit jsondecode(file(...)), das Bicep-Beispiel mit loadJsonContent('shared_resources.json').
Gut Kurze Tests. Die Abhängigkeiten werden nicht in jedem Integrationstest getestet: das Modul schon. Terraform- und Bicep-Tests nutzen dieselben Ressourcen. Alle Abhängigkeiten werden an einer Stelle geändert. Weniger Ursachen für einen fehlgeschlagenen Test.
Schlecht Komplexer. Der Editor kann die Werte der shared resources nicht automatisch vervollständigen.

Die Alternative (jedes Modul bringt seine eigenen Abhängigkeiten mit) hält die Module getrennt, erzeugt aber viele Kopien derselben Ressourcen, macht eindeutige Namen zum Problem und dauert deutlich länger.

Was die shared resources sind

Shared resource Verwendet für
Resource group, virtuelles Netzwerk und Subnet, mit einer Network Security Group Private endpoints und Netzwerkschnittstellen von VMs. Das Netzwerk ist mit dem Netzwerk der Build-Agents gepeert, damit die Agents private Ressourcen erreichen können.
Private DNS zones für die gängigen Private-Link-Services (blob, table, queue, file, PostgreSQL, Container Registry, Key Vault, SQL, Service Bus, App Configuration), verknüpft mit dem Netzwerk der Agents Namensauflösung für die private endpoints der Beispiele
User-assigned Identity, mit den Rollen, die sie auf dem Key Vault braucht Customer-managed keys und andere Identitätstests
Key Vault, Log-Analytics-workspace, Storage Account, Container Registry Abhängigkeiten vieler Module
Recovery Services vault mit einer täglichen VM-Backup-Policy Tests für virtuelle Maschinen
Ein generiertes SSH-Schlüsselpaar, im Key Vault gespeichert Tests für Linux-VMs

Die Identitäten und das Netzwerk der Agents gehören dem pflegenden Team. Deine eigene Testumgebung braucht eigene.

Die Tests ausführen

  • Normalerweise: nutze die CI-pipeline der Technologie. Sie läuft bei pull requests, die diese Technologie ändern, und jede Woche (Pipelines und Skripte). Ein pull request führt die Tests der Module aus, die er ändert, und alle Tests, wenn er Dateien außerhalb der Ordner für Module, Beispiele und Tests ändert. Nach dem Lauf wird alles entfernt, was während der Tests angelegt wurde, außer den shared resources.
  • Ein Test: Starte die CI-pipeline von Hand und gib den Namen des Tests (Terraform) oder des Testfiles (Bicep) an.
  • Manuell: Eine pipeline kann apply oder destroy für einen Pfad des Repositorys ausführen (zum Beispiel examples/bicep/<module>), mit der Option, danach den Policy-Scan auszuführen. Nutze sie, um ein Template auszuprobieren, während du es baust. Sie läuft auf privaten Build-Agents.
  • Lokal: Der Development Container legt die shared resources mit Terraform an und schreibt shared_resources.json für beide Testordner, sodass du einen Test von deinem eigenen Rechner aus gegen eine Test-subscription ausführen kannst.