Ga naar inhoud

De templates testen

Elke module wordt op twee punten getest: dat hij werkt (hij deployt, en de resources gedragen zich zoals verwacht) en dat hij compliant is (de resources die hij aanmaakt schenden de Azure-policies van het platform niet). De tests draaien in een pipeline, parallel, in een eigen testsubscription.

Het testproces

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. De test deployt het voorbeeld van een module (examples/<technology>/<module>) in een eigen resource group.
  2. De tool van de technologie controleert dat de deployment werkt.
  3. Een PowerShell-script start een policy-evaluatie op de resource group, leest de compliancestatus van de policies die in de testsubscription zijn toegewezen, en laat de test mislukken als een policy non-compliant is.
  4. De resources van de test worden verwijderd. De gedeelde resources blijven (hieronder).

Compliance met Azure Policy

De templates moeten voldoen aan de baselines van het platform: de CIS Microsoft Azure Foundations Benchmark 2.0, ISO 27001:2013 en de Microsoft cloud security benchmark. In plaats van een tweede set regels te schrijven en te onderhouden in een code scanner, gebruiken de tests Azure Policy als enige bron van waarheid:

  • De policies van het platform worden toegewezen op het niveau van de testsubscription.
  • Elke template wordt gedeployd in een eigen resource group, zodat het resultaat bij één template hoort.
  • Na de deployment start de test een policy-evaluatie en leest daarna het compliance-resultaat.
Goed Onafhankelijk van de taal (Bicep of Terraform). De policies zijn de bron van waarheid. Weinig te onderhouden. Eenvoudig te implementeren.
Slecht Langzamer dan een statische scan. De test deployt de resources, evalueert de policies en verwijdert de resources, wat per module minuten kost.

Code scanners (statische analyse van Terraform en Bicep) zijn geprobeerd en voorlopig afgewezen: ze gaven false positives bij geneste modules en bij loops, misten sommige checks en hadden slechte Bicep-ondersteuning. Ze kunnen later worden toegevoegd als de testtijd een probleem wordt voor de ontwikkeling.

De policycontrole

Het script tests/Get-PolicyResult.ps1 doet de controle voor beide technologieën. Het neemt de naam van de resource group en een optionele lijst met te negeren policy reference ID's:

  1. Het controleert of de resource group bestaat en start er een policy-scan op (az policy state trigger-scan).
  2. Het toont de policy states met de status NonCompliant, met de naam van de assignment en het policy reference ID.
  3. Het haalt de genegeerde policies uit de telling en geeft het aantal non-compliant policies terug.

De test slaagt alleen als het aantal 0 is.

Genegeerde policies

Een test kan een lijst met te negeren policy reference ID's meegeven. De lijst blijft kort en elke entry heeft een commentaar met de reden in het testbestand. De redenen vallen in twee groepen:

  • De policy kan door de module niet worden geëvalueerd of opgelost: bijvoorbeeld de flow-log-policy voor network security groups (flow logs zijn op dat niveau niet meer mogelijk), de vulnerability assessment-policies voor registries en databases (ze hebben tijd of een subscriptionplan nodig) en de policies voor ongebruikte of niet-geauthenticeerde API's op de API Management Developer SKU (Azure maakt een Echo API aan).
  • De policy heeft meer tijd nodig dan een test kan wachten: guest configuration-policies op virtuele machines worden later geëvalueerd en de test kan ze niet triggeren.

Dezelfde bevindingen zijn voor afnemers gedocumenteerd in Bekende bevindingen. Een genegeerde policy toevoegen is een beslissing die gereviewd moet worden: leg die uit in het testbestand en in de modulenotities.

Tools per technologie

Technologie Tool Waarom
Terraform Terratest (een Go-bibliotheek) Volwassen en veel gebruikt. Draait tests parallel. Flexibel, en bruikbaar voor andere tests. Vraagt een beetje kennis van Go om de eerste teststructuur te bouwen.
Bicep Pester (PowerShell-testframework) Weinig moeite om de uitvoer vast te leggen en een testrapport te maken dat Azure DevOps kan lezen. Een script draait de tests parallel, want Pester heeft geen native parallellisme. Bekende zwakke punten: de totale testtijd in het rapport klopt niet, en de log toont pas uitvoer als alle tests klaar zijn.

Twee opties zijn niet gekozen: het native terraform test-framework (geen parallellisme, minder flexibel en minder volwassen) en een eigen PowerShell-loop met parallelle deployments voor Bicep (flexibel, maar veel werk om te bouwen en te onderhouden).

