Terraform Scaffolding für Azure – jetzt secretless mit OIDC
Inhalt
- Das Problem mit Secrets in CI/CD-Pipelines
- Was ist Terraform Scaffolding für Azure
- Was ist neu? OIDC als Default
- Warum OIDC? Die Vorteile auf einen Blick
- Wie funktioniert Workload Identity Federation?
- Quick Start: In 5 Minuten secretless auf Azure
- Migration: Von Client Secret zu Workload Identity Federation
- Fazit
Das Problem mit Secrets in CI/CD-Pipelines
Secrets in CI/CD-Pipelines haben ein Ablaufdatum. Und irgendwann vergisst jemand, sie rechtzeitig zu erneuern. Wer Infrastructure as Code mit Terraform auf Azure betreibt, kennt das Spiel: Ein Service Principal wird erstellt, das Client Secret landet im Key Vault oder direkt in den Pipeline-Variablen, und alles läuft. Bis das Secret abläuft. Oder geleakt wird. Oder jemand vergisst, es zu rotieren.
Die Realität sieht so aus:
- Secrets müssen rotiert werden und das regelmäßig. Wer das vergisst, riskiert Ausfälle.
- Secrets können geleakt werden. Ob durch Logs, fehlerhafte Konfiguration oder kompromittierte Systeme.
- Secrets sind Angriffsvektoren. Ein gestohlenes Client Secret gibt vollen Zugriff auf Azure-Ressourcen.
Die gute Nachricht: Es gibt einen besseren Weg. Mit Workload Identity Federation (OIDC) können GitHub Actions und Azure DevOps Pipelines sich direkt bei Azure authentifizieren ganz ohne gespeicherte Secrets.
In diesem Beitrag zeigen wir, wie unser aktualisiertes Terraform Scaffolding für Azure diesen Ansatz out-of-the-box unterstützt und wie Sie in wenigen Minuten auf “secretless” umsteigen können.
Was ist Terraform Scaffolding für Azure?
Bevor man mit Terraform auf Azure loslegen kann, braucht man ein solides Fundament: Einen Service Principal mit den richtigen Berechtigungen, ein Storage Account für den Terraform State und bisher einen sicheren Ort für Secrets. Genau das haben wir vor einiger Zeit mit unserem Terraform Scaffold for Azure gelöst. Wer den ursprünglichen Ankündigungspost noch nicht kennt: Das Repository bietet ein einfaches Setup-Skript, das in wenigen Minuten alles Nötige provisioniert.
Was bekommen Sie Out-of-the-Box?
| Komponente | Beschreibung |
| Service Principal | Mit Contributor- und Role Based Access Control Administrator-Rolle (eingeschränkt durch Conditions) |
| Storage Account | Für den Terraform State mit aktivierter Soft-Delete-Policy |
| Entra ID Berechtigungen | Der Service Principal erhält Graph API Permissions (Group.ReadWrite.All, GroupMember.ReadWrite.All, User.Read.All), um mit dem azuread Provider Gruppen und Identitäten zu verwalten. Erfordert Admin Consent. |
| Bash & PowerShell Support | Skripte für beide Umgebungen |
Das Ziel war von Anfang an: In unter 10 Minuten produktionsbereit ohne manuelles Klicken im Azure Portal.
Was ist neu? OIDC als Default
Die größte Änderung im Repository: OIDC ist jetzt der Standard.
Bisher lag der Fokus auf dem klassischen Ansatz mit Client Secret + Key Vault. Das funktioniert, bringt aber die bekannten Nachteile mit sich. Secrets müssen sicher gespeichert, rotiert und verwaltet werden.
Mit dem Update haben wir die Struktur umgestellt:
- Root Verzeichnis: Workload Identity Federation für GitHub Actions oder Azure DevOps – empfohlen
- client-secret Verzeichnis: Klassischer Ansatz mit Key Vault – für Szenarien, in denen OIDC nicht möglich ist
Was bedeutet “secretless”?
Bei der OIDC-Variante wird kein Client Secret mehr erstellt oder gespeichert. Stattdessen stellt GitHub Actions oder Azure DevOps ein kurzlebiges Token aus, das Azure direkt gegen die Federated Credential validiert. Das Ergebnis:
- Kein Secret im Key Vault
- Kein Secret in Pipeline-Variablen
- Kein Secret, das rotiert werden muss
- Kein Secret, das geleakt werden kann
Der Service Principal existiert weiterhin, aber ohne permanente Credentials.
Warum ist OIDC sicherer?
Mit Client Secrets gibt es immer ein Zeitfenster, in dem ein gestohlenes Secret missbraucht werden kann. Selbst wenn das Secret nie absichtlich geteilt wird, kann es durch Debug-Logs, fehlerhafte Maskierung oder kompromittierte Backups nach außen gelangen. Einmal geleakt, hat ein Angreifer vollen Zugriff, bis jemand das Secret bemerkt und rotiert.
Bei OIDC existiert dieses Zeitfenster praktisch nicht. Das Token, das GitHub ausstellt, ist nur wenige Minuten gültig und an einen spezifischen Workflow-Run gebunden. Selbst wenn es abgefangen würde, wäre es bereits abgelaufen, bevor ein Angreifer es nutzen könnte.
Noch wichtiger: Die Kontrolle liegt nicht mehr beim Secret, sondern bei der Herkunft. Entra ID prüft, ob das Token tatsächlich von GitHub stammt, ob es für das richtige Repository ausgestellt wurde und ob das Environment übereinstimmt. Ein Fork des Repositories oder ein Workflow aus einem anderen Branch wird abgelehnt, selbst wenn der Angreifer Zugriff auf die GitHub Secrets hat.
Vergleich: Client Secret vs. OIDC
| Aspekt | Client Secret | OIDC (Workload Identity Federation) |
| Secret-Rotation nötig? | Ja, alle 1-2 Jahre | Nein |
| Leak-Risiko | Vorhanden | Eliminiert |
| Kontrolle über Quelle | Keine (wer das Secret hat, hat Zugriff) | Repo, Branch, Environment |
| Empfohlen von Microsoft | Legacy | Best Practice |
Wie funktioniert Workload Identity Federation?
Für alle, die verstehen wollen, was unter der Haube passiert, hier ist der Ablauf der OIDC-Authentifizierung dargestellt.
Der Authentifizierungsfluss in vier Schritten:
- GitHub Actions / Azure DevOps startet und fordert ein OIDC-Token vom eigenen Identity Provider an
- Der CI/CD-Provider stellt ein signiertes JWT aus, das Informationen enthält wie: Repository, Branch, Environment, Workflow
- Das Token wird an Entra ID gesendet, zusammen mit der Client ID des Service Principals
- Entra ID validiert das Token gegen die konfigurierte Federated Credential – stimmen Issuer, Subject und Audience überein, wird ein Azure Access Token ausgestellt

