← Blog
Analytics & Monitoring·12. Juli 2026·6 min Lesezeit

aarm: Ablaufende Entra-App-Secrets aufspüren — per CLI, ohne Azure-Infrastruktur

Client Secrets auf Entra App Registrations laufen lautlos ab — ohne Vorwarnung, ohne tenantweite Übersicht. aarm ist eine schlanke CLI, die alle Secrets deines Tenants ausliest, das Risiko bewertet und JSON für Automatisierung liefert. Ganz ohne Desktop-Tool oder Azure-Infrastruktur.

Ein Client Secret auf einer App Registration ist eine tickende Uhr. Es läuft ab — leise, ohne Vorwarnung, und Microsoft schickt dir keine Erinnerung. Schlimmer noch: Es gibt keine eingebaute Ansicht, die dir alle ablaufenden Secrets über den ganzen Tenant zeigt. Das Ergebnis merkst du oft erst, wenn die Authentifizierung in Produktion bricht — meist an einem Freitagabend.

Das Problem: Secrets, die lautlos ablaufen

Jede App Registration in Microsoft Entra ID kann mehrere passwordCredentials (Client Secrets) haben, jedes mit eigenem Ablaufdatum. In einem gewachsenen Tenant sind das schnell hunderte Secrets über dutzende Apps. Es gibt keine automatische Erneuerung, keine Benachrichtigung und keine zentrale Liste. Wer den Überblick behalten will, klickt sich App für App durchs Portal — oder schreibt sich selbst ein Graph-Skript.

Genau diese Lücke schließt aarm — die Azure App Registration Monitor CLI.

Was aarm macht

aarm authentifiziert sich gegen deinen Tenant, fragt über Microsoft Graph alle App Registrations und ihre Secrets ab und rechnet für jedes Secret den Ablaufstatus und ein Risiko-Level aus. Das Ergebnis bekommst du als lesbare Tabelle oder als stabiles JSON für die Automatisierung.

  • Tenantweite Übersicht statt App-für-App-Klicken
  • Risiko-Bewertung je Secret (Info → Critical, basierend auf Restlaufzeit)
  • Preflight-Checks, die dir sagen, welche Berechtigungen tatsächlich vorhanden sind
  • Stabiles JSON für CI/CD, geplante Jobs und Skripte

aarm ist Teil der AARM-Toolchain und nutzt dieselbe Engine wie das AARM-Desktop-Tool (die npm-Library @brunsforge/azure-app-registration-monitor). Der Punkt, der diesen Artikel trägt: Du brauchst weder das Desktop-Tool noch irgendeine Azure-Infrastruktur. Die CLI allein liest die Secret-Informationen deines Tenants aus — auf deinem Laptop, in einer Pipeline, in einem Cron-Job.

Installieren

Der schnellste Weg führt über npm (Node.js ≥ 18 vorausgesetzt):

npm install -g @brunsforge/aarm
aarm --version

Willst du direkt vom Quellcode arbeiten oder ist das Paket in deinem Umfeld nicht erreichbar, klonst du das Monorepo von GitHub und baust Core + CLI:

git clone https://github.com/brunsforge/AzureAppRegistrationSecretMonitor.git
cd AzureAppRegistrationSecretMonitor

# Workspace-Abhängigkeiten (Core + CLI) installieren
npm install

# Erst die Core-Library bauen, dann die CLI
npm run build --workspace packages/core
npm run build --workspace packages/cli

# aarm auf den PATH legen
cd packages/cli
npm link

aarm --version

Für schnelles Ausprobieren ohne Build-Schritt geht auch npx tsx src/index.ts <command> direkt aus packages/cli.

Einmal einrichten: App Registration + Tenant

aarm braucht eine eigene App Registration pro Tenant — das ist aarms Identität, mit der es Graph aufruft. Sie ist getrennt von all den App Registrations, die aarm liest. Mindestberechtigung: Application.Read.All (Microsoft Graph) mit erteiltem Admin-Consent.