Hoe een test is opgebouwd

  • Terraform: één Go-test per module in tests/terraform/<module>_test.go, genaamd Test<ModuleName>. Hij draait in de fasen apply, policy validation en destroy. Hij gebruikt de backend van de state storage van de test met een key op basis van het pad van het voorbeeld, zodat elk voorbeeld zijn eigen state heeft. Gemeenschappelijke helpers staan in common.go.
  • Bicep: één Pester-bestand per module in tests/bicep, met de tests deploys en validates policies, en een cleanup die de resource group verwijdert. Een runnerscript start alle testbestanden parallel (één threadjob per bestand), voegt de resultaten samen en schrijft een JUnit-rapport.

Het scaffoldingscript maakt de Terraform-test aan vanuit een template wanneer je een module maakt (Pipelines en scripts). Kopieer voor Bicep een bestaand testbestand en pas het aan.

Gedeelde resources tijdens tests

De meeste modules hebben dezelfde afhankelijkheden nodig: een virtual network en subnet, een Log Analytics workspace, een Key Vault, een storage account. Die allemaal voor elke test deployen zou de tests traag en de voorbeelden complex maken.

In plaats daarvan worden de afhankelijkheden één keer aangemaakt en hergebruikt elke test ze:

  1. De gedeelde resources zijn in Terraform gedefinieerd in de map dependencies/. Het storage account voor de Terraform state is de ene resource die niet vanaf daar wordt aangemaakt: een klein Bicep-template maakt het aan.
  2. Een pipeline maakt ze aan in zijn eerste fase, als ze nog niet bestaan.
  3. De pipeline geeft informatie over de gedeelde resources terug als JSON-object en geeft die door aan de volgende jobs.
  4. Voordat een module wordt gedeployd, schrijft een job de JSON naar een bestand met de naam shared_resources.json. Het Terraform-voorbeeld leest het met jsondecode(file(...)), het Bicep-voorbeeld met loadJsonContent('shared_resources.json').
Goed Korte tests. De afhankelijkheden worden niet in elke integratietest getest: de module wel. Terraform- en Bicep-tests hergebruiken dezelfde resources. Alle afhankelijkheden worden op één plek gewijzigd. Minder oorzaken voor een mislukte test.
Slecht Complexer. De editor kan de waarden van de gedeelde resources niet automatisch aanvullen.

Het alternatief (elke module brengt zijn eigen afhankelijkheden mee) houdt modules gescheiden, maar maakt veel kopieën van dezelfde resources, maakt unieke namen een probleem en duurt een stuk langer.

Wat de gedeelde resources zijn

Gedeelde resource Gebruikt voor
Resource group, virtual network en subnet, met een network security group Private endpoints en netwerkinterfaces van VM's. Het netwerk is gepeerd met het netwerk van de build agents, zodat de agents private resources kunnen bereiken.
Private DNS-zones voor de gangbare private link-services (blob, table, queue, file, PostgreSQL, container registry, Key Vault, SQL, Service Bus, App Configuration), gelinkt aan het netwerk van de agents Naamresolutie voor de private endpoints van de voorbeelden
User-assigned identity, met de rollen die ze nodig heeft op de Key Vault Customer-managed keys en andere identity-tests
Key Vault, Log Analytics workspace, storage account, container registry Afhankelijkheden van veel modules
Recovery Services vault met een dagelijkse VM-back-uppolicy Tests van virtuele machines
Een gegenereerd SSH-sleutelpaar, opgeslagen in de Key Vault Tests van Linux virtuele machines

De identities en het netwerk van de agents zijn die van het beherende team. Je eigen testomgeving heeft er eigen nodig.

De tests draaien

  • Normaal: gebruik de CI-pipeline van de technologie. Die draait bij pull requests die die technologie wijzigen, en elke week (Pipelines en scripts). Een pull request draait de tests van de modules die hij wijzigt, en alle tests als hij bestanden buiten de map met modules, voorbeelden en tests wijzigt. Na de run wordt alles wat tijdens de tests is aangemaakt verwijderd, behalve de gedeelde resources.
  • Eén test: start de CI-pipeline met de hand en geef de naam van de test (Terraform) of van het testbestand (Bicep).
  • Handmatig: een pipeline kan apply of destroy draaien voor één pad van de repository (bijvoorbeeld examples/bicep/<module>), met een optie om daarna de policy-scan te draaien. Gebruik dit om een template uit te proberen terwijl je hem bouwt. Het draait op private build agents.
  • Lokaal: de development container maakt de gedeelde resources aan met Terraform en schrijft shared_resources.json voor beide testmappen, zodat je een test vanaf je eigen machine tegen een testsubscription kunt draaien.