Blog

Erstellung eines Microsoft Teams-Bots mit privatem Netzwerk in Azure (auf Basis von Terraform)

Victor de Baare

Victor de Baare

Aktualisiert September 15, 2026
9 Minuten

Dieser Artikel ist Teil der XPRT. Magazin Nr. 21


Microsoft Teams-Bots beginnen oft als kleine Integration: ein öffentlicher Endpunkt, ein gemeinsam genutztes Geheimnis – und schon sind Sie fertig. In Unternehmensumgebungen kann diese Einfachheit jedoch zu einem Problem werden. Möglicherweise gelten bei Ihnen Anforderungen hinsichtlich Netzwerkisolierung, Identitätsnutzung und Nachvollziehbarkeit, und plötzlich ist es nicht mehr akzeptabel, den Bot „einfach ins Internet zu stellen“.

Dieser Artikel bietet eine Schritt-für-Schritt-Anleitung zu einer „Private-First“-Architektur für einen Teams-Bot auf Azure. Die Bot-Laufzeitumgebung wird auf Azure Container Apps ausgeführt, der eingehende Datenverkehr wird über Application Gateway (WAF v2) zentralisiert, der ausgehende Datenverkehr wird durch die Azure-Firewall gesteuert, Geheimnisse werden in Azure Key Vault gespeichert, und der Zugriff auf Azure OpenAI erfolgt privat, wobei der öffentliche Netzwerkzugriff deaktiviert ist. Die gesamte Plattform wird mit Terraform bereitgestellt, und der Bot selbst nutzt Semantic Kernel, sodass ein einziger Bot mehrere Befehle unterstützen kann, ohne dass es zu einer „Bot-Flut“ kommt.

Warum private Netzwerke für einen Teams-Bot wichtig sind

Ein Teams-Bot mag wie eine Erweiterung im SaaS-Stil aussehen, doch aus Sicht der Plattform handelt es sich dennoch um eine Backend-Workload. Er empfängt eingehenden Datenverkehr, ruft andere Dienste auf, verarbeitet Daten, die unter Umständen sensibel sind, und benötigt möglicherweise Anmeldedaten oder Tokens, um seine Aufgaben zu erfüllen.

Private Netzwerke bieten einige praktische Vorteile. Sie verringern die Angriffsfläche, indem sie eine direkte öffentliche Exposition der Laufzeitumgebung vermeiden. Es zentralisiert die Überprüfung und das Routing des Datenverkehrs, sodass Sie Richtlinien an einer einzigen Stelle anwenden können. Es unterstützt Compliance-Maßnahmen, indem es identitätsbasierte Kontrollen ergänzt. Zudem macht es die Konnektivität vorhersehbarer, da die erforderlichen Endpunkte explizit festgelegt und dokumentiert werden, anstatt dass „alles, was der Code erreichen kann“, als Ziel gilt.

Ein Überblick über die Architektur auf hoher Ebene

Der Anforderungspfad beginnt in Microsoft Teams und verläuft über den Azure Bot Service. Der Azure Bot Service ist mit einer benutzerzugewiesenen verwalteten Identität konfiguriert und verweist seinen Messaging-Endpunkt auf Application Gateway. Application Gateway ist der einzige öffentliche Einstiegspunkt; es beendet die TLS-Verbindung und leitet den Datenverkehr an den Bot weiter, der in Azure Container Apps ausgeführt wird.

Wenn der Bot externe Dienste aufrufen muss, erfolgt dies über die Azure-Firewall. Hier wird die ausgehende Datenübertragung gesteuert: Sie gestatten dem Bot ausdrücklich den Zugriff auf die Endpunkte, die er für die Bot Framework-Authentifizierung und die Nachrichtenübermittlung benötigt, und verhindern gleichzeitig versehentliche oder unerwünschte Zugriffe auf das Internet.

Für KI-Antworten greift der Bot auf Azure OpenAI zurück. In diesem Konzept ist der öffentliche Netzwerkzugriff für Azure OpenAI deaktiviert, sodass der Zugriff ausschließlich über ein privates Netzwerk erfolgt. Geheimnisse und Konfigurationsdaten werden in Azure Key Vault gespeichert, und die Dienste authentifizieren sich bei Azure-Ressourcen mithilfe verwalteter Identitäten anstelle von gespeicherten Geheimnissen.


Identität an erster Stelle: vom Benutzer zugewiesene verwaltete Identitäten

