diff --git a/Fuchs/Docs/keyvault-dpapi-ocore-implementation.md b/Fuchs/Docs/keyvault-dpapi-ocore-implementation.md
deleted file mode 100644
index b087e9a..0000000
--- a/Fuchs/Docs/keyvault-dpapi-ocore-implementation.md
+++ /dev/null
@@ -1,933 +0,0 @@
-# Agent Instructions: Key Vault + DPAPI — Multi-App Architektur (15 Projekte, 1 Server)
-
-## Ausgangslage
-
-- 15 Anwendungen (Web Apps, Console Apps, Windows Services, Worker Services)
-- Alle laufen auf demselben Windows-Server
-- Ein gemeinsamer Azure Key Vault
-- Secrets sind aktuell in `appsettings.json`, `web.config` oder Umgebungsvariablen
-- Zwei bestehende Shared Projects: **OCORE** und **OCORE_web**
-
----
-
-## Aufteilung zwischen OCORE und OCORE_web
-
-```
-OCORE OCORE_web
-────────────────────────────── ──────────────────────────────
-Alles was NICHT Web-spezifisch Nur was WebApplicationBuilder
-ist. Funktioniert für: benötigt. Funktioniert für:
-
- - Console Apps - ASP.NET Core Web Apps
- - Windows Services - ASP.NET Core Web APIs
- - Worker Services - Razor Pages / MVC
- - WinForms / WPF
- - Web Apps (via OCORE_web)
-
-Enthält: Enthält:
- AppSecretsOptions SecretManagementWebExtensions
- DpapiSecretsCache (eine einzige Datei)
- DpapiCacheConfigurationSource
- KeyVaultSyncService
- PrefixKeyVaultSecretManager
- SecretManagementExtensions
- (IHostBuilder)
-```
-
-> OCORE_web referenziert OCORE — d.h. Web Apps brauchen nur OCORE_web
-> zu referenzieren, OCORE kommt transitiv mit.
-
----
-
-## Architektur-Übersicht
-
-```
-┌─────────────────────────────────────────────────────────────────┐
-│ Azure Key Vault │
-│ │
-│ crm--ConnectionStrings--Database → CRM Web App │
-│ crm--ExternalApi--ApiKey → CRM Web App │
-│ erp--ConnectionStrings--Database → ERP Web App │
-│ erp--Smtp--Password → ERP Web App │
-│ importer--ConnectionStrings--Db → Importer Console App │
-│ scheduler--ConnectionStrings--Db → Scheduler Windows Svc │
-│ ... │
-└──────────────────────────┬──────────────────────────────────────┘
- │ Managed Identity (Server-Level)
-┌──────────────────────────▼──────────────────────────────────────┐
-│ Windows Server │
-│ │
-│ C:\inetpub\crm\ secrets.cache (DPAPI, crm-Entropy) │
-│ C:\inetpub\erp\ secrets.cache (DPAPI, erp-Entropy) │
-│ C:\services\importer\ secrets.cache (DPAPI, importer-Entropy) │
-│ C:\services\scheduler\secrets.cache (DPAPI, scheduler-Entropy)│
-│ ... │
-│ │
-│ OCORE.dll ← alle 15 Apps referenzieren dies │
-│ OCORE_web.dll ← nur Web Apps referenzieren dies zusätzlich │
-└─────────────────────────────────────────────────────────────────┘
-```
-
-### Wichtige Designentscheidungen
-
-| Thema | Entscheidung | Begründung |
-|---|---|---|
-| Key Vault | Ein gemeinsamer | Einfacher zu verwalten, RBAC auf Secret-Ebene möglich |
-| Namespacing | `{appname}--{key}` Präfix | Verhindert Kollisionen zwischen Apps |
-| DPAPI Scope | `LocalMachine` + App-spezifische Entropy | Isolation: App A kann Cache von App B nicht lesen |
-| Basiscode | OCORE | Kein Web-Framework nötig, funktioniert überall |
-| Web-Extension | OCORE_web | Nur eine Datei, dünne Schicht über OCORE |
-| App-Identity | Eine Server-Managed-Identity | Alle Apps teilen dieselbe Identity, RBAC per Secret |
-
----
-
-## Teil 1: OCORE — NuGet-Pakete hinzufügen
-
-```bash
-cd OCORE
-dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
-dotnet add package Azure.Identity
-dotnet add package Azure.Security.KeyVault.Secrets
-dotnet add package Microsoft.Extensions.Hosting.Abstractions
-dotnet add package Microsoft.Extensions.Configuration.Abstractions
-dotnet add package Microsoft.Extensions.Logging.Abstractions
-```
-
----
-
-## Teil 2: OCORE — Dateien anlegen
-
-Neue Ordnerstruktur **innerhalb von OCORE** (bestehende Struktur bleibt unverändert):
-
-```
-OCORE/
- ... (bestehende Dateien)
- Secrets/
- Models/
- AppSecretsOptions.cs
- Cache/
- DpapiSecretsCache.cs
- DpapiCacheConfigurationSource.cs
- Sync/
- KeyVaultSyncService.cs
- Extensions/
- SecretManagementExtensions.cs ← IHostBuilder Extension + gemeinsame Logik
- PrefixKeyVaultSecretManager.cs
-```
-
----
-
-### 2.1 OCORE/Secrets/Models/AppSecretsOptions.cs
-
-```csharp
-namespace OCORE.Secrets.Models;
-
-public class AppSecretsOptions
-{
- public const string SectionName = "SecretManagement";
-
- ///
- /// Vollständige URI des Azure Key Vault.
- /// Beispiel: https://company-vault.vault.azure.net/
- ///
- public string VaultUri { get; set; } = string.Empty;
-
- ///
- /// App-Name als Präfix für alle Secrets in Key Vault.
- /// Beispiel: "crm" → Secret "crm--ConnectionStrings--Database"
- /// Nur Kleinbuchstaben und Bindestriche (Key Vault Konvention).
- ///
- public string AppName { get; set; } = string.Empty;
-
- ///
- /// Pfad zur DPAPI-Cache-Datei (relativ zum App-Verzeichnis).
- ///
- public string CacheFilePath { get; set; } = "secrets.cache";
-
- ///
- /// Sync-Intervall in Stunden.
- ///
- public int SyncIntervalHours { get; set; } = 6;
-
- ///
- /// Secret-Namen ohne App-Präfix.
- /// Beispiel: ["ConnectionStrings--Database", "ExternalApi--ApiKey"]
- ///
- public List ManagedSecretKeys { get; set; } = [];
-}
-```
-
----
-
-### 2.2 OCORE/Secrets/Cache/DpapiSecretsCache.cs
-
-```csharp
-using System.Security.Cryptography;
-using System.Text;
-using System.Text.Json;
-using Microsoft.Extensions.Logging;
-
-namespace OCORE.Secrets.Cache;
-
-public interface IDpapiSecretsCache
-{
- Task WriteAsync(Dictionary secrets);
- Task> ReadAsync();
- bool Exists();
-}
-
-public class DpapiSecretsCache : IDpapiSecretsCache
-{
- private readonly string _filePath;
- private readonly byte[] _entropy;
- private readonly ILogger _logger;
-
- ///
- /// Entropy wird aus AppName abgeleitet — verhindert dass eine andere App
- /// auf demselben Server den Cache dieser App lesen kann.
- ///
- public DpapiSecretsCache(string filePath, string appName, ILogger logger)
- {
- _filePath = filePath;
- _entropy = Encoding.UTF8.GetBytes($"dpapi-entropy-{appName}");
- _logger = logger;
- }
-
- public async Task WriteAsync(Dictionary secrets)
- {
- try
- {
- var json = JsonSerializer.SerializeToUtf8Bytes(secrets);
- var encrypted = ProtectedData.Protect(json, _entropy, DataProtectionScope.LocalMachine);
- await File.WriteAllBytesAsync(_filePath, encrypted);
- _logger.LogInformation("[OCORE.Secrets] DPAPI Cache geschrieben: {Count} Secrets → {Path}",
- secrets.Count, _filePath);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "[OCORE.Secrets] DPAPI Cache konnte nicht geschrieben werden: {Path}", _filePath);
- }
- }
-
- public async Task> ReadAsync()
- {
- if (!Exists()) return [];
-
- try
- {
- var encrypted = await File.ReadAllBytesAsync(_filePath);
- var decrypted = ProtectedData.Unprotect(encrypted, _entropy, DataProtectionScope.LocalMachine);
- return JsonSerializer.Deserialize>(decrypted) ?? [];
- }
- catch (CryptographicException ex)
- {
- _logger.LogError(ex,
- "[OCORE.Secrets] DPAPI Entschlüsselung fehlgeschlagen — AppName korrekt? {Path}", _filePath);
- return [];
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "[OCORE.Secrets] DPAPI Cache Lesefehler: {Path}", _filePath);
- return [];
- }
- }
-
- public bool Exists() => File.Exists(_filePath);
-}
-```
-
----
-
-### 2.3 OCORE/Secrets/Cache/DpapiCacheConfigurationSource.cs
-
-```csharp
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-
-namespace OCORE.Secrets.Cache;
-
-public class DpapiCacheConfigurationSource : IConfigurationSource
-{
- private readonly string _filePath;
- private readonly string _appName;
-
- public DpapiCacheConfigurationSource(string filePath, string appName)
- {
- _filePath = filePath;
- _appName = appName;
- }
-
- public IConfigurationProvider Build(IConfigurationBuilder builder)
- => new DpapiCacheConfigurationProvider(_filePath, _appName);
-}
-
-public class DpapiCacheConfigurationProvider : ConfigurationProvider
-{
- private readonly string _filePath;
- private readonly string _appName;
-
- public DpapiCacheConfigurationProvider(string filePath, string appName)
- {
- _filePath = filePath;
- _appName = appName;
- }
-
- public override void Load()
- {
- if (!File.Exists(_filePath)) return;
-
- try
- {
- using var loggerFactory = LoggerFactory.Create(b => b.AddConsole());
- var logger = loggerFactory.CreateLogger();
- var cache = new DpapiSecretsCache(_filePath, _appName, logger);
- var secrets = cache.ReadAsync().GetAwaiter().GetResult();
-
- // "crm--ConnectionStrings--Database" → "ConnectionStrings:Database"
- var prefix = $"{_appName}--";
- Data = secrets
- .Where(kvp => kvp.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
- .ToDictionary(
- kvp => kvp.Key[prefix.Length..].Replace("--", ":"),
- kvp => (string?)kvp.Value);
- }
- catch (Exception ex)
- {
- Console.WriteLine($"[OCORE.Secrets] DPAPI Cache Ladefehler: {ex.Message}");
- Data = [];
- }
- }
-}
-```
-
----
-
-### 2.4 OCORE/Secrets/Sync/KeyVaultSyncService.cs
-
-```csharp
-using Azure.Security.KeyVault.Secrets;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Options;
-using OCORE.Secrets.Cache;
-using OCORE.Secrets.Models;
-
-namespace OCORE.Secrets.Sync;
-
-///
-/// Läuft als BackgroundService in jedem Host-Typ:
-/// Web Apps, Console Apps, Windows Services, Worker Services.
-/// Synct Secrets aus Key Vault → DPAPI-Cache, periodisch.
-///
-public class KeyVaultSyncService : BackgroundService
-{
- private readonly SecretClient _secretClient;
- private readonly IDpapiSecretsCache _cache;
- private readonly IConfigurationRoot _configRoot;
- private readonly AppSecretsOptions _options;
- private readonly ILogger _logger;
-
- public KeyVaultSyncService(
- SecretClient secretClient,
- IDpapiSecretsCache cache,
- IConfiguration configuration,
- IOptions options,
- ILogger logger)
- {
- _secretClient = secretClient;
- _cache = cache;
- _configRoot = (IConfigurationRoot)configuration;
- _options = options.Value;
- _logger = logger;
- }
-
- protected override async Task ExecuteAsync(CancellationToken stoppingToken)
- {
- await SyncAsync(stoppingToken);
-
- using var timer = new PeriodicTimer(TimeSpan.FromHours(_options.SyncIntervalHours));
- while (await timer.WaitForNextTickAsync(stoppingToken))
- {
- await SyncAsync(stoppingToken);
- }
- }
-
- private async Task SyncAsync(CancellationToken cancellationToken)
- {
- _logger.LogInformation("[OCORE.Secrets:{App}] Key Vault Sync gestartet", _options.AppName);
- var synced = new Dictionary();
-
- foreach (var key in _options.ManagedSecretKeys)
- {
- var fullName = $"{_options.AppName}--{key}";
- try
- {
- var secret = await _secretClient.GetSecretAsync(fullName, version: null, cancellationToken);
- synced[fullName] = secret.Value.Value;
- }
- catch (Exception ex)
- {
- _logger.LogWarning("[OCORE.Secrets:{App}] Secret '{Name}' nicht geladen: {Error}",
- _options.AppName, fullName, ex.Message);
- }
- }
-
- if (synced.Count > 0)
- {
- await _cache.WriteAsync(synced);
- _configRoot.Reload();
- _logger.LogInformation("[OCORE.Secrets:{App}] Sync OK: {Count}/{Total}",
- _options.AppName, synced.Count, _options.ManagedSecretKeys.Count);
- }
- else
- {
- _logger.LogWarning("[OCORE.Secrets:{App}] Sync: Keine Secrets geladen — Cache unverändert",
- _options.AppName);
- }
- }
-}
-```
-
----
-
-### 2.5 OCORE/Secrets/Extensions/PrefixKeyVaultSecretManager.cs
-
-```csharp
-using Azure.Extensions.AspNetCore.Configuration.Secrets;
-using Azure.Security.KeyVault.Secrets;
-
-namespace OCORE.Secrets.Extensions;
-
-///
-/// Filtert beim Key Vault Laden nur Secrets mit dem App-Präfix
-/// und konvertiert den Namen in einen IConfiguration-Key.
-/// "crm--ConnectionStrings--Database" → "ConnectionStrings:Database"
-///
-public class PrefixKeyVaultSecretManager : KeyVaultSecretManager
-{
- private readonly string _prefix;
-
- public PrefixKeyVaultSecretManager(string appName)
- => _prefix = $"{appName}--";
-
- public override bool Load(SecretProperties secret)
- => secret.Name.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase);
-
- public override string GetKey(KeyVaultSecret secret)
- => secret.Name[_prefix.Length..].Replace("--", ":");
-}
-```
-
----
-
-### 2.6 OCORE/Secrets/Extensions/SecretManagementExtensions.cs
-
-```csharp
-using Azure.Identity;
-using Azure.Security.KeyVault.Secrets;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Hosting;
-using OCORE.Secrets.Cache;
-using OCORE.Secrets.Models;
-using OCORE.Secrets.Sync;
-
-namespace OCORE.Secrets.Extensions;
-
-public static class SecretManagementExtensions
-{
- ///
- /// Für Console Apps, Worker Services, Windows Services, WinForms/WPF.
- /// Verwendung: Host.CreateDefaultBuilder(args).AddSecretManagement()
- ///
- public static IHostBuilder AddSecretManagement(this IHostBuilder hostBuilder)
- {
- hostBuilder.ConfigureAppConfiguration((context, config) =>
- {
- var built = config.Build();
- var options = built.GetSection(AppSecretsOptions.SectionName).Get()
- ?? new AppSecretsOptions();
-
- if (!IsConfigured(options)) return;
-
- var cacheFilePath = ResolveCachePath(
- context.HostingEnvironment.ContentRootPath, options.CacheFilePath);
-
- // 1. DPAPI-Cache (Fallback)
- config.Add(new DpapiCacheConfigurationSource(cacheFilePath, options.AppName));
-
- // 2. Key Vault (primär — überschreibt Cache wenn erreichbar)
- TryAddKeyVault(config, options);
- });
-
- hostBuilder.ConfigureServices((context, services) =>
- {
- RegisterCoreServices(services, context.Configuration);
- });
-
- return hostBuilder;
- }
-
- ///
- /// Gemeinsame Service-Registrierung — wird von OCORE und OCORE_web verwendet.
- ///
- public static void RegisterCoreServices(IServiceCollection services, IConfiguration configuration)
- {
- var options = configuration
- .GetSection(AppSecretsOptions.SectionName)
- .Get() ?? new AppSecretsOptions();
-
- if (!IsConfigured(options)) return;
-
- services.Configure(
- configuration.GetSection(AppSecretsOptions.SectionName));
-
- services.AddSingleton(new SecretClient(
- new Uri(options.VaultUri),
- new DefaultAzureCredential()));
-
- services.AddSingleton(sp =>
- new DpapiSecretsCache(
- ResolveCachePath(AppContext.BaseDirectory, options.CacheFilePath),
- options.AppName,
- sp.GetRequiredService>()));
-
- services.AddHostedService();
- }
-
- public static void TryAddKeyVault(IConfigurationBuilder config, AppSecretsOptions options)
- {
- try
- {
- config.AddAzureKeyVault(
- new Uri(options.VaultUri),
- new DefaultAzureCredential(),
- new PrefixKeyVaultSecretManager(options.AppName));
- }
- catch (Exception ex)
- {
- Console.WriteLine($"[OCORE.Secrets] Key Vault nicht erreichbar: {ex.Message}");
- Console.WriteLine("[OCORE.Secrets] Starte mit DPAPI-Cache");
- }
- }
-
- public static bool IsConfigured(AppSecretsOptions options)
- => !string.IsNullOrEmpty(options.VaultUri) && !string.IsNullOrEmpty(options.AppName);
-
- public static string ResolveCachePath(string basePath, string cacheFilePath)
- => Path.IsPathRooted(cacheFilePath)
- ? cacheFilePath
- : Path.Combine(basePath, cacheFilePath);
-}
-```
-
----
-
-## Teil 3: OCORE_web — Einzige neue Datei
-
-OCORE_web referenziert OCORE bereits. Nur eine neue Datei hinzufügen:
-
-```
-OCORE_web/
- ... (bestehende Dateien)
- Secrets/
- SecretManagementWebExtensions.cs ← einzige neue Datei in OCORE_web
-```
-
-### 3.1 OCORE_web/Secrets/SecretManagementWebExtensions.cs
-
-```csharp
-using Microsoft.AspNetCore.Builder;
-using Microsoft.Extensions.Configuration;
-using OCORE.Secrets.Extensions;
-using OCORE.Secrets.Models;
-
-namespace OCORE_web.Secrets;
-
-public static class SecretManagementWebExtensions
-{
- ///
- /// Für ASP.NET Core Web Apps und Web APIs.
- /// Verwendung: builder.AddSecretManagement()
- ///
- public static WebApplicationBuilder AddSecretManagement(
- this WebApplicationBuilder builder)
- {
- var options = builder.Configuration
- .GetSection(AppSecretsOptions.SectionName)
- .Get() ?? new AppSecretsOptions();
-
- if (!SecretManagementExtensions.IsConfigured(options)) return builder;
-
- var cacheFilePath = SecretManagementExtensions.ResolveCachePath(
- builder.Environment.ContentRootPath, options.CacheFilePath);
-
- // 1. DPAPI-Cache (Fallback)
- builder.Configuration.Add(
- new OCORE.Secrets.Cache.DpapiCacheConfigurationSource(cacheFilePath, options.AppName));
-
- // 2. Key Vault (primär)
- SecretManagementExtensions.TryAddKeyVault(builder.Configuration, options);
-
- // 3. Services — aus OCORE (kein duplizierter Code)
- SecretManagementExtensions.RegisterCoreServices(builder.Services, builder.Configuration);
-
- return builder;
- }
-}
-```
-
----
-
-## Teil 4: Verwendung je App-Typ
-
-### Web App (referenziert OCORE_web)
-
-```csharp
-// Program.cs
-using OCORE_web.Secrets;
-
-var builder = WebApplication.CreateBuilder(args);
-builder.AddSecretManagement(); // ← aus OCORE_web
-
-var app = builder.Build();
-app.Run();
-```
-
-### Console App / Worker Service / Windows Service (referenziert OCORE)
-
-```csharp
-// Program.cs
-using OCORE.Secrets.Extensions;
-
-// Console / Worker
-var host = Host.CreateDefaultBuilder(args)
- .AddSecretManagement() // ← aus OCORE
- .ConfigureServices(services =>
- {
- services.AddHostedService();
- })
- .Build();
-
-await host.RunAsync();
-```
-
-```csharp
-// Windows Service — identisch, nur UseWindowsService() zusätzlich
-var host = Host.CreateDefaultBuilder(args)
- .UseWindowsService()
- .AddSecretManagement() // ← aus OCORE
- .ConfigureServices(services =>
- {
- services.AddHostedService();
- })
- .Build();
-
-await host.RunAsync();
-```
-
-### WinForms / WPF (referenziert OCORE)
-
-```csharp
-// Program.cs — Generic Host nachrüsten
-using OCORE.Secrets.Extensions;
-
-var host = Host.CreateDefaultBuilder()
- .AddSecretManagement() // ← aus OCORE
- .ConfigureServices(services =>
- {
- services.AddSingleton();
- })
- .Build();
-
-Application.Run(host.Services.GetRequiredService());
-```
-
----
-
-## Teil 5: appsettings.json (identisch für alle App-Typen)
-
-```json
-{
- "SecretManagement": {
- "VaultUri": "https://company-vault.vault.azure.net/",
- "AppName": "crm",
- "CacheFilePath": "secrets.cache",
- "SyncIntervalHours": 6,
- "ManagedSecretKeys": [
- "ConnectionStrings--Database",
- "ExternalApi--ApiKey"
- ]
- },
- "ConnectionStrings": {
- "Database": "MANAGED_BY_KEYVAULT"
- }
-}
-```
-
-> `AppName` ist der einzige Wert der sich zwischen den 15 Projekten unterscheidet.
-
----
-
-## Teil 6: Key Vault Naming-Konvention
-
-```
-Format: {appname}--{ConfigSection}--{ConfigKey}
-
-Web Apps:
- crm--ConnectionStrings--Database
- crm--ExternalApi--ApiKey
- erp--ConnectionStrings--Database
- erp--Smtp--Password
- portal--Auth--JwtSecret
-
-Non-Web Apps:
- importer--ConnectionStrings--Database
- scheduler--ConnectionStrings--Database
- scheduler--Smtp--Password
-```
-
-### Naming-Regeln
-
-- Nur alphanumerische Zeichen und einfache Bindestriche `-`
-- Hierarchie-Trenner: `--` (doppelter Bindestrich)
-- Kein Unterstrich, kein Punkt, kein Leerzeichen
-- Maximal 127 Zeichen
-
----
-
-## Teil 7: Einmalige Migration — Alle Apps
-
-### 7.1 Inventur-Script
-
-```powershell
-# inventarisierung.ps1
-param([string]$SolutionRoot = "C:\Projects\MySolution")
-
-$patterns = @(
- 'password\s*=\s*["\'][^"\']+["\']',
- 'connectionstring\s*=\s*["\'][^"\']+["\']',
- 'apikey\s*=\s*["\'][^"\']+["\']',
- 'secret\s*=\s*["\'][^"\']+["\']',
- 'pwd\s*=\s*[^;]+'
-)
-
-Get-ChildItem -Path $SolutionRoot -Recurse -Include "*.json","*.config","*.cs" |
- Where-Object { $_.FullName -notmatch '\\(bin|obj|\.git)\\' } |
- ForEach-Object {
- $file = $_
- $content = Get-Content $file.FullName -Raw -ErrorAction SilentlyContinue
- foreach ($pattern in $patterns) {
- if ($content -match $pattern) {
- Write-Host "⚠️ $($file.FullName)" -ForegroundColor Yellow
- break
- }
- }
- }
-```
-
-### 7.2 Migration Script
-
-```powershell
-# migrate-all-to-keyvault.ps1
-param([Parameter(Mandatory)][string]$VaultName)
-
-Connect-AzAccount
-
-$allSecrets = @{
- # Web Apps
- "crm--ConnectionStrings--Database" = "Server=PROD-SQL;Database=CrmDb;..."
- "crm--ExternalApi--ApiKey" = "crm-api-key"
- "erp--ConnectionStrings--Database" = "Server=PROD-SQL;Database=ErpDb;..."
- "erp--Smtp--Password" = "smtp-passwort"
- "portal--ConnectionStrings--Database" = "Server=PROD-SQL;Database=PortalDb;..."
- "portal--Auth--JwtSecret" = "jwt-secret"
-
- # Non-Web Apps (OCORE)
- "importer--ConnectionStrings--Database" = "Server=PROD-SQL;Database=ImportDb;..."
- "scheduler--ConnectionStrings--Database" = "Server=PROD-SQL;Database=SchedDb;..."
- "scheduler--Smtp--Password" = "smtp-passwort"
-
- # Weitere Apps ergänzen...
-}
-
-$total = $allSecrets.Count
-$done = 0
-
-foreach ($name in $allSecrets.Keys) {
- $value = ConvertTo-SecureString $allSecrets[$name] -AsPlainText -Force
- Set-AzKeyVaultSecret -VaultName $VaultName -Name $name -SecretValue $value
- $done++
- Write-Progress -Activity "Migriere Secrets" -Status "$name" -PercentComplete (($done/$total)*100)
- Write-Host "✅ [$done/$total] $name"
-}
-
-Write-Host "`nMigration abgeschlossen." -ForegroundColor Green
-```
-
-### 7.3 Bereinigung
-
-```powershell
-# bereinigung.ps1
-$projektOrdner = @(
- "C:\Projects\CRM",
- "C:\Projects\ERP",
- "C:\Projects\Portal",
- "C:\Projects\Importer",
- "C:\Projects\Scheduler"
- # Alle Pfade eintragen
-)
-
-foreach ($ordner in $projektOrdner) {
- $file = Join-Path $ordner "appsettings.json"
- if (Test-Path $file) {
- $json = Get-Content $file -Raw | ConvertFrom-Json
- if ($json.ConnectionStrings) {
- $json.ConnectionStrings.PSObject.Properties |
- ForEach-Object { $_.Value = "MANAGED_BY_KEYVAULT" }
- }
- $json | ConvertTo-Json -Depth 10 | Set-Content $file
- Write-Host "✅ Bereinigt: $file"
- }
-}
-```
-
----
-
-## Teil 8: Server-Setup (einmalig)
-
-### 8.1 Managed Identity + Key Vault Zugriff
-
-```powershell
-# server-setup.ps1 — einmalig als Admin
-
-# Managed Identity der VM abrufen
-$identity = Get-AzVM -ResourceGroupName "MyRG" -Name "PROD-SERVER-01"
-$objectId = $identity.Identity.PrincipalId
-
-# Key Vault: Server darf lesen, nicht schreiben
-Set-AzKeyVaultAccessPolicy `
- -VaultName "company-vault" `
- -ObjectId $objectId `
- -PermissionsToSecrets Get, List
-
-Write-Host "✅ Key Vault Zugriff konfiguriert für Object ID: $objectId"
-```
-
-### 8.2 Schreibrechte für Cache-Dateien
-
-```powershell
-# Für IIS Web Apps (App Pool User)
-$webApps = @("CRM", "ERP", "Portal")
-foreach ($pool in $webApps) {
- $appPath = "C:\inetpub\$pool"
- $user = "IIS AppPool\$pool"
- $acl = Get-Acl $appPath
- $rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
- $user, "Modify", "ContainerInherit,ObjectInherit", "None", "Allow")
- $acl.SetAccessRule($rule)
- Set-Acl -Path $appPath -AclObject $acl
- Write-Host "✅ Web App Schreibrecht: $user → $appPath"
-}
-
-# Für Windows Services / Console Apps (LocalSystem oder dedizierter Service-User)
-$serviceApps = @("Importer", "Scheduler")
-foreach ($svc in $serviceApps) {
- $appPath = "C:\services\$svc"
- # LocalSystem hat standardmäßig Schreibrecht — nur prüfen ob Ordner existiert
- if (-not (Test-Path $appPath)) {
- New-Item -ItemType Directory -Path $appPath
- Write-Host "✅ Service-Ordner erstellt: $appPath"
- }
-}
-```
-
----
-
-## Teil 9: .gitignore (Solution-Level)
-
-```gitignore
-# Secret Management
-secrets.cache
-**/secrets.cache
-
-# Konfigurationsdateien mit echten Werten
-appsettings.Production.json
-appsettings.Staging.json
-
-# Zertifikate
-*.pfx
-*.p12
-*.key
-```
-
----
-
-## Teil 10: Pro-Projekt Checkliste
-
-### Web Apps (OCORE_web)
-
-```
-[ ] 1. AppName festlegen (kurz, eindeutig, Kleinbuchstaben)
-[ ] 2. OCORE_web bereits referenziert (transitiv: OCORE kommt mit)
-[ ] 3. Program.cs:
- using OCORE_web.Secrets;
- builder.AddSecretManagement();
-[ ] 4. appsettings.json: SecretManagement-Block mit korrektem AppName
-[ ] 5. ManagedSecretKeys vollständig auflisten
-[ ] 6. Secrets in Key Vault mit {appname}-- Präfix vorhanden
-[ ] 7. Klartext-Secrets aus appsettings.json / web.config entfernt
-[ ] 8. secrets.cache in .gitignore
-[ ] 9. Logs nach Deploy prüfen:
- "[OCORE.Secrets:crm] Sync OK" → ✅
- "[OCORE.Secrets:crm] Key Vault nicht erreichbar" → ❌
-```
-
-### Non-Web Apps (OCORE)
-
-```
-[ ] 1. AppName festlegen (kurz, eindeutig, Kleinbuchstaben)
-[ ] 2. OCORE bereits referenziert (vorhanden)
-[ ] 3. Program.cs:
- using OCORE.Secrets.Extensions;
- Host.CreateDefaultBuilder(args).AddSecretManagement()
- (Windows Service: zusätzlich .UseWindowsService())
-[ ] 4. appsettings.json: SecretManagement-Block mit korrektem AppName
-[ ] 5. ManagedSecretKeys vollständig auflisten
-[ ] 6. Secrets in Key Vault mit {appname}-- Präfix vorhanden
-[ ] 7. Klartext-Secrets aus Konfigurationsdateien entfernt
-[ ] 8. secrets.cache in .gitignore
-[ ] 9. Logs prüfen nach erstem Start
-```
-
----
-
-## Zusammenfassung der Artefakte
-
-```
-OCORE/Secrets/ ← Einmalig anlegen
- Models/AppSecretsOptions.cs
- Cache/DpapiSecretsCache.cs
- Cache/DpapiCacheConfigurationSource.cs
- Sync/KeyVaultSyncService.cs
- Extensions/PrefixKeyVaultSecretManager.cs
- Extensions/SecretManagementExtensions.cs ← IHostBuilder + shared helpers
-
-OCORE_web/Secrets/ ← Einmalig anlegen (1 Datei)
- SecretManagementWebExtensions.cs ← WebApplicationBuilder Extension
-
-Pro Web-Projekt (15x oder subset):
- Program.cs → +2 Zeilen (using + builder.AddSecretManagement())
- appsettings.json → SecretManagement-Block, keine Secrets mehr
-
-Pro Non-Web-Projekt:
- Program.cs → +2 Zeilen (using + .AddSecretManagement())
- appsettings.json → SecretManagement-Block, keine Secrets mehr
-
-Scripts/
- inventarisierung.ps1 ← Wo liegen aktuell Secrets?
- migrate-all-to-keyvault.ps1 ← Einmalige Migration aller Apps
- bereinigung.ps1 ← Platzhalter setzen nach Migration
- server-setup.ps1 ← Managed Identity + ACLs (einmalig)
-```