Der schnellste Weg für einen ersten Test ist der Device-Code-Modus mit einem Public-Client — du meldest dich interaktiv im Browser an, es gibt kein Secret zu verwalten:

  1. Azure Portal → Entra ID → App registrations → New registration, Name z. B. aarm-test, Single Tenant, keine Redirect-URI
  2. API permissions → Microsoft Graph → Delegated → Application.Read.All
  3. Grant admin consent (braucht einen Global Administrator)
  4. Authentication → Allow public client flows: Yes
  5. Die Application (client) ID kopieren

Bei den delegierten Modi (device-code, interactive-browser, azure-cli) braucht dein Benutzer zusätzlich die Entra-Rolle Cloud Application Administrator oder Application Administrator — sonst siehst du nur Apps, die dir selbst gehören.

Dann den Tenant in aarm hinterlegen:

aarm tenants add \
  --tenant-id    "<entra-tenant-id-guid>" \
  --display-name "Contoso PROD" \
  --auth-mode    device-code \
  --client-id    "<app-registration-client-id>"

Für unbeaufsichtigte Läufe (CI/CD) nimmst du stattdessen --auth-mode client-secret mit einer Daemon-App und Application-Berechtigungen; aarm fragt dann das Secret ab und legt es im OS-Credential-Store ab (Windows Credential Manager / macOS Keychain / libsecret) — nie in einer JSON-Datei.

Erst prüfen, dann lesen: preflight

Bevor du Secrets auslistest, bestätige, dass Auth und Berechtigungen sitzen:

aarm --tenant "Contoso PROD" preflight run
Preflight — xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Environment : prod
Checked at  : 2026-05-01T12:00:00.000Z

Authentication  : OK
Graph reachable : OK

Capabilities
┌────────────────────────────────────┬──────────────────────────────┐
│ Capability                         │ Status                       │
├────────────────────────────────────┼──────────────────────────────┤
│ canReadApplications                │ [✓] Available                │
│ canReadApplicationSecrets          │ [✓] Available                │
│ canReadOwners                      │ [ ] Unavailable              │
└────────────────────────────────────┴──────────────────────────────┘

Missing permissions
  ✗ Delegated permission missing: Directory.Read.All (admin consent required)

Fehlt etwas, sagt dir aarm --tenant "Contoso PROD" preflight explain in Klartext, welche Berechtigung wo im Portal fehlt — mode-aware, also passend zu deinem Auth-Modus.

Secrets auslesen

Jetzt der Kern — alle Secrets des Tenants, farbcodiert nach Risiko:

# Alle Secrets
aarm --tenant "Contoso PROD" secrets list

# Nur die, die in den nächsten 90 Tagen ablaufen
aarm --tenant "Contoso PROD" secrets expiring --days 90

# Bereits abgelaufene
aarm --tenant "Contoso PROD" secrets expired

Die Tabelle macht das Risiko sofort sichtbar:

┌──────────┬──────────────────┬─────────────┬────────────┬──────┬─────────────┐
│ Risk     │ App              │ Secret      │ Expires    │ Days │ Status      │
├──────────┼──────────────────┼─────────────┼────────────┼──────┼─────────────┤
│ CRITICAL │ Export Worker    │ prod-secret │ expired    │ -4   │ Expired     │
│ HIGH     │ CRM Connector    │ crm-prod    │ 21/05/2026 │ 21   │ ExpiringSoon│
│ MEDIUM   │ Teams Bot        │ bot-secret  │ 10/07/2026 │ 71   │ Valid       │
│ INFO     │ Test Tool        │ dev-secret  │ 01/11/2026 │ 185  │ Valid       │
└──────────┴──────────────────┴─────────────┴────────────┴──────┴─────────────┘
4 secret(s)

Für die Vogelperspektive gibt es aarm --tenant "Contoso PROD" apps list — dieselbe Risiko-Logik, aber pro App aggregiert (Anzahl Secrets, abgelaufen, ablaufend).