Das Rückgrat der Konfiguration bildet eine vom Benutzer zugewiesene verwaltete Identität, die Sie mehreren Diensten zuordnen können. Dadurch bleibt das Vertrauensmodell einfach. Anstatt Geheimnisse über Pipelines und App-Einstellungen zu verteilen, stellt Azure zur Laufzeit Token aus, und Azure RBAC steuert, auf welche Ressourcen der Bot zugreifen darf.

In Terraform erstellen Sie in der Regel die Identität und weisen ihr die minimal erforderlichen Berechtigungen zu. Wenn der Bot beispielsweise Geheimnisse aus dem Key Vault ausliest, kann ihm für diesen Vault die Rolle „ Key Vault Secrets User “ zugewiesen werden.

Der Azure Bot Service unterstützt die Verwendung einer benutzerzugewiesenen verwalteten Identität anstelle einer App-Registrierung mit einem Client-Secret. Diese Entscheidung ist von Bedeutung, da dadurch die Weitergabe von Geheimnissen aus Ihrer Bereitstellung entfällt

data "azurerm_client_config" "current" {}

resource "azurerm_key_vault" "main" {
  name                = "kv-bot-nprd-001"
  location            = "westeurope"
  resource_group_name = "rg-bot-nprd-001"
  tenant_id           = data.azurerm_client_config.current.tenant_id
  sku_name = "standard"
  enable_rbac_authorization = true
  purge_protection_enabled   = true
  soft_delete_retention_days = 7
}

resource "azurerm_user_assigned_identity" "identity" {
  name                = "uai-bot-nprd-001"
  resource_group_name = "rg-bot-nprd-001"
  location            = "westeurope"
}

resource "azurerm_role_assignment" "kv_secrets_user" {
  scope                = azurerm_key_vault.main.id
  role_definition_name = "Key Vault Secrets User"
  principal_id         = azurerm_user_assigned_identity.identity.principal_id
}

resource "azurerm_bot_service_azure_bot" "main" {
  name                    = "bot-command-nprd-001"
  location                = "global"
  microsoft_app_type      = "UserAssignedMSI"
  microsoft_app_id        = azurerm_user_assigned_identity.identity.client_id
  microsoft_app_msi_id    = azurerm_user_assigned_identity.identity.id
  microsoft_app_tenant_id = data.azurerm_client_config.current.tenant_id
  endpoint                = "https://appgw-commandbot-001-frontend.commandbot.azurewebsites.net/api/messages"
}

Ein wichtiges Detail bei der Netzwerkkonfiguration ist der Endpunkt. Der Endpunkt des Bots sollte auf den HTTP-Listener des Application Gateways verweisen und nicht direkt auf die Container-App. Dadurch bleibt die öffentlich zugängliche Oberfläche klein, und das Gateway kann Ihre Ingress-Kontrollen durchsetzen. Die Container-App ist nur über das Gateway erreichbar, und das Gateway ist die einzige Komponente mit einer öffentlichen IP-Adresse.

Ausführen des Bots auf Azure Container Apps

Um den Bot in Azure Container Apps auszuführen, benötigen wir eine saubere Methode, um Images abzurufen und die Konfiguration zu laden. Ein gängiges Verfahren besteht darin, Azure Container Apps eine benutzerzugewiesene Identität zuzuweisen, die sowohl aus der Azure Container Registry abrufen als auch aus Key Vault lesen kann, sodass die Laufzeitumgebung niemals statische Anmeldeinformationen benötigt.

Damit der Bot eingehende Tokens korrekt validieren kann, benötigt die Laufzeitumgebung zudem einige Konfigurationswerte. In einer ASP.NET-Anwendung werden diese in der Regel als „ appsettings “-Schlüssel angegeben, in Container-Apps legen Sie sie jedoch als Umgebungsvariablen mit doppelten Unterstrichen fest (__). Die unten aufgeführten Werte verknüpfen die Token-Validierung wieder mit der dem Bot zugewiesenen verwalteten Identität und dem Mandanten – genau das ist gewünscht, wenn Sie den Bot-Dienst im Modus „ UserAssignedMSI “ ausführen.


