Sicherer Zugriff auf Azure AI-Services
Die Azure AI-Dienste ermöglichen in erster Linie Entwicklern, eigene AI-Anwendungen und Copiloten zu entwickeln. Wie das konzeptionell funktioniert, zeige ich dir in diesem Beitrag und weiteren folgenden. Die Kenntnisse sind wichtig für die Zertifizierung zum Kurs Azure AI 102, „Microsoft Certified: Azure AI Engineer Associate“. Diesmal zeige ich dir, wie die du das Authentifizieren und Autorisieren der AI-Dienste aus deinem Anwendungs-Code möglichst sicher gestaltetest.
Alle bisher gezeigten Azure-AI-Dienste wie Vision, Language, Speech oder OpenAI verfügen über REST-Endpunkte, sowie über Client-SDK-Unterstützung für C# und Python. Du kannst Anwendungen entwickeln, die für den Zugriff auf diese Azure AI-Backend-Dienste beispielsweise die Zugriffsschlüssel des jeweiligen AI-Dienstes für die Autorisierung verwenden. Dies ist – auch wenn die Methode in diesem Artikel im Fokus steht, weil sie von allen AI-Diensten unterstützt wird und relativ einfach einzurichten ist – nicht die einzige und meist auch nicht in jedem Fall die Beste. Andere mögliche Verfahren sind:
Passende Schulungen
AI-102 – Designing and Implementing a Microsoft Azure AI Solution (AI-102T00)
Sicherer Zugriff auf Azure AI-Services
- Zugriffstoken: Einige (nicht alle) Azure AI-Dienste unterstützen die Authentifizierung mit Zugriffstoken. Diese Token können durch den Austausch eines Ressourcenschlüssels generiert werden und sind in der Regel für eine begrenzte Zeit gültig.
- Microsoft Entra ID: Diese Methode ermöglicht eine rollenbasierte Zugriffssteuerung (RBAC) und ist besonders nützlich für komplexere Szenarien, die eine granulare Kontrolle über den Zugriff erfordern. Du kannst dazu entweder system- oder benutzerseitig zugewiesene verwaltete Identitäten verwenden.
- OAuth 2.0: Mit OAuth 2.0 kannst du den Zugriff auf deine APIs durch das Verwenden von Token, welche von einem Identitätsanbieter wie Microsoft Entra ID generiert werden, autorisieren.
Das Verwenden von Zugriffsschlüsseln bedeutet jedoch, dass dein Anwendungscode den Schlüssel enthält. Eine andere Möglichkeit besteht darin, diesen Schlüssel in einer Umgebungsvariablen oder einer Konfigurationsdatei zu speichern. Ich habe dir bereits erklärt, wie du solche Informationen z. B. in einer Konfigurationsdatei „appsettings.json“ speichern kannst. Bei diesem Ansatz ist der Schlüssel jedoch anfällig für unbefugten Zugriff.
Ein besserer Ansatz bei der Entwicklung von Azure-AI-Anwendungen besteht darin, den Schlüssel sicher in einem Azure Key Vault (Schlüsseltresor) zu speichern. Der Teil deines Anwendungscodes, der die Schlüssel aus dem Key Vault abruft, kann gegenüber Entra ID als Dienstprinzipal dargestellt werden. Du kannst den Zugriff auf Schlüssel in einem Schlüsseltresor aber auch mit einer verwalteten Identität umsetzen, wenn dein Anwendungscode z. B. auf einer Azure-VM läuft, also auf einer Azure-Ressource, die verwaltetet Identitäten unterstürzt. Das kannst du dir vorstellen, wie ein Benutzerkonto, das von der Anwendung selbst verwendet wird.
Legst du Informationen wie Zugriffschlüssel, wie in vorangegangen Beitrag dieser kleinen Serie gezeigt, lediglich in einer Konfigurationsdatei ab, die Teil der Anwendung ist, besteht immer die Gefahr, das Informationen dieser Art unbeabsichtigt offengelegt werden, etwa wenn du deinen Quellcode auf einem GitHub-Repository hostest. Ich zeige daher in diesem Artikel, wie du sensible Anmeldeinformationen in einem Azure Schlüsseltresor ablegst und deine AI-Anwendung dann so konfigurierst, dass sie in der Lage ist, diese Informationen abzurufen.
Umgebung vorbereiten
Ich werde für dieses Beispiel VS Code als IDE verwenden. Falls noch nicht geschehen, klone dieses https://github.com/MicrosoftLearning/mslearn-ai-services GitHub-Repository von Microsoft für AI-Übungen auf deinen Arbeitsplatz und öffnen dann das Verzeichnis https://github.com/MicrosoftLearning/mslearn-ai-services/tree/main/Labfiles/02-ai-services-security in VS Code. Eventuell musst du dabei dem Installieren weiterer Dateien zustimmen, damit VS Code um C# -Code in diesem Repo unterstützt. Das sollte dann etwa so aussehen:
Außerdem benötigst du eine Azure-AI-Ressource vom Typ „Azure AI services multi-service account“.
Erinnere dich: beim Erstellen einer Azure-AI-Service-Ressource werden stets zwei Authentifizierungsschlüssel generiert. Du kannst diese wahlweise im Azure-Portal im Menü „Schlüssel und Endpunkt“ der betreffenden Ressource sehen und verwalten …
(hier findest du auch den Standort und den „http-Endpunkt“ der Ressource)
… oder mit Hilfe der Azure-Befehlszeilenschnittstelle (CLI). Möchtest du die Azure AI-Dienstschlüssel über die Azure-CLI abzurufen, öffnest du einfach in VS Code ein neues Terminal und gibst diesen Befehl ein:
az cognitiveservices account keys list --name <resourcename> --resource-group <resourcegroup>
Denke daran, dass du dich zuvor mit ..
az login
bei der Azure-CLI authentifizieren musst. Mit dem Kommandozeilenwerkzeug CURL kannst du deinen Azure-AI-Dienst sehr einfach über die REST-API testen. Du findest dazu im Ordner https://github.com/MicrosoftLearning/mslearn-ai-services/tree/main/Labfiles/02-ai-services-security zwei Dateien „rest-test.cmd“ und „rest-test.sh“. Du kannst dazu je nach Umgebung eine der beiden Dateien im Terminal öffnen und den CURL-Aufruf …
curl -X POST "<yourEndpoint>/language/:analyze-text?api-version=2023-04-01" -H "Content-Type: application/json" -H "Ocp-Apim-Subscription-Key: <your-key>" --data-ascii "{'analysisInput':{'documents':[{'id':1,'text':'Guten Tag'}]}, 'kind': 'LanguageDetection'}"
darin mit deinen Authentifizierungsdaten (<yourEndpoint> und <your-key>) ergänzen. Ersetze den Text bei ‚text‘ durch einen Text in der Sprache deiner Wahl. Der Azure-AI-Dienst sollte die verwendete Sprache zuverlässig erkennen.
Hast du die Vermutung, dass einer der Schlüssel kompromittiert wurde, kannst Du diesen entweder im Azure-Portal erneuen, indem du wahlweise auf „Key1 neu generieren“, bzw. „Key2 neu generieren“ klickst oder mit dem CLI-Aufruf:
az cognitiveservices account keys regenerate --name <resourceName> --resource-group <resourceGroup> --key-name key1
Hier musst du wieder den Namen deiner Azure-AI-Ressource und deiner Ressourcengruppe einsetzen sowie den Namen des zu erneuernden Schlüssels (key 1 oder key2). Der Aufruf gibt ein JSON-Dokument mit dem Inhalt beider Schlüssel zurück, also in diesem Fall den erneuerten „key1“ sowie den unveränderten „key2“.
Danach muss der o. g. CURL-Aufruf beim Starten der „rest-test.cmd“-Dateien scheitern, sofern du den neuen Schlüssel nicht auch darin ersetzt hast.
Beachte hierbei aber, dass dein API-Schlüssel im Klartext im CURL-Aufruf steckt. Dem wollen wir im nächsten Schritt Abhilfe schaffen, indem wir ihn in einem Azure-Schlüsseltresor legen und deinen Anwendungscode mit Hilfe eines Dienstprinzipals berechtigen, den Schlüssel aus dem Tresor abzurufen.
Azure Key Vault und Gemeinnis erstellen
Lege also jetzt im Azure-Portal einen neuen Schlüsseltresor an. Im ersten Tab „Grundlegende Einstellungen“ wählst du wie bei den meisten Azure-Ressourcen üblich dein Abonnement, deine Ressourcengruppe, vergibst einen Namen für deinen Schlüsseltresor, wählst den gewünschten Standort und einen Tarif. Für diesen Beitrag genügt „Standard“. Bei „Premium“ hättest du u. a. die Option, einen von Microsoft verwalteten, Hardware-gestützten Tresor (HSM) zu verwenden.
Wichtig ist nun der nächste Tab „Zugriffskonfiguration“. Während für die Berechtigung von Verwaltungsvorgängen (Tresor erstellen / löschen) grundsätzliche Azure-RBAC zum Einsatz komm, hast du für das Autorisieren von Datenvorgängen (Schlüssel erstellen, löschen, lesen, packen, entpacken, signieren etc.) grundsätzlich zwei Möglichkeiten, nämlich entweder RBAC-Rollen oder Tresorrichtlinien. Neuer, von Microsoft empfohlen (und daher „Default“) ist die Option „Rollenbasierte Azure-Zugriffssteuerung“. Wie verwenden für dieses Beispiel trotzdem Tresorrichtlinien, weil ich der Ansicht bin, dass die einzelnen Berechtigungen für Schlüssel-, Geheimnis, und Zertifikats-Berechtigungen hier besser zu erkennen sind. Du musst dich allerdings beim Erstellen des Tresors entscheiden. Das spätere Umstellen ist zwar möglich, aber aufwendig.
Wurde der Schlüsseltresor erstellt, kannst du zur Ressource navigieren und dort ein neues „Geheimnis“ erstellen. Klicke dazu im Abschnitt „Objekte“ auf „Geheimnisse“ und dort auf „+Generieren / Importieren“.
Schlüsseltresore unterstützen grundsätzlich die drei Objekttypen Schlüssel, Geheimnisse und Zertifikate. Aber Achtung: Unser API-Schlüssel für die Azure-AI-Ressource ist natürlich aus Sicht des Schlüsseltresors ein ganz gewöhnliches Geheimnis, also eine geheime Zeichenkette, wie dies auch bei einer Datenbank-Verbindungszeichenfolge oder einem Datenbank-Passwort der Fall wäre. „Schlüssel“ in einem Schlüsseltresor hingegen sind RSA-Keys für die asymetrische Verschlüsselung.
Lege im Dialog „Geheimnis erstellen“ einen „Namen“ und einen „Geheimniswert“ fest. Bei Geheimniswert trägt du den Inhalt des API-Schlüssels deiner Azure-AI-Ressource ein. Optional kann du ein Aktivierungsdatum und ein Ablaufdatum festlegen und das Geheimnis grundsätzlich aktivieren oder deaktivieren. Klicke dann auf „Erstellen“. Der Name ist zwar im Prinzip frei wählbar, wenn du aber im späteren Verlauf dieses Beispiels keine Anpassungen an der Programm-Datei vornehmen möchtest, muss der Name in diesen Fall „AI-Services-Key lauten.
Navigierst du anschließend zu deiner Objekt-Übersicht, findest du dort ein neues Geheimnis. Der Zustand sollte „Aktiviert“ sein.
Dienstprinzipal erstellen
Damit deine Anwendung auf das Geheimnis im Schlüsseltresor zuzugreifen kann, muss deine Anwendung ein Dienstprinzip verwenden, das Zugriff auf das Geheimnis hat. Wie verwenden erneut die Azure-CLI zum Erstellen des Dienstprinzipals und das Erstellen eine passenden Zugriffsrichtlinie im Schlüsseltresor. Folgendes Kommando erstellt des Dienstprinzipal. Achte darauf, dass du bei Azure angemeldet bist. Falls nicht, verwende dazu erneut „az login“. Setze in folgendes Kommando die ID deines Azure-Abonnements und den Namen der Ressourcengruppe ein. Den Namen für deinen Dienstprinzipal darfst du dir frei ausdenken hier „api://MySP
“.
az ad sp create-for-rbac -n "api://<spName>" --role owner --scopes subscriptions/<subscriptionId>/resourceGroups/<resourceGroup>
Als Ergebnis bekommst du ein JSON-Dokument mit „appId“, „displayName“, “password“ und „tenant“. Notiere dir unbedingt das Passwort. Dieses wirst du später benötigen und kann nicht rekonstruiert werden. Zwar entsteht mit Absetzen des Befehls auch eine neue App-Registrierung, welche du im Azure-Portal aufrufen kannst, dort kannst du aber lediglich die App-ID abrufen, nicht aber das Passwort.
Die zugehörige App-Registration sieht dann so aus:
Die „appId“ in der Befehlsausgabe oben, bzw. die „Anwendungs-ID“ in der App-Registrierung ist quasi dein „Dienstprinzipal“. Du kannst weitere Details zu deiner AppId auch jederzeit über die CLI ermitteln. Dazu gibst du ein ..
az ad sp show --id <appId>
du erhältst dann eine Ausgabe wie Folgende;
Du kannst die Werte gern mit der App-Registration im Azure-Portal vergleichen. Die „AppId“ entspricht dem Namen deines Dienstprinzipals (servicePrincipalName). Die „appOwnerOrganizationId“ entspricht der „Verzeichnis-ID“ in deiner App-Registration, also der ID deines EntraID-Mandanten. Jetzt musst du deinem neuen Dienstprinzipal die passende Berechtigung auf deinen Azure Key Vault geben, damit diese das Geheimnis aus dem Tresor abrufen darf. Das machst du entweder an der CLI mit …
az keyvault set-policy -n <keyVaultName> --object-id <objectId> --secret-permissions get list
(für “objectID” setzt du die ID deines Dienstprinzipals ein. Der Befehl gibt anschließend (get list) die gesetzten Berechtigungen für Zertifikate, Schlüssel und Geheimnisse für den gesamten Schlüsseltresor aus. Der unseren Dienstprinzipal betreffende Teil sieht etwa so aus:
Du kannst die Berechtigungen aber auch komfortabler im Azure-Portal ansehen oder sie dort setzen. Navigiere dazu in deinem Schlüsseltresor zu „Zugriffsrichtlinien“. Dort findest du unter „ANWENDUNG“ den Namen deines Dienstprinzipals und rechts davon die gesetzten Schlüssel-, Geheimnis- und Zertifikats-Berechtigungen.
Dienstprinzipal au der Anwendung nutzen
Jetzt sind alle Vorbereitungen getroffen, dass du deine Anwendungscode so konfigurieren kannst, den API-Schlüssel (also ein Geheimnis im Key Vault) aus dem Schlüsseltresor abzurufen. Navigiere dazu in VS Code zum Unterordner „keyvault_client“ je nach Umgebung (C# oder Python) unterhalb eines der Ordner „C-Sharp“ oder „Python“. Falls noch nicht geschehen, installiere jetzt ggf. noch die notwendigen Software-Pakete, um in VS Code auf Azure Key Vault, sowie die Text-Analytics-API zugreifen zu können. Unter C# gelingt das mit …
dotnet add package Azure.AI.TextAnalytics
–version 5.3.0
dotnet add package Azure.Identity
–version 1.12.0
dotnet add package Azure.Security.KeyVault.Secrets
–version 4.6.0
Anschließend öffnest du in VS-Code die Datei „appsettings.json“. Wie schon im letzten Artikel verwenden wir zwar auch hier wieder eine Konfigurationsdatei, um den Zugriff auf Azure-Backenddienste zu konfigurieren, allerdings enthält diese diesmal keine API-Schlüssel in Klartext. Stattdessen setzt du in die Datei neben dem obligatorischen Endpunkt deiner Azure-AI-Ressource den Namen deines Schlüsseltresors, die „Tenant ID“ (Verzeichnis-ID) deines Mandanten, die „AppId“ deines Dienstprinzipals und das vorhin notierte Passwort deines Dienstprinzipals ein. Vergiss nicht, die Änderungen zu speichern.
Ist das erledigt, kannst du bei Interesse auch einen Blick in die Code-Datei (bei C#: Program.cs) im gleichen Ordner werfen. Wie schon beim letzten Beitrag erwähnt, geht es hier nicht um das Programmieren an sich oder irgendwelche Anpassungen. Du solltest aber grob verstehen, was das Programm macht. Zuerst werden die Namespaces für die benötigten SDKs installiert („Azure“, „Azure.AI.TextAnalytics“, „Azure.Identity“, „zure.Security.KeyVault.Secrets“. Die Main-Klasse des neuen Namespaces „keyvault_client“ ruft die Anwendungskonfigurationseinstellungen ab und verwendet dann die Credentials des Dienstprinzipals, um den Azure AI-Services-Schlüssel aus dem Schlüsseltresor abzurufen. Die eigentliche Funktion „GetLanguage“ nutzt dann das SDK, um einen Client für den Dienst zu erstellen, und verwendet dann den Client, um die Sprache des eingegebenen Textes zu erkennen.
Führe nun abschließend das Programm aus mit:
dotnet run
Teste das Programm erneut mit Text in verschiedenen Sprachen.
Kontakt
„*“ zeigt erforderliche Felder an