Beliebte Suchanfragen
|
//

Zero Trust Azure Identity & Access Architektur

4.6.2024 | 13 Minuten Lesezeit

Falko Lehmann und Hendrik Kamp haben in ihrem Blog Post zu Zero-trust Architecture bereits dargelegt, warum Zero-Trust Sicherheitsmodelle gegenüber traditionellen Perimetersicherheitsmodellen zu bevorzugen sind, um Schaden durch Cyber-Angriffe zu minimieren. Falko und Hendrik haben hier bereits das Cloud-agnostische Spiffe Framework erwähnt, um Maschinen und Workload Identitäten zu verifizieren. In diesem Blog Post möchte ich darauf eingehen, wie die Prinzipien "Never trust, always verify" und "Least Privilege" in der Praxis in der Azure Cloud mit Azure Technologien umgesetzt werden können.

Microsoft Entra ID Applications und Security Principals

Azure stellt mit Microsoft Entra ID und Azure AD B2C als Teil der Microsoft Entra External ID Plattformen Identity Services mit OpenID Connect Schnittstelle bereit. OpenID Connect hat sich inzwischen als Standard für die Authentifizierung von Usern und Services im Web durchgesetzt.

Das “Never trust, always verify” Prinzip erfordert, dass jeder Service bei jeder Anfrage die Identität des Clients, sowie dessen Berechtigungen im Service-Kontext, verifizieren muss.

In Microsoft Entra ID wird der Kontext, in dem Berechtigungen vergeben und verifiziert werden, Application genannt. Jeder selbst betriebene Service erfordert das Anlegen einer dedizierten Application. Diese ermöglicht u.a. die Definition von Client-spezifischen Berechtigungen.

Bei Clients handelt es sich immer um Security Principals. Diese können im Kontext einer Application als Service Principal oder im Kontext eines User Interfaces als User Principal ausgeprägt sein. Service Principal-Objekte lassen sich Application-Objekten eindeutig über die sogenannte "Client-ID" zuordnen. Synonym zum Begriff "Client-ID" wird auch der "App-ID" genutzt. Beide Entra ID Objekte haben jeweils noch separate Objekt-IDs, die mit der Client-ID bzw. App-ID nicht zu verwechseln sind.

Es ist zu beachten, dass Entra ID Applications im Azure Portal unter der Kategorie "App Registrations" zu finden sind. Im Azure Portal gibt es auch eine zweite Kategorie "Enterprise Applications". Diese ist allerdings etwas misverständlich benannt und listet eigentlich Service Principals auf. Es ist möglich, dass Service Principals ohne eine App Registration im eigenen Azure Tenant existieren können. Beispielsweise wenn eine App Registration in einem fremden Tenant angelegt wurde und von einem User im eigenen Tenant genutzt oder wenn diese in Verbindung mit einer so genannten Managed Identity angelegt wird.

Beispiel Service Architektur

Um verschiedene Szenarien in diesem Blog Artikel zu erläutern, ist es hilfreich, zunächst eine minimale Service-Architektur zu modellieren.

Als Fachlichkeit dieser Architektur wählen wir eine vereinfachte Entwickler Platform mit Code Repositories und CI Service, ähnlich wie GitHub oder GitLab.

Teil dieser Architektur sind zwei Services:

  • Code Repository Service inkl. UI (z.B. Swagger-UI)
  • CI Service inkl. UI (z.B. Swagger-UI)

Der Code Repository Service ist ein REST-Service mit folgenden Endpunkten:

  • /repository/
    • POST - Repository Ressource erzeugen
    • GET - Alle Repository Ressourcen abrufen
  • /repository/{repository-name}
    • GET - Repository Ressource abrufen
    • PUT - Repository Ressource ändern
  • /repository/{repository-name}/code
    • GET - Den aktuellen Quellcode eines Repositories abrufen
    • PUT - Den Quellcode eines Repositories ändern

Der CI Service ist in diesem Beispiel ebenfalls ein REST-Service mit folgenden Endpunkten:

  • /job/
    • POST - CI Job erzeugen
      • Parameter:
        • repository_name
        • shell_command
    • GET - Alle CI Jobs abrufen
  • /job/{job-id}
    • GET - CI Job Details abrufen

Der POST /job/ CI Service Endpunkt ist abhängig vom Code Repository Service. Es wird angenommen, dass der CI Service hier zunächst den Quellcode des angegebenen Repositories herunterlädt und anschließend einen Shell-Befehl im Kontext des Quellcode-Verzeichnisses ausführt. Dieser Endpunkt dient als zentrales Beispiel für notwendige Service-zu-Service Kommunikation in den nachfolgenden Szenarien.