Die Federated Credential definiert drei Prüfpunkte. Nur wenn alle drei Felder exakt übereinstimmen, wird der Zugriff gewährt. Ein Token aus einem anderen Repository oder Branch wird abgelehnt.
| Feld | Beispiel GitHub | Beispiel Azure DevOps |
| Issuer | https://token.actions.githubusercontent.com | https://vstoken.dev.azure.com/<org-id> |
| Subject | repo:org/repo:environment:prod | sc://org/project/service-connection |
| Audience | api://AzureADTokenExchange | api://AzureADTokenExchange |
Quick Start: In 5 Minuten secretless auf Azure
Hier ist die Kurzanleitung, um das Terraform Scaffolding mit OIDC aufzusetzen.
Voraussetzungen
- Azure CLI installiert und authentifiziert (az login)
- Bash mit jq oder PowerShell
- Azure-Berechtigungen: Subscription Owner (oder Contributor + User Access Administrator) sowie Application Developer in Entra ID
Schritt 1: Repository klonen
- git clone https://github.com/whiteducksoftware/terraform-scaffold-for-azure.git
- cd terraform-scaffold-for-azure
Schritt 2: die .env Datei mit den gewünschten Namen anpassen
Schritt 3: Federated Credential konfigurieren
- Für GitHub Actions – federated_credential_github.json anpassen
- Für Azure DevOps – federated_credential_ado.json anpassen
Schritt 4: Setup-Skript ausführen (hier up.sh als Beispiel)
In up.sh das richtige Credential-File auswählen und ausführen:
- # FEDERATED_CREDENTIAL_FILE=”federated_credential_github.json” # für GitHub
- # FEDERATED_CREDENTIAL_FILE=”federated_credential_ado.json” # für Azure DevOps
Schritt 5: CI/CD-Pipeline konfigurieren (GitHub Actions Beispiel)
name: TF Deploy
on:
workflow_dispatch:
push:
branches:
- 'main'
# OIDC-Authentifizierung erfordert diese Permission.
# Ohne 'id-token: write' kann der Workflow kein OIDC-Token anfordern.
permissions:
id-token: write
contents: read
env:
tf_version: 1.14.3
# Aktiviert OIDC für den AzureRM Terraform Provider.
# Damit entfällt ARM_CLIENT_SECRET komplett.
ARM_USE_OIDC: true
# Nutzt Entra ID Auth für den Storage Account statt Access Keys.
ARM_USE_AZUREAD: true
jobs:
deploy-dev:
name: Deploy DEV
runs-on: ubuntu-latest
# Das Environment muss exakt mit dem 'subject' in der Federated Credential übereinstimmen.
# Beispiel: repo:org/repo:environment:dev
# Ein Workflow ohne passendes Environment wird von Entra ID abgelehnt.
environment: dev
env:
stage: dev
rg_name: '<your-resource-group-name>'
sa_name: '<your-storage-account-name>'
sc_name: '<your-blob-name>'
# Diese drei Werte sind KEINE Secrets im klassischen Sinne.
# Sie sind öffentliche Identifier – ein Angreifer kann damit alleine nichts anfangen.
# Die Authentifizierung erfolgt ausschließlich über das signierte OIDC-Token.
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}
steps:
- name: Checkout
uses: actions/checkout@v4
# OIDC-Login: Kein client-secret Parameter!
# Die Action fordert automatisch ein OIDC-Token von GitHub an
# und tauscht es bei Entra ID gegen ein Azure Access Token.
- name: Azure CLI login
uses: azure/login@v2
with:
client-id: ${{ secrets.ARM_CLIENT_ID }}
tenant-id: ${{ secrets.ARM_TENANT_ID }}
subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: ${{ env.tf_version }}
# Terraform nutzt die OIDC-Session aus dem azure/login Step.
# Keine zusätzlichen Credentials nötig – ARM_USE_OIDC übernimmt den Rest.
- name: Terraform Init
run: |
terraform init -input=false \
-backend-config="resource_group_name=${{ env.rg_name }}" \
-backend-config="storage_account_name=${{ env.sa_name }}" \
-backend-config="container_name=${{ env.sc_name }}" \
-backend-config="key=${{ env.stage }}.tfstate"
- name: Terraform Format
run: terraform fmt --check
- name: Terraform Validate
run: terraform validate
- name: Terraform Plan
run: |
terraform plan \
-var-file=./env/${{ env.stage }}.tfvars \
-out ${{ env.stage }}_tfplan.out
- name: Terraform Apply
run: terraform apply -auto-approve ${{ env.stage }}_tfplan.out
Migration: Von Client Secret zu Workload Identity Federation
Wer das Terraform Scaffolding bereits mit Client Secret nutzt, kann mit wenigen Schritten auf OIDC umsteigen.
Azure DevOps
Um auf dem einfachsten Weg die bestehende Azure DevOps Service Connection und den Service Principal auf Federated Credentials umzustellen, geht man wie folgt vor:
- Eine weitere Service Connection vom Typ „Azure Resource Manager“ anlegen.
- „App registration or managed identity (manual)“ auswählen.
- Als Credential „Workload Identity Federation“ setzen.
- Einen Namen vergeben und die eigene Tenant ID eintragen.
- Im nächsten Schritt den Issuer und den Subject Identifier kopieren und für später bereithalten.
- Die Service Connection anschließend fertigstellen und speichern.
- Zu Entra ID wechseln und den Service Principal editieren.
- Unter „Certificates and secrets“ ein Federated Credential anlegen und als Scenario „Other issuer“ auswählen.
- Issuer und Subject Identifier aus Schritt 6 einfügen und speichern.
- Die Pipeline anpassen, damit diese die neue Service Connection verwendet. Danach kann die alte Service Connection mit dem Credential-Typ „Secret“ entfernt werden.
- Das Secret beim Service Principal sollte ebenfalls gelöscht werden.
GitHub
Für die Migration auf federated identity credential für GitHub sind folgende Schritte notwendig:
- Zu Entra ID wechseln und den Service Principal editieren.
- Unter „Certificates and secrets“ ein Federated Credential anlegen und als Scenario „GitHub Actions deploying Azure resources“ auswählen.
- Organisation, Repository und Entity Type so setzen, dass folgender Subject identifier dabei rauskommt: repo:myorg/myrepo:environment:dev (hier das Environment dev als Beispiel).
- Das Secret beim Service Principal sollte ebenfalls gelöscht werden.
In beiden Fällen muss die Pipeline bzw. der Workflow auf die Secret-freie Authentifizierung angepasst werden.
Fazit
Secrets in CI/CD-Pipelines gehören der Vergangenheit an zumindest für die Authentifizierung bei Azure. Mit Workload Identity Federation bietet Microsoft einen sicheren, wartungsfreien Weg, um GitHub Actions und Azure DevOps Pipelines mit Azure zu verbinden. Kein Client Secret, das rotiert werden muss. Kein Leak-Risiko durch versehentlich geloggte Credentials. Keine nächtlichen Pipeline-Ausfälle, weil jemand die Rotation vergessen hat.
Unser aktualisiertes Terraform Scaffolding für Azure macht den Einstieg so einfach wie möglich:
- OIDC ist jetzt der Default – die sichere Variante ist gleichzeitig die einfachste
- GitHub Actions und Azure DevOps werden beide unterstützt
- Ein Skript, fünf Minuten und die Infrastruktur steht
Falls Sie Fragen, Probleme oder Verbesserungsvorschläge haben, kontaktieren Sie uns! Wir helfen gerne weiter und freuen uns über Issues und Pull Requests im Repository.