Secret Management und vor allem der Austausch solcher sicherheitsrelevanten Artefakten ist ein schwieriges Thema welches meiner Erfahrung nach auch häufig ziemlich Stiefmütterlich behandelt wird. Gerade in Version Control Systems (VCS) wie zum Beispiel Git findet man viel zu oft ein Secret, welches in einer Applikation für die Single Sign On Anbindung via OpenID Connect (OIDC) verwendet wird. Auch der Austausch von Secrets mit Partner oder Lieferanten erfolgt gerne mal über unsichere Kanäle wie Email, bei dem die Verschlüsselung nicht durchgängig sichergestellt ist.
Microsoft Entra ID bietet nebst dem klassischen Secret und einem Zertifikat, welches als asymmetrischer Schlüssel verwendet wird, die Möglichkeit dem OIDC Token eines fremden Identity Providers (IdP) zu vertrauen. Letzteres ist sehr interessant, da es einerseits die Gefahr von leaked Secrets mitigiert und andererseits der Fall nicht eintreffen kann, dass ein Secret oder Zertifikat abläuft und dadurch der Service gestört ist. Zudem ist es, einmal eingerichtet, frei von jeglichem Wartungsaufwand.
Verwendungszweck
Federated Credentials können für Software Workloads eingesetzt werden, wo üblicherweise ein Secret oder Zertifikat genutzt wird. Ein hervorragendes Beispiel hierfür ist Terraform (Enterprise und Cloud). Wenn ein Terraform Workspace den Provider azuread oder azurerm verwendet kann mit Federated Credentials ein kurzlebiges OIDC Token von Terraform selber ausgestellt werden, welches bei Entra ID gegen ein Token des jeweiligen Service Principals getauscht wird. Mit letzterem Token und dessen entsprechenden Microsoft Graph Berechtigungen wird dann der Terraform Plan ausgeführt.
Voraussetzungen
- OpenID Provider Configuration Document muss öffentlich verfügbar sein
- JSON Web Key Set (JWKS) muss öffentlich verfügbar sein
- Issuer muss eine öffentlich erreichbare URL sein
Wie funktioniert es
Zu Demonstrationszwecken erstelle ich auf einer Keycloak Instanz einen OIDC Client mit Service account roles, welcher den Client Credentials Flow ermöglicht. Wichtig ist dabei die Audience auf api://AzureADTokenExchange
zu setzen (oder alternativ in Entra ID die Audience ändern)
Auf dem Service Principal in Microsoft Entra ID wähle ich als Szenario “Other issuer” und trage ich den Issuer und den Subject Identifier des OIDC Clients von Keycloak ein um die Vertrauenstellung zu erstellen.
Mit Hilfe von Postman hole ich mir mit dem Client Credentials Flow ein ID Token von Keycloak unter Angabe der foldenden Parametern:
- client_id: fc-demo-blog
- client_secret: -in Keycloak generiert-
- scope: openid
- grant_type: client_credentials
Im Payload des resultierenden ID Token sind die gewünschten Claims iss (für Issuer), sub (für Subject) und aud (für Audience), welche den Einstellungen in den Federated Credentials in Microsoft Entra ID entsprechen müssen.
Dieses ID Token wiederum verwende ich nun erneut in Postman für den Token Austausch mit Microsoft Entra ID unter Angabe der folgenden Parametern:
- scope: https://graph.microsoft.com/.default (damit ich alle App Roles erhalte, die dem Service Prinicpal zugewiesen sind)
- client_id: entspricht der Client ID des Service Principals, auf dem die Federated Credentials hinterlegt sind
- client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer
- grant_type: client_credentials
- client_assertion: das ID Token, welches ich von Keycloak erhalten habe
Microsoft Entra ID stellt mir nun ein Access Token aus, welches ich verwenden kann, um auf die entsprechenden APIs zuzugreifen.
Token Forging
Da ich genauer wissen wollte, wie der Federated Credentials Token überprüft wird und keine Dokumentation darüber gefunden habe (ausser, dass der Issuer und das Subject geprüft wird) startet ich selber ein kleines Experiment.
In Go habe ich mir ein kleines Tool geschrieben, dass mir ein JSON Web Token (JWT) mit exakt denselben Claims wie das Keycloak Token ausstellt. Mit einem symmetrischen Schlüssel (HS256) für die Signatur erhalte ich von Microsoft Entra ID folgende Antwort:
|
|
Ok damit wäre mal klar, dass ein asymmetrischer Schlüssel verwendet werden muss (was sich mit meinen Erwartungen an dieses Feature deckt). Weiter gehts mit einem asymmetrischen Schlüssel (RS256) und einem fiktiven kid (Key ID) im Header. Dies liefert mir das Resultat:
|
|
Die Antwort bedeutet, dass die OIDC Metadaten URL aus derjenigen des Issuer Claims abgeleitet wird. Nun bleibt noch den korrekten kid (Key ID) im Header anzugeben und zu bestätigen, dass die Signature anhand des Public Keys geprüft wird.
|
|
Und genau so ist es! Das heisst Microsoft Entra ID prüft die OIDC Metadaten, welche vom Issuer Claim abgeleitet werden und verifiziert die Signatur. Damit wird es einem Angreifer stark erschwert ein Token selber zu erstellen um den Service Principal zu missbrauchen, er müsste dafür den fremden Identity Provider übernehmen. Der Quellcode für die Tests ist folgender:
|
|
Verwendung in Terraform
Ein sehr gutes Beispiel, wo Federated Credentials eingesetzt werden können, ist für mich Terraform weil damit die ganzen Wartungsaufwände für Secrets wegfallen. Dafür wird in den Terraform Workspace Variablen folgende zwei Einträge gemacht:
- TFC_AZURE_PROVIDER_AUTH = true
- TFC_AZURE_RUN_CLIENT_ID = Client ID des Service Principals in Entra ID
In der Provider Konfiguration muss der Tenant angegeben werden und use_oidc = true
gesetzt werden
|
|
Und schlussendlich noch die Federated Credentials Konfiguration auf der App Registration in Microsoft Entra ID. Hier müssen zwei Einträge angelegt werden, eines für die Plan Phase und eines für die Apply Phase. Der Subject Identitfier setzt sich aus den Elementen Organisation, Project, Workspace und Run Phase zusammen und kann somit sehr granular auf die Service Principals (zur Erinnerung: die Kombination aus Issuer und Subject muss eindeutig sein im Tenant) verteilt werden.
- Issuer: https://app.terraform.io
- Subject Identifier: organization:irbech:project:Default Project:workspace:entraid-apps:run_phase:plan
und
- Issuer: https://app.terraform.io
- Subject Identifier: organization:irbech:project:Default Project:workspace:entraid-apps:run_phase:apply