JSON für Automatisierung — der eigentliche Hebel

Für Menschen die Tabelle, für Maschinen --output json. Jede JSON-Ausgabe folgt demselben stabilen Envelope: success, metadata, data, warnings, errors.

aarm --tenant "Contoso PROD" secrets expiring --days 30 --output json
{
  "success": true,
  "metadata": {
    "tenantId": "<tenant-id>",
    "environmentName": "default",
    "generatedAt": "2026-05-01T12:00:00.000Z",
    "toolVersion": "0.1.0"
  },
  "data": [
    {
      "appDisplayName": "CRM Connector",
      "keyId": "…",
      "displayName": "crm-prod",
      "startDateTime": "2025-01-15T00:00:00Z",
      "endDateTime": "2026-05-21T00:00:00Z",
      "daysUntilExpiry": 21,
      "status": "ExpiringSoon",
      "riskLevel": "High"
    }
  ],
  "warnings": [],
  "errors": []
}

Weil das Format stabil ist, lässt sich der Output direkt weiterverarbeiten. Ein Gate in der Pipeline, das rot wird, sobald ein Secret abgelaufen ist:

# GitHub Actions
- name: Check for expired secrets
  run: |
    aarm --tenant "Contoso PROD" --output json secrets expired \
      | jq '.data | length' \
      | xargs -I{} test {} -eq 0
  env:
    AARM_CONFIG_DIR: ${{ runner.temp }}/aarm-config

Oder du filterst gezielt mit jq, etwa vor einer Rotation:

aarm --tenant "Contoso PROD" secrets expiring --days 14 --output json \
  | jq '.data[] | {app: .appDisplayName, keyId: .keyId, days: .daysUntilExpiry}'

Reports zum Verschicken

Für Tickets und Mails erzeugt aarm report fertige Ausgaben in table, json, markdown oder csv:

# Ablaufende Secrets als Markdown (direkt in ein Ticket kopieren)
aarm --tenant "Contoso PROD" report expiring --days 30 --output markdown

# Tenant-Gesamtüberblick: Apps, Secrets, Risikoverteilung
aarm --tenant "Contoso PROD" report tenant-summary

# Rotations-Checkliste für alles, was demnächst abläuft
aarm --tenant "Contoso PROD" report rotation-guide --days 14 --output markdown

Wo die Daten liegen

aarm legt seine Profile unter ~/.aarm/ ab — tenants.json (nicht-sensibel) plus eine history/ pro Tenant mit den letzten Scans. Secrets und Passwörter landen nie in diesen Dateien, sondern im OS-Credential-Store. Praktisch: Das Desktop-Tool nutzt denselben Store und dasselbe Config-Format, ein via aarm tenants add konfigurierter Tenant steht dort sofort bereit. Mit --config-dir (oder AARM_CONFIG_DIR) kapselst du in CI/CD eine eigene, isolierte Konfiguration.

Wo der Benefit liegt

  • Sichtbarkeit über den ganzen Tenant statt App-für-App-Portal
  • Risiko sofort einsortiert — du weißt, was zuerst dran ist
  • Automatisierbar über den stabilen JSON-Envelope und die Exit-Codes (0 ok, 2 Auth, 3 fehlende Berechtigung, 4 Konfiguration …)
  • Sichere Credentials im OS-Store, nie im Klartext
  • Kein Overhead: kein Desktop-Tool, keine Azure-Function, keine gehostete Infrastruktur — nur Node und eine App Registration

Loslegen

npm install -g @brunsforge/aarm
aarm tenants add                       # interaktiv
aarm --tenant "Contoso PROD" preflight run
aarm --tenant "Contoso PROD" secrets expiring --months 3

Quellcode und die vollständige Doku (alle Auth-Modi, jede Portal-Einstellung) liegen offen auf GitHub: github.com/brunsforge/AzureAppRegistrationSecretMonitor. Das npm-Paket findest du unter @brunsforge/aarm.

Siehe auch