resource "azurerm_container_app" "support_bot" {
  name                         = "ca-commandbot-nprd-001"
  resource_group_name          = "rg-bot-nprd-001"
  container_app_environment_id = var.container_app_environment_id
  revision_mode                = "Single"

  registry {
    server   = azurerm_container_registry.main.login_server
    identity = azurerm_user_assigned_identity.containerregistry.id
  }

  identity {
    type = "UserAssigned"
    identity_ids = [
      azurerm_user_assigned_identity.containerregistry.id,
      azurerm_user_assigned_identity.identity.id
    ]
  }

  ....

  template {
    container {
      env {
        name  = "Connections__BotServiceConnection__Settings__ClientId"
        value = azurerm_user_assigned_identity.identity.client_id
      }

      env {
        name  = "Connections__BotServiceConnection__Settings__TenantId"
        value = var.tenant_id
      }

      env {
        name  = "TokenValidation__Audiences__0"
        value = azurerm_user_assigned_identity.identity.client_id
      }

      env {
        name  = "TokenValidation__TenantId"
        value = var.tenant_id
      }
    }
  }
}

Application Gateway als einziger Eingangs-Gateway

Application Gateway ist der Layer-7-Reverse-Proxy (HTTP/HTTPS) und Lastverteiler von Azure. Er wird vor Ihrer Workload bereitgestellt, beendet die TLS-Verbindung, wendet Routing-Regeln (host- bzw. pfadbasiert) an und leitet die Anfrage anschließend an ein Backend-Ziel weiter. Indem Sie Application Gateway vor dem Bot platzieren, stellen Sie sicher, dass der gesamte eingehende Datenverkehr über einen einzigen, kontrollierten Einstiegspunkt läuft. Auf diese Weise können Sie TLS erzwingen, WAF-Regeln anwenden und einen klaren Prüfpfad erstellen.

resource "azurerm_public_ip" "appgw" {
  name                = "pip-appgw-bot-${var.environment}-001"
  location            = var.location
  resource_group_name = var.resource_group_name

  allocation_method = "Static"
  sku               = "Standard"
}

resource "azurerm_application_gateway" "main" {
  name                = "agw-bot-${var.environment}-001"
  location            = var.location
  resource_group_name = var.resource_group_name

  sku {
    name     = "WAF_v2"
    tier     = "WAF_v2"
    capacity = 2
  }

  # Must be a dedicated subnet (commonly named "AppGatewaySubnet")
  gateway_ip_configuration {
    name      = "appgw-ipcfg"
    subnet_id = "example-subnet-id"
  }

  frontend_port {
    name = "https-443"
    port = 443
  }

  frontend_ip_configuration {
    name                 = "public-frontend"
    public_ip_address_id = azurerm_public_ip.appgw.id
  }

  ssl_certificate {
    name     = "listener-cert"
    data     = var.ssl_cert_pfx_base64
    password = var.ssl_cert_password
  }

  http_listener {
    name                           = "https-listener"
    frontend_ip_configuration_name = "public-frontend"
    frontend_port_name             = "https-443"
    protocol                       = "Https"
    ssl_certificate_name           = "listener-cert"

    # This should match the public hostname you publish for the bot endpoint
    # (the same host used in the Azure Bot Service endpoint URL)
    host_name = var.bot_public_hostname
  }

  backend_address_pool {
    name  = "bot-backend-pool"
    fqdns = ["containerapps.commandbot.azurecontainerapps.io"]
  }

  backend_http_settings {
    name                                = "https-backend-settings"
    protocol                            = "Https"
    port                                = 443
    request_timeout                     = 30
    pick_host_name_from_backend_address = true
  }
}

Das Ergebnis ist eindeutig: Auch wenn das Gesamtsystem eingehende Anrufe aus Teams unterstützt, ist die Bot-Laufzeitumgebung selbst nicht direkt zugänglich.

Azure Firewall zur Steuerung des ausgehenden Datenverkehrs

Bei den Ausgehenden-Beschränkungen zeigt sich, wie Unternehmensnetzwerke in der Praxis funktionieren. Der Bot muss eine kleine Anzahl von Endpunkten erreichen, damit die Bot Framework-Abläufe funktionieren, und er muss möglicherweise mit anderen Azure-Diensten kommunizieren. Indem Sie den ausgehenden Datenverkehr über die Azure Firewall leiten, können Sie explizite Regeln festlegen und überprüfen, was erreichbar ist und was nicht.

Für den Datenverkehr des Bot Frameworks müssen Sie in der Regel Endpunkte wie login.botframework.com, token.botframework.com und smba.trafficmanager.net zulassen. Je nach Ihren genauen Anforderungen an das Routing und die Steuerungsebene müssen Sie möglicherweise auch die Container-Apps-Domäne (z. B. *.azurecontainerapps.io) zulassen.

Azure OpenAI mit deaktiviertem öffentlichen Netzwerkzugang

