KISicheres Gateway für Microsoft-365-Copilot-Zugriff auf ERP-Daten
Ein Security-Reverse-Proxy zwischen Microsoft Entra ID und dem Sage-100-ERP, damit Copilot gezielt auf Artikel-, Kunden- und Belegdaten zugreifen kann, ohne die ERP-Instanz direkt aus dem Internet erreichbar zu machen.
Ausgangslage
Microsoft 365 Copilot sollte gezielt und sicher auf ERP-Daten zugreifen können (Artikel, Kunden, Belege) — die Sage-100-Instanz durfte dafür aber nicht direkt aus dem Internet erreichbar sein. Mehrere Parteien waren beteiligt: Kunde (DMZ-VM, Firewall, ERP-Betrieb), ein Microsoft-Partner (Copilot-Connector) und ein ERP-Dienstleister (Sage-Seite).
Umsetzung
Ein eigens entwickeltes Gateway zwischen Entra ID und Sage: JWT-Validierung, segmentgenaue Pfad-Whitelist mit Fail-Closed-Prinzip, strikte Mandantentrennung über Entra-App-Rollen, Pflicht-Idempotency-Key gegen ERP-Doppelbuchungen und vollständiges Audit-Log mit Live-Stream.
Ergebnis
Vollständig implementiert innerhalb von vier Tagen, seither im laufenden Live-Betrieb kontinuierlich erweitert. Rund 220 Commits, Installer-Version 1.0.50 nach etwa 3,5 Monaten, 226 automatisierte Tests, zwei Security-Audits mit insgesamt 7 behobenen CVEs.
Ausgangslage
Ein mittelständisches Unternehmen mit Sage-100-ERP (Zugriff über die SData-REST-API) wollte Microsoft 365 Copilot gezielt auf ERP-Daten zugreifen lassen. Die zentrale Anforderung: Die Sage-Instanz darf dafür nicht direkt aus dem Internet erreichbar sein.
Drei Parteien waren beteiligt — der Kunde stellte DMZ-VM, Firewall und ERP-Betrieb, ein Microsoft-Partner baute den Copilot-Connector, ein ERP-Dienstleister lieferte die Sage-Seite. codekunst systems baute das Gateway dazwischen.
Lösung: ein Security-Reverse-Proxy zwischen Entra ID und Sage
Das Gateway prüft jeden Aufruf und tauscht das eingehende Token gegen Sage-Zugangsdaten aus. Die wichtigsten Bausteine:
| Baustein | Umsetzung |
|---|---|
| Authentifizierung | Entra-ID-JWT-Validierung, App-only (client_credentials) für den Connector, MSAL-Login für das Dashboard |
| Autorisierung | Segmentgenaue Pfad-Whitelist mit HTTP-Method-Matching und Resource-Mappings, fail-closed |
| Mandantentrennung | Entra-App-Rollen (Dev/Test/Prod) plus Routing über Datenbank/Mandant — Test kann strukturell nie Prod erreichen |
| Upstream-Auth | Pro Mandant konfigurierbar, Bearer oder HTTP Basic |
| Schutz vor Doppelbuchungen | Pflicht-Header Idempotency-Key bei jedem POST, Replay bei identischem Body, 409 bei abweichendem Body |
| Audit | Access-Log (EF Core/SQLite) mit Live-Stream (SSE), Response-Body-Einsicht und Diagnose bei falscher Token-Audience |
| Betrieb | Admin-Bereich mit Live-Reload, Whitelist, Sage-Mandanten und IP-Whitelist hinter WAF (Trusted-Proxy) |
| Ausrollung | Inno-Setup-Installer (self-contained), Linux-Dockerfile, DPAPI-verschlüsselte Secrets |
Technik
- Backend: .NET 10, ASP.NET Core mit Controllern, YARP als Forwarder, Kestrel
- Frontend: React, TypeScript und Vite im Security-Ops-Console-Design
- Persistenz: EF Core mit SQLite
- Entwicklung mit KI-Unterstützung nach dem Muster Spec → Plan → Subagent-Umsetzung → zweistufiges Review
Lektion 1: Entra-Audience-Fallen
Entra ID stellt die aud-Angabe im Token je nach App-Typ als App-ID-URI oder als nackte ClientId aus. Das führte zu einem produktiven 401 (IDX10214). Die Lösung war eine Liste gültiger Audiences statt eines Einzelwerts.
Lektion 2: Least Privilege konsequent umgesetzt
Eine einzelne App-Registrierung war ursprünglich API, SPA und Testclient zugleich. Sie wurde in ein 3-App-Modell getrennt: Gateway-API, Dashboard und Copilot-Connector als eigenständige Registrierungen.
Lektion 3: ein Mandant, eine App
Der ERP-Dienstleister wollte je Umgebung eine eigene Registrierung, damit ein Test-Request strukturell nie Prod treffen kann — diese Trennung wurde entsprechend umgesetzt.
Lektion 4: Verwechslungsgefahr bei GUIDs
ApplicationId (aud), ClientId (azp) und Object ID wurden während der Implementierung wiederholt vertauscht. Das Dashboard zeigt seither die unbekannte Audience direkt im Log an, statt nur einen generischen Fehler zu melden.
Lektion 5: ERP-Eigenheiten sind kein Proxy-Bug
Zwei Fälle zeigten, dass ein Fehler nicht automatisch am eigenen Code liegt:
- Sage liefert ohne den Parameter include=$children still leere Daten zurück, ohne Fehlermeldung.
- Ein selbstsigniertes Zertifikat der Sage-Instanz führte zu 502-Fehlern am Gateway.
Lektion 6: Duplikat-Risiko ernst nehmen
Sage hat keinen eingebauten Duplicate-Check. Deshalb wurde der Idempotency-Key als Pflicht-Header eingeführt — bewusst auch als Breaking Change gegenüber einer laxeren Ausgangsversion.
Lektion 7: Dependency-Hygiene und CVEs
Zwei Security-Audits behoben zusammen 7 CVEs, darunter einen als High eingestuften React-Router-CSRF-Fund. Zwei Versions-Pins sind bewusst dokumentiert begründet: Microsoft.OpenApi bleibt unter 3.x und TypeScript unter 7.x, weil ein Update jeweils den Build beziehungsweise das Linting bricht — eine bewusste, begründete Entscheidung statt eines übersehenen Findings.
Lektion 8: Barrierefreiheit nachgeprüft
Eine Fehlermeldung im Dashboard hatte im Light-Theme nur einen Kontrast von 1,31:1 — praktisch unlesbar. Nach der Korrektur liegt der Kontrast bei 12,8:1 (Light) beziehungsweise 14,6:1 (Dark).
Was bewusst nicht öffentlich ist
Firmen-, Domain- und Hostnamen, Tenant- und Client-IDs sowie Namen beteiligter Dienstleister und Ansprechpartner sind aus dieser Fallstudie bewusst entfernt.
Vergleichbares Projekt anfragen
Sagen Sie uns kurz, wie Ihre Umgebung aussieht — wir sagen Ihnen ehrlich, was ein vergleichbares Projekt bei Ihnen bedeuten würde.