Szenarien

Im Folgenden möchte ich verschiedene Szenarien anhand der dargestellten Beispiel-Architektur demonstrieren. Diese Szenarien unterscheiden sich vor allem über ihre jeweilige Komplexität und die Konsequenz in der Umsetzung des “Least Privilege" Prinzips. Die fortgeschrittenen Szenarien sind zunehmend komplexer, bieten aber auch immer weitergehenden Schutz vor Privilege Escalation Attacken, die nach dem “Assume Breach” Prinzip in Zero-Trust Modellen immer anzunehmen sind.

Perimeter Security Szenario

In diesem Abschnitt möchte ich das Perimetersicherheitsmodell beispielhaft erläutern. Ich weise an dieser Stelle bereits darauf hin, dass dieses Sicherheitsmodell in den meisten Anwendungsfällen als unzureichend angesehen werden muss und mit dem Zero-Trust-Sicherheitsmodell unvereinbar ist.

Im Perimeter Security Szenario darf der CI Service aus dem internen Netz auf alle Schnittstellen des Code Repositories zugreifen. Dieses Sicherheitsmodell basiert dabei auf der Annahme, dass kein Angreifer in das interne Netz eindringen und laterale Attacken gegen interne Services ausführen kann.

In Azure kann dieses Sicherheitsmodell mit Hilfe virtueller Netzwerke abgebildet werden. Beide Services werden in demselben internen Netzwerk platziert. Die Authentifizierung und Autorisierung des CI Services gegenüber des Code Repository Services basiert anschließend ausschließlich auf der Prüfung der Client IP Adresse.

Um das Kriterium des Perimeter Sicherheitsmodells zu erfüllen, muss sich die Client IP im internen Netz befinden. Die zu prüfende Client IP muss dazu nicht mit der Client IP Adresse der TCP Verbindung übereinstimmen. Ggf. wird auch der Angabe der Client IP Adresse aus dem Forwarded oder dem X-Forwarded-For HTTP-Headers vertraut, falls sich der Service hinter einem Reverse-Proxy-Server befindet. In diesem Fall ist sicherzustellen, dass der Code Repository Service nicht über eine öffentliche IP verfügt, Routing in das interne Netz nur aus anderen vertrauenswürdigen Netzen möglich ist und der Reverse-Proxy-Server so konfiguriert ist, dass kein Spoofing der geprüften HTTP-Header möglich ist. Falls der Code Repository Service nicht über eine öffentliche IP verfügt und nicht über einen HTTP-Reverse-Proxy abrufbar ist, kann Perimeter Security auch implizit angenommen werden, ohne die Client IP durch den Code Repository Service explizit zu prüfen.

Dieser Abschnitt zeigt, dass das Perimeter Sicherheitsmodell auf sehr vielen Annahmen beruht und u.U. eine falsche von vielen notwendigen Konfigurationen zur Kompromittierung des Sicherheitsmodells führen kann. Besser ist es, keinem Client implizit zu vertrauen, sondern die Identitäten und Berechtigungen jedes Clients explizit zu prüfen.

Abschließend ist zu erwähnen, dass grundsätzlich das Perimentersicherheitsmodell vermieden werden sollte. Allerdings setzt auch Microsoft in Teilen auf Perimetersicherheit. Als Beispiel ist hier der IMDS Endpunkt zu nennen. Dieser ist kritisch bei der Ausstellung von Access Token für Managed Identities und ist innerhalb von VMs grundsätzlich aufrufbar und ohne weitere Authentifizierung nutzbar. Dieser Umstand ist besonders zu berücksichtigen, wenn Anwendungen in Containern innerhalb einer VM ausgeführt und gemäß des "Least Privilege" Prinzips keinen Zugriff auf Managed Identities haben sollen, die der VM zugewiesen sind. Auch in Azure Kubernetes Service Nodes wird dieses Prinzip aktuell nicht erfüllt und muss vom AKS Cluster Administrator nachgepflegt werden.

Service Principal App Role basiertes Sicherheitsmodell

In diesem Szenario darf der CI Service auf alle Schnittstellen des Code Repository Services zugreifen. Die Identität und Berechtigungen des Clients werden dabei explizit überprüft. Optional können die Berechtigungen kategorisch eingeschränkt werden. Kategorisch bedeutet, dass der Zugriff nur auf bestimmte Arten von Schnittstellen erlaubt wird.

In diesem Szenario muss für den Code Repository Service eine App Registration angelegt werden.