Wenn Ihr Bot Azure OpenAI nutzt, ist der private Zugriff häufig die „zwingende Voraussetzung“, die die gesamte Architektur bestimmt. In Terraform können Sie das Konto mit deaktiviertem öffentlichem Netzwerkzugang bereitstellen und die Authentifizierung über eine verwaltete Identität vornehmen. Neben der OpenAI-Ressource benötigen wir außerdem eine Bereitstellung für das spezifische Modell, das wir verwenden möchten, in diesem Fall gpt-4o-mini. Mit dieser Konfiguration verbleibt der gesamte Datenverkehr zwischen dem Bot und Azure OpenAI im Microsoft-Backbone und durchläuft zu keinem Zeitpunkt das öffentliche Internet.


resource "azurerm_cognitive_account" "openai" { name = "openai-ccoe-bot-${var.environment}-swe-001" kind = "OpenAI" sku_name = "S0" public_network_access_enabled = false custom_subdomain_name = "openai-ccoe-bot-${var.environment}-001" identity { type = "UserAssigned" identity_ids = [azurerm_user_assigned_identity.this.id] } } resource "azurerm_cognitive_deployment" "gpt_4o_mini" { name = "gpt-4o-mini" model { format = "OpenAI" name = "gpt-4o-mini" version = "2024-07-18" } sku { name = "GlobalStandard" capacity = 12 } }

Ein Bot, viele Befehle mit Semantic Kernel

Semantic Kernel bietet eine Orchestrierungsschicht, die es erleichtert, den Bot im Laufe der Zeit zu erweitern. Anstatt für jede Funktion einen neuen Bot bereitzustellen, können Sie einen einzigen Bot beibehalten und Befehle im Rahmen eines einheitlichen Sicherheitsmodells hinzufügen. In einer realen Umgebung beginnen Sie in der Regel mit etwas wie „ help “ und einem kleinen Betriebsbefehl wie „ Update Members “ und bauen das System von dort aus weiter aus, während der Bot immer mehr Aufgaben übernimmt.

Die Bot-Laufzeitumgebung in C#

Auf der Anwendungsseite handelt es sich bei dem Bot um eine gewöhnliche ASP.NET-Anwendung, die den Bot Framework-Endpunkt bereitstellt und den Semantic Kernel integriert. Um die Anwendung „plattformfreundlich“ zu halten, kommt es in der Regel auf langweilige, aber wichtige Details an: saubere Abhängigkeitsinjektion, Zustandsprüfungen und klare Abgrenzungen zwischen Orchestrierung und Integrationen.

builder.Services.AddKernel();

builder.Services.AddAzureOpenAIChatCompletion(
   deploymentName: config.Azure.OpenAIDeploymentName,
   endpoint: config.Azure.OpenAIEndpoint,
   apiKey: config.Azure.OpenAIApiKey
);

app.MapPost("/api/messages", async (
    HttpRequest request,
    HttpResponse response,
    IAgentHttpAdapter adapter,
    IAgent agent,
    CancellationToken cancellationToken) =>
{
    await adapter.ProcessAsync(request, response, agent, cancellationToken);
});

builder.Services.AddHealthChecks();
app.MapHealthChecks("/health");

Zusammenfassung

Diese Architektur ist komplexer als ein öffentlicher Endpunkt und ein Geheimnis, doch Sie setzen sie ein, um Risiken zu minimieren und die Plattform überprüfbar und kontrollierbar zu machen. Application Gateway bietet Ihnen einen einzigen, überprüfbaren Eingangsspunkt (TLS-Terminierung, Routing, WAF), anstatt die Laufzeitumgebung direkt offenzulegen. Azure Firewall macht den ausgehenden Datenverkehr explizit, sodass der Bot nur die Endpunkte erreichen kann, die er tatsächlich benötigt. Verwaltete Identitäten und Key Vault beseitigen die unkontrollierte Verbreitung von Geheimnissen und ermöglichen Ihnen die Zugriffskontrolle mittels RBAC. Der private Azure OpenAI-Zugang hält den KI-Datenverkehr vom öffentlichen Internet fern und unterstützt strengere Compliance-Anforderungen. Terraform bindet alle Komponenten zusammen, sodass der Endzustand reproduzierbar, überprüfbar und über alle Umgebungen hinweg konsistent ist.

GitHub-Repository

Verfasst von

Victor de Baare

Victor de Baare is a developer with a deep passion for building systems that make work more efficient and straightforward. He thrives under pressure, persistently working towards his goals regardless of the challenge. He finds joy in whisky, food, gaming, and board games. He continues to inspire and innovate, making significant contributions to technology and efficiency.

Contact

Let’s discuss how we can support your journey.