Sicherungscommit vor dem Aufraeumen des Repos, damit nachvollziehbar bleibt, welcher Stand vor der Bereinigung galt. Build gruen, 476 Tests gruen. Zwei Straenge, die sich ueber .csproj, Program.cs und appsettings.json ueberschneiden und darum gemeinsam abgelegt werden: Deploymentcenter-Integration (P3c, Plan D-0 bis D-5 code-seitig fertig): - Deploymentcenter.Client 2.5.0 als lokales Paket, Source-Mapping erweitert - DeploymentcenterOptions, DeploymentcenterErrorReporter, LicenseGate/LicenseCli - WatchdogHeartbeatService auf die Deploymentcenter-API umgestellt (version, os, checks, metrics, status stopped) - Security: MasterKeyResolver, SecretRedactor, FilePermissions - Directory.Build.props mit zentraler Version 0.1.0 (Packager-Versionsdisziplin) - deploy/: Packager-Vorlage und systemd-Unit; echte Zugangsdaten bleiben ueber .gitignore aussen vor - setup.json fuer die Erstinstallation - UMSETZUNGSPLAN-Deploymentcenter-Integration.md; ANALYSE-Linux-Portierung.md verweist auf den neuen Plan Einfenster-Shell (UI-Redesign): - ShellWindow + ShellNavModel als Seitenleisten-Shell - WindowMenuBar und WindowMenuModel entfallen - Modul- und Kernfenster auf die Shell-Einbettung angepasst Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
2.9 KiB
C#
65 lines
2.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
|
|
namespace PolyTrader.Core.Security
|
|
{
|
|
/// <summary>
|
|
/// Löst den AES-Master-Key auf, mit dem <see cref="SecretProtection"/> Secrets at-rest
|
|
/// verschlüsselt (D-5/D-10 aus dem Deploymentcenter-Umsetzungsplan).
|
|
///
|
|
/// Reihenfolge: Umgebungsvariable, dann eine vorhandene Schlüsseldatei, sonst wird ein neuer
|
|
/// erzeugt und geschrieben — das ist der Regelfall direkt nach <c>update-agent --action install</c>
|
|
/// auf einem frischen System, wo niemand von Hand einen Schlüssel eintippt.
|
|
///
|
|
/// **Die eine Regel, die nie verletzt werden darf:** Erzeugt wird AUSSCHLIESSLICH, wenn weder
|
|
/// Variable noch Datei existieren. Ein versehentliches Überschreiben würde jedes bereits
|
|
/// verschlüsselte Secret in der Datenbank unwiederbringlich unlesbar machen — der einzige
|
|
/// wirklich unumkehrbare Fehler in diesem gesamten Bereich. Ein zweiter Aufruf (jeder weitere
|
|
/// Programmstart) liest deshalb ausnahmslos die vorhandene Datei, statt neu zu erzeugen.
|
|
/// </summary>
|
|
public static class MasterKeyResolver
|
|
{
|
|
/// <param name="keyFilePath">Pfad der Schlüsseldatei (z.B. neben der Anwendung).</param>
|
|
/// <param name="readEnvironmentVariable">Liest z.B. POLYTRADER_MASTER_KEY. Injizierbar für Tests.</param>
|
|
/// <param name="onGenerated">Aufgerufen, wenn ein neuer Schlüssel erzeugt wurde (Pfad als Parameter).</param>
|
|
/// <param name="onWarning">Aufgerufen bei einem nicht fatalen Problem (z.B. Dateirechte, Erzeugung fehlgeschlagen).</param>
|
|
public static string? Resolve(
|
|
string keyFilePath,
|
|
Func<string?> readEnvironmentVariable,
|
|
Action<string>? onGenerated = null,
|
|
Action<string>? onWarning = null)
|
|
{
|
|
string? masterKey = readEnvironmentVariable();
|
|
if (!string.IsNullOrWhiteSpace(masterKey))
|
|
{
|
|
return masterKey;
|
|
}
|
|
|
|
if (File.Exists(keyFilePath))
|
|
{
|
|
return File.ReadAllText(keyFilePath).Trim();
|
|
}
|
|
|
|
try
|
|
{
|
|
byte[] keyBytes = RandomNumberGenerator.GetBytes(32); // 256-Bit-Schlüssel
|
|
string generated = Convert.ToBase64String(keyBytes);
|
|
|
|
File.WriteAllText(keyFilePath, generated);
|
|
FilePermissions.RestrictToOwner(keyFilePath, onWarning);
|
|
|
|
onGenerated?.Invoke(keyFilePath);
|
|
return generated;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Erzeugung darf den Start nie verhindern - ohne Master-Key laufen Secrets eben
|
|
// unverschlüsselt weiter (wie bisher ohne POLYTRADER_MASTER_KEY), statt abzustürzen.
|
|
onWarning?.Invoke($"Master-Key konnte nicht automatisch erzeugt werden: {ex.Message}.");
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|