Der CI Service muss sich gegenüber der Application des Code Repository Services mit Hilfe eines Service Principals authentifizieren. Dazu eignet sich am besten ein Service Principal in der Ausprägung einer Managed Identity. Managed Identities können in Azure direkt mit Services verknüpft werden, so dass diese zur Laufzeit Entra ID Token für den Service Principal beziehen können, ohne statische Secrets nutzen und rotieren zu müssen.

Eine Alternative zur Nutzung der Managed Identity wäre die Nutzung des gut dokumentierten Client Credentials Flows im Zusammenhang mit einer dedizierten App Registration für den CI Service. Für diesen ist allerdings ein statisches Client Secret erforderlich, welches aus Sicherheitsgründen zur Laufzeit regelmäßig erneuert werden muss. Die Nutzung der Managed Identity ist somit zu bevorzugen, aber in manchen Fällen unmöglich.

Wenn sich der CI Service gegenüber dem Code Repository Service authentifizieren möchte, muss dieser sich einen Token beschaffen, der im Kontext des Code Repository Services gültig ist, so dass die Authentifizierung nicht mit beliebigen Entra ID Tokens erfolgen kann, die eigentlich für einen anderen Zweck vorgesehen sind. Eine unzureichende Verifizierung des vorgesehenen Zwecks von Access Tokens hat in der Vergangenheit bereits zu vielen Sicherheitsvorfällen geführt.

Bei dem Bezug eines OAuth 2 Tokens ist der Zweck eines Token über eine Liste von Scope Deklarationen im Token Request anzugeben. Für Applications ist der Scope immer zusammengesetzt aus der Client-ID und dem Default-Scope (.default), beispielsweise e620bb52-4cf2-4919-87f1-ac13302b1b60/.default. Des Weiteren muss der Request an den richtigen OAuth 2 Token Issuer, in unserem Fall das richtige Entra ID Directory, gesendet werden. Der Token ist anschließend nur im Kontext eines bestimmten Entra ID Directories (verifizierbar über den Issuer (iss) Token Claim) und für eine spezifische Application (verifizierbar über den Audience (aud) Token Claim) gültig.

Beispiel Client Credentials Request:

1curl https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token \
2    -d grant_type=client_credentials \
3    -d scope=${CODE_REPOSITORY_SERVICE_CLIENT_ID}/.default \
4    -d client_id=${CI_SERVICE_CLIENT_ID} \
5    -d client_secret=${CI_SERVICE_SECRET}

Decoded Token:

1{
2  "aud": "{CODE_REPOSITORY_CLIENT_ID}",  // Audience: Client ID of the code repository server application. Other applications must not accept this token.
3  "iss": "https://login.microsoftonline.com/{TENANT_ID}/v2.0",
4  "azp": "{CI_SERVICE_CLIENT_ID}", // Authorized Party: Client ID of the ci service application.
5  "oid": "{CI_SERVICE_PRINCIPAL_OBJECT_ID}",
6  "roles": [
7    "Repositories.Code.Read.All" // ci-service has the code-repsitory-service app role "Repositories.Code.Read.All" assigned. Therefore it is allowed to read the repository code of any user.
8  ],
9  "sub": "{CI_SERVICE_PRINCIPAL_OBJECT_ID}",
10  "tid": "{TENANT_ID}",
11  [...]
12}

Es ist außerdem möglich, einem Service Principal sogenannte App Roles zuzuweisen. Sollte dies der Fall sein, sind diese Rollen im roles Claim aufgeführt. In unserem Szenario können wir App Roles dazu verwenden, um dem CI Service kategorisch Zugriff auf die GET /repository/{repository-name}/code Endpunkte, beispielsweise in Form einer “Repository.Code.Read.All” App Role, zu geben. Eine feingranularere Berechtigungsstruktur für spezifische Repositories ist mit App Roles nicht möglich.

App Roles können App Registrations im Azure Portal über den Reiter “API Permissions” zugeordnet werden. Bei Managed Identities oder anderen Service Principals ohne App Registration ist diese Zuweisung leider nur über die REST API oder z.B. über Terraform möglich.

Mit der Möglichkeit der Vergabe von weitreichenden Berechtigungen in Form von App Roles an Service Principals, sollte sehr vorsichtig umgegangen werden. Der Midnight Blizzard Angriff auf Microsoft im Januar 2024 war auch deshalb so weitreichend, weil der Angreifer die Möglichkeit hatte, die “full_access_as_app” App Role für einen Microsoft Exchange Server an eine eigene Application zu vergeben. “full_access_as_app” ermöglicht dem privilegierten Service Principal vollen Zugriff auf alle E-Mails aller User. Bereits die Existenz entsprechender App Roles ist somit bereits ein Risiko.

