Skip to content
• 4 min read

Acmebot härten für ISO 27001 & NIS2: Zero-Trust-Zertifikate in Azure

Basierend auf einem realen Betrieb: der Übergang eines Acmebot-Deploys zu einer vollständig netzwerkisolierten Zero-Trust-Architektur, mitgebaut für ISO 27001 und NIS2-Anforderungen.

Let’s-Encrypt-Zertifikate mit Azure Key Vault zu automatisieren ist ein gelöstes Problem. Tools wie Azure Acmebot machen den Deploy lächerlich einfach.

Das Problem: Der Standard-Deploy ist auf Erreichbarkeit optimiert, nicht auf Isolation. Drei Komponenten öffnen eine öffentliche Angriffsfläche - und sobald du ein Audit gegen ISO 27001, KRITIS oder NIS2 fährst, klickst du dir genau damit die Findings zusammen, egal wie stark deine Auth am Ende ist.

Die drei offenen Flächen im Standard-Deploy

  1. Storage Account: Die Function App braucht einen Storage Account für Zustand und WebJobs. Default: nimmt Verkehr aus allen Netzwerken an.
  2. Key Vault: Dein Zertifikatsspeicher hängt per Default am öffentlichen Endpoint.
  3. Function App: Das Acmebot-Dashboard und der ACME-Webhook sind ohne Netzwerk-Restriktion öffentlich erreichbar.

Für jede ernsthafte Compliance-Liste muss dieses Modell komplett umgedreht werden. Das Zielprinzip: Default-Deny auf Netzwerk-Ebene, nicht nur auf Identity-Ebene.

Zielarchitektur

Das öffentliche Internet-Routing ersetzen wir durch das interne Azure-Backbone, über drei Stellschrauben:

  • VNet Integration: Die Function App wird in ein dediziertes, delegiertes Subnetz injiziert. Sämtlicher ausgehender Verkehr startet in deinem Virtual Network.
  • Azure Private Link: Storage Account und Key Vault bekommen private IPs in deinem VNet über Private Endpoints. Ihre öffentlichen Endpoints werden komplett abgeschaltet.
  • Private DNS Zones: Interne Auflösung sorgt dafür, dass die Function App *.blob.core.windows.net und *.vaultcore.azure.net über private IPs auflöst - nicht über öffentliche.

Schritt 1: VNet Integration

Die Function App braucht ein eigenes Subnetz mit Delegation zu Microsoft.Web/serverFarms. Ein /27-Präfix reicht für diese Workload und verschwendet keine Adressen.

resource "azurerm_subnet" "acmebot_integration" {
  name                 = "snet-acmebot-integration"
  resource_group_name  = azurerm_resource_group.app.name
  virtual_network_name = azurerm_virtual_network.core.name
  address_prefixes     = ["10.0.4.0/27"]

  delegation {
    name = "acmebot-fn-delegation"
    service_delegation {
      name    = "Microsoft.Web/serverFarms"
      actions = ["Microsoft.Network/virtualNetworks/subnets/action"]
    }
  }
}

Schritt 2: Private Endpoints

Jeder Endpoint braucht seinen eigenen Private Endpoint samt DNS-Zone-Verankerung. Das Pattern wiederholt sich dreimal - hier der Storage Account exemplarisch:

resource "azurerm_private_endpoint" "sa_blob" {
  name                = "pe-sa-blob"
  location            = azurerm_resource_group.app.location
  resource_group_name = azurerm_resource_group.app.name
  subnet_id           = azurerm_subnet.acmebot_integration.id

  private_service_connection {
    name                           = "psc-sa-blob"
    private_connection_resource_id = azurerm_storage_account.acmebot.id
    is_manual_connection           = false
    subresource_names              = ["blob"]
  }

  private_dns_zone_group {
    name                 = "pdns-blob"
    private_dns_zone_ids = [azurerm_private_dns_zone.blob.id]
  }
}

Schritt 3: Öffentliche Endpoints abschalten

Der wichtigste Schritt ist der destruktive: Nachdem alle Private Endpoints stehen, wird der öffentliche Zugriff aktiv abgeschaltet - nicht nur per Firewall-Rule kaschiert, sondern im Terraform-Code verboten. Damit ist die Öffnung weg, nicht bloß versteckt:

resource "azurerm_storage_account" "acmebot" {
  ...
  public_network_access_enabled = false
  min_tls_version               = "TLS1_2"
}

Für den Key Vault gilt dasselbe, plus ein Härtungs-Panel, das öffentliche Zugriffe per Default ausschließt:

resource "azurerm_key_vault" "acmebot" {
  ...
  public_network_access_enabled = false
  enable_rbac_authorization     = true
}

Schritt 4: Private DNS auf zusammenwachsen lassen

Damit die Function App die privaten IPs auch findet, braucht es die drei Zonen und ihre Verkettung ans Hub-VNet:

resource "azurerm_private_dns_zone" "blob" {
  name                = "privatelink.blob.core.windows.net"
  resource_group_name = azurerm_resource_group.app.name
}

resource "azurerm_private_dns_zone_virtual_network_link" "blob" {
  name                  = "link-hub"
  resource_group_name   = azurerm_resource_group.app.name
  private_dns_zone_name = azurerm_private_dns_zone.blob.name
  virtual_network_id    = azurerm_virtual_network.hub.id
}

Zonen, die du brauchst: privatelink.blob.core.windows.net, privatelink.vaultcore.azure.net und fürs App-Subnetze die Function-App-DNS-Platte.

Warum das fürs Audit zählt

Nach dem Umbau existiert für die Acmebot-Platte kein öffentlicher Endpoint mehr im Landschaftsbild. Ein Scanner (z.B. nmap oder ein Compliance-Scan wie bei einer ISO-27001-Begutachtung) findet schlicht nichts, das er attackieren könnte. Nicht „abgesichert”, sondern abwesend.

Zusammen mit den anderen NIS2-Säulen - Zero-Trust-Netzwerk, durchgängige Terraform-IaC (kein Click-Ops), Secrets via Key Vault statt in Code - erfüllst du genau die Netzwerk-Kontrollen, die in der NIS2-Artikel-21-Rechnung und in ISO-27001-Annex-A gefragt sind:

  • Privates Netz statt öffentlicher Endpoints → A.13/Netzwerksegmentierung
  • IaC statt manueller Änderungen → A.8/Configuration Management
  • Secrets im Vault, nie im Repo → A.10/Kryptographie

Verwandt: Wie du die vollständigen NIS2-Artikel-21-Pflichten mit Terraform baust, steht bereits im Blog - der Praxisteil ist in „NIS2 Artikel 21 in Azure implementieren” nachzulesen.

Share
DW

David Woitzik

Hybrid Cloud Engineer

Specializing in Azure, Terraform, and Zero-Trust network architecture. I publish the hardened templates and deep dives I wish existed when I needed them.

Was this helpful?

More like this in your inbox

New enterprise modules and deep dives — straight to your inbox. No spam.