Nutzung delegierter Berechtigungen von Benutzern durch Service Principals

In diesem Szenario darf der CI Service mit den Berechtigungen eines Users auf alle Repositories zugreifen, auf die der User normalerweise in der Code Repository Application Zugriff hätte. Berechtigungen können auch hier kategorisch weiter eingeschränkt werden. Kategorisch bedeutet, dass nur Zugriff auf bestimmte Schnittstellen erlaubt wird.

In diesem Szenario muss für den Code Repository Service ebenfalls eine App Registration angelegt werden, damit sich Security Principals gegen diese authentifizieren können. Auch der CI Service benötigt eine App Registration, um einen Service Principal bereitgestellt zu bekommen und User delegierte Berechtigungen konfigurieren zu können.

In unserem Beispiel möchten wir dem CI Service die Möglichkeit geben, auf den Code der Repositories zugreifen zu können, auf den auch ein User Zugriff hat. Die Berechtigungen sollen außerdem nur während der Verarbeitung einer User-Anfrage gewährt werden. Außerhalb der Verarbeitung einer entsprechenden Anfrage soll der CI Service dem “Least Privileges” Prinzip folgend keine Rechte für den Zugriff auf den Code Repository Service besitzen. Die Fähigkeit eines Service Principals die Rechte eines Users zu übernehmen, wird auch User Impersonation genannt.

Die Definition von User delegierten Berechtigungen werden in App Registrations über Scope-Definitionen abgebildet. Namen von Scopes teilen sich einen Namensraum mit App Roles. Sollte es somit bereits eine App Role “Repository.Code.Read.All” geben, können wir keinen Scope mit demselben Namen anlegen. Für unser Beispiel Szenario macht es Sinn mindestens zwei Scopes zu definieren: “UserImpersonation.ReadWrite.All” und “UserImpersonation.Repository.Code.Read.All” zu definieren. Der erste Scope kann von einer UI des Code Repository Services verwendet werden, um alle Daten des Services managen zu können. Der zweite Scope kann vom CI Service verwendet werden, um die Zugriffsrechte des Users auf den Code aller Repositories des Users nutzen zu können.

Die Zuweisung von Scopes zu einer App Registration ist ebenfalls über den “API Permissions” Reiter im Azure Portal möglich, ähnlich wie bereits auch die Zuweisung von App Roles möglich ist.

Nachdem dem CI Service die “UserImpersonation.Repository.Code.Read.All” Scope zugeordnet wurde, wird beim nächstes Login des Users ein zusätzlicher Zustimmungsdialog angezeigt. In diesem muss der User bestätigen, dass der CI Service die Identität des Users bei der Kommunikation mit dem Code Repository Service annehmen darf und mit dessen Berechtigungen den Code aller Repositories lesen darf. Ohne weiteren Scope darf der CI Service dennoch mit keinen weiteren Code Repository Service Ressourcen des Users interagieren. Ein Microsoft Entra Administrator kann die Zustimmung des Users auch pauschal für alle User des Entra Directories gewähren. Dann ist diese Zustimmung beim Login nicht mehr explizit von jedem User notwendig.

User Zustimmungsdialog für Code Repository Service

User Zustimmungsdialoge für die Nutzung des CI Service mit der Einwilligung der Nutzung des Code Repository Services mit Berechtigungen des Users durch den CI Service

Um mit dem Code Repository Service zu kommunizieren, muss der CI Service einen User Token, der an den eigenen Service gerichtet ist, gegen einen Token austauschen, der speziell für den Code Repository Service ausgestellt wird. Dieser Token-Austausch kann über den On-Behalf-Of Flow realisiert werden.

Beispiel On-Behalf-Of Request

1curl https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token \
2    -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
3    -d requested_token_use=on_behalf_of \
4    -d scope=${CODE_REPOSITORY_SERVICE_CLIENT_ID}/.default \
5    -d client_id=${CI_SERVICE_CLIENT_ID} \
6    -d client_secret=${CI_SERVICE_SECRET} \
7    -d assertion=${USER_TOKEN_WITH_CI_SERVICE_AUDIENCE}

Decoded Token

1{
2  "aud": "{CODE_REPOSITORY_CLIENT_ID}",  // Audience: Client ID of the code repository server application. Other applications must not accept this token.
3  "iss": "https://login.microsoftonline.com/{TENANT_ID}/v2.0",
4  "azp": "{CI_SERVICE_CLIENT_ID}",  // Authorized Party: Client ID of the ci service application.
5  "name": "{USER_DISPLAY_NAME}",
6  "oid": "{USER_PRINCIPAL_OBJECT_ID_IN_ENTRA_ID}",
7  "preferred_username": "{PREFERRED_USERNAME}",
8  "scp": "UserImpersonation.Repositories.Code.Read.All", // Scopes: Token authorizes to read repository code on behalf of the user. This scope is only valid for the specfic audience (in this case the code repository service)!
9  "sub": "{APPLICATION_SCOPED_USER_ID_STRING}",
10  "tid": "{TENANT_ID}",
11  [...]
12}

Es gibt leider aktuell keine Möglichkeit diesen Token Austausch ohne statischem Client Secret durchzuführen, welches regelmäßig rotiert werden muss. Der Flow unterstützt laut Dokumentation zwar einen JWT “client_assertion” Parameter als Alternative zum Secret. Bei dem Versuch, diesen für den Austausch eines User-Tokens zu nutzen, schlägt der Austausch allerdings fehl.

Feingranulare User delegierte Berechtigungen

Für das Szenario des CI Services ist es vorstellbar, dass eine noch feingranularere Berechtigungsstruktur gewünscht wäre. Beispielsweise wäre es schön, wenn ein User dem CI Service nur das Laden des Codes bestimmter Repositories erlauben könnte, ähnlich wie dies bei GitHub mit fine-grained Personal Access Tokens möglich ist. Im Kontext eines CI Services kann dies die Auswirkungen eines komprimitierten Repositories auf ein einzelnes Repository begrenzen und weitere laterale Bewegungen eines Angreifers einschränken.

Leider gibt es von Azure für diese Art von Berechtigungsstrukturen keine bereitgestellte Lösung. Entsprechend fortgeschrittene Anforderungen müss aktuell von jeder Anwendung bzw. Organisation individuell gelöst werden. Auch der Azure Resource Manager hat entsprechend fortgeschrittene Anforderungen und bildet diese über Azure RBAC ab. Das Azure RBAC System kann somit unter Umständen als Orientierungshilfe dienen. Hier können feingranulare Berechtigungen an Rollen geknüpft, und diese wiederum auf Ressouren- und Subressourcen-Ebene hierachisch an User geknüpft werden. Azure RBAC kann eingeschränkt von Azure Kunden für eigene Services genutzt werden, wenn diese in Azure als Custom Resource Provider betrieben werden. Wenn dies der Fall sein sollte, übernimmt Azure RBAC hier die Berechtigungsprüfung für den Zugriff auf Ressourcen des entsprechenden Custom Resource Providers.

Unabhängig von Azure-spezifischen Lösungen lohnt sich bei entsprechenden Anforderungen möglicherweise ein Blick auf OpenFGA und Spicedb, sowie ein Blick auf den neuen OAuth 2.0 Rich Authorization Requests Standard. Letzterer ist bis heute leider nur in sehr wenigen Produkten umgesetzt. Stichwörter für die Kategorien dieser Lösungen sind ABAC (Attribute-Based Access Control) und ReBAC (Relationship-based access control ). Für OpenFGA und Spicedb sind Beispiele für GitHub-ähnliche Berechtigungsmodelle verfügbar [1] [2].

Fazit

Das Zero-Trust Sicherheitsmodell stellt weitgehende Anforderungen an die Identity und Access-Architekur von Service-Architekturen. Wie in den Szenarien bezüglich der Security Principal Authentifizierung mit App Role Authorisierung und der Authorisierung mit delegierten User Berechtigungen gezeigt wurde, stellt Microsoft Entra ID hier den Entwicklern bereits einige Werkzeuge zur Verfügung. Fein-granulare Berechtigungsstrukturen auf Ressourcen- und Subresourcen-Ebene sind dagegen auf die Implementierung von Custom Resource Providern beschränkt und ggf. eine Azure-unabhängige Lösung zu nutzen.

Wenn Sie die Entra ID spezifischen Szenarien näher nachvollziehen möchten, können Sie gerne einen Blick auf das Microsoft Entra ID Playground GitHub Repository werfen, welches im Rahmen der Erstellung dieses Blog Posts entwickelt wurde. Dieses enthält Terraform Module zum Aufsetzen einer Beispiel-Umgebung in Azure und die Beispiel-Implementierungen der CI und Code Repository Services in ASP.NET Core.

|

Beitrag teilen

//

Weitere Artikel in diesem Themenbereich

Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.