Improve certificate path probing with demo UNC target and remove Sonic container scan.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-29 09:14:25 +02:00
co-authored by Cursor
parent f7e9bd43e2
commit f584fd68fe
16 changed files with 1026 additions and 999 deletions
+156 -23
View File
@@ -900,7 +900,7 @@ namespace ZA.CoreService.ESBCertificateManager
Label hint = new Label
{
Text = "Unterstützte Formate: .cer, .crt, .pem, .pfx",
Text = "Unterstützte Formate: .cer, .crt, .pem, .pfx, .p12",
ForeColor = MutedTextColor,
Font = new Font("Segoe UI", 8.5f),
AutoSize = true,
@@ -920,11 +920,11 @@ namespace ZA.CoreService.ESBCertificateManager
{
Title = "Zertifikatsdatei auswählen",
Filter =
"Unterstützte Zertifikatsdateien (*.cer;*.crt;*.pem;*.pfx)|*.cer;*.crt;*.pem;*.pfx|" +
"Unterstützte Zertifikatsdateien (*.cer;*.crt;*.pem;*.pfx;*.p12)|*.cer;*.crt;*.pem;*.pfx;*.p12|" +
"CER-Zertifikate (*.cer)|*.cer|" +
"CRT-Zertifikate (*.crt)|*.crt|" +
"PEM-Zertifikate (*.pem)|*.pem|" +
"PFX-Zertifikate (*.pfx)|*.pfx",
"PFX/P12-Zertifikate (*.pfx;*.p12)|*.pfx;*.p12",
FilterIndex = 1,
Multiselect = false,
CheckFileExists = true,
@@ -947,7 +947,7 @@ namespace ZA.CoreService.ESBCertificateManager
MessageBox.Show(
this,
"Bitte wähle eine Zertifikatsdatei in einem der folgenden Formate aus:\n" +
".cer, .crt, .pem oder .pfx",
".cer, .crt, .pem, .pfx oder .p12",
"Nicht unterstütztes Dateiformat",
MessageBoxButtons.OK,
MessageBoxIcon.Warning);
@@ -971,15 +971,17 @@ namespace ZA.CoreService.ESBCertificateManager
private CertificateInfo? TryReadSelectedCertificate(string selectedPath)
{
bool isPfx = Path.GetExtension(selectedPath)
.Equals(".pfx", StringComparison.OrdinalIgnoreCase);
string extension = Path.GetExtension(selectedPath);
bool needsPassword =
extension.Equals(".pfx", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".p12", StringComparison.OrdinalIgnoreCase);
try
{
// Zuerst ohne Passwort versuchen
return ReadCertificate(selectedPath, null);
}
catch (CryptographicException) when (isPfx)
catch (CryptographicException) when (needsPassword)
{
// Passwort nur abfragen, wenn das Laden ohne Passwort fehlgeschlagen ist
string? pfxPassword = PromptForPfxPassword();
@@ -1141,8 +1143,12 @@ namespace ZA.CoreService.ESBCertificateManager
string extension = Path.GetExtension(certificatePath);
using X509Certificate2 certificate =
bool passwordProtected =
extension.Equals(".pfx", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".p12", StringComparison.OrdinalIgnoreCase);
using X509Certificate2 certificate =
passwordProtected
? new X509Certificate2(
certificatePath,
pfxPassword,
@@ -1245,7 +1251,8 @@ namespace ZA.CoreService.ESBCertificateManager
".cer",
".crt",
".pem",
".pfx"
".pfx",
".p12"
};
return supportedExtensions.Contains(
@@ -1389,9 +1396,9 @@ namespace ZA.CoreService.ESBCertificateManager
});
dgvTargets.Columns.Add("Target", "Ziel");
dgvTargets.Columns.Add("Environment", "Umgebung");
dgvTargets.Columns.Add("Container", "Container / Service");
dgvTargets.Columns.Add("Tls", "TLS-Prüfung");
dgvTargets.Columns.Add("Path", "Zertifikat-Pfad");
dgvTargets.Columns.Add("Expiry", "Aktuelles Zertifikat");
dgvTargets.Columns.Add("Container", "Neustart-Container");
dgvTargets.Columns.Add("Status", "Status");
dgvTargets.CurrentCellDirtyStateChanged += (_, _) =>
@@ -1527,10 +1534,12 @@ namespace ZA.CoreService.ESBCertificateManager
"Bereitstellungsziele werden aus SQL geladen ...",
isError: false);
_loadedTargets =
IReadOnlyList<DeploymentTarget> fromSql =
await _deploymentTargetRepository.GetActiveAsync(
_settings.EnvironmentCode);
_loadedTargets = EnsureDemoTarget(fromSql);
BindTargetsToGrid(_loadedTargets);
if (_loadedTargets.Count == 0)
@@ -1542,9 +1551,38 @@ namespace ZA.CoreService.ESBCertificateManager
return;
}
// Demo: erstes Ziel vorauswählen
if (dgvTargets.Rows.Count > 0)
{
dgvTargets.Rows[0].Cells["Selected"].Value = true;
}
SetStatus(
$"{_loadedTargets.Count} Bereitstellungsziel(e) aus SQL geladen.",
$"{_loadedTargets.Count} Bereitstellungsziel(e) geladen"
+ (_settings.Demo.Enabled
? $" · Demo: {Path.Combine(_settings.Demo.TargetDirectory, _settings.Demo.TargetFileName)}"
: string.Empty),
isError: false);
await ProbeTargetCertificatesAsync();
}
catch (Exception ex) when (_settings.Demo.Enabled)
{
// Demo trotzdem möglich, wenn SQL kurz nicht erreichbar ist
_loadedTargets = EnsureDemoTarget([]);
BindTargetsToGrid(_loadedTargets);
if (dgvTargets.Rows.Count > 0)
{
dgvTargets.Rows[0].Cells["Selected"].Value = true;
}
SetStatus(
$"SQL-Ziele nicht geladen ({ex.Message}). "
+ "Demo-Pfad ist trotzdem verfügbar.",
isError: true);
await ProbeTargetCertificatesAsync();
}
catch
{
@@ -1565,23 +1603,118 @@ namespace ZA.CoreService.ESBCertificateManager
foreach (DeploymentTarget target in targets)
{
int tlsPort = target.TlsPort is > 0 ? target.TlsPort.Value : 443;
string tls = string.IsNullOrWhiteSpace(target.TlsHost)
? "—"
: $"{target.TlsHost}:{tlsPort}";
int rowIndex = dgvTargets.Rows.Add(
false,
target.Name,
target.Environment,
target.ContainerName,
tls,
target.FullTargetPath,
"wird geprüft …",
string.IsNullOrWhiteSpace(target.ContainerName)
? "—"
: target.ContainerName,
"Bereit");
dgvTargets.Rows[rowIndex].Tag = target;
}
}
private IReadOnlyList<DeploymentTarget> EnsureDemoTarget(
IReadOnlyList<DeploymentTarget> fromSql)
{
if (!_settings.Demo.Enabled)
{
return fromSql;
}
string directory = _settings.Demo.TargetDirectory.Trim();
string fileName = _settings.Demo.TargetFileName.Trim();
if (string.IsNullOrWhiteSpace(directory)
|| string.IsNullOrWhiteSpace(fileName))
{
return fromSql;
}
bool alreadyPresent = fromSql.Any(target =>
string.Equals(
target.TargetDirectory.TrimEnd('\\', '/'),
directory.TrimEnd('\\', '/'),
StringComparison.OrdinalIgnoreCase)
&& string.Equals(
target.CertificateFileName,
fileName,
StringComparison.OrdinalIgnoreCase));
if (alreadyPresent)
{
return fromSql;
}
List<DeploymentTarget> merged = [.. fromSql];
merged.Insert(
0,
new DeploymentTarget
{
Id = -1,
Name = _settings.Demo.DisplayName,
Environment = _settings.EnvironmentCode,
IsActive = true,
TargetDirectory = directory,
CertificateFileName = fileName,
BackupEnabled = true,
BackupDirectoryName = "Backup",
ContainerName = _settings.Demo.ContainerName,
RestartType = RestartType.None,
SonicConnectionName =
_settings.Demo.SonicConnectionName,
RestartTimeoutSeconds = 180
});
return merged;
}
private async Task ProbeTargetCertificatesAsync()
{
CertificateProbeService probe = new();
foreach (DataGridViewRow row in dgvTargets.Rows)
{
if (row.Tag is not DeploymentTarget target)
{
continue;
}
CertificateProbeResult result = await Task.Run(
() => probe.Probe(target.FullTargetPath));
if (IsDisposed || !dgvTargets.Columns.Contains("Expiry"))
{
return;
}
if (result.Success && result.Certificate is not null)
{
string expiry = result.Certificate.ValidUntil
.ToLocalTime()
.ToString("dd.MM.yyyy");
row.Cells["Expiry"].Value = result.Certificate.IsCurrentlyValid
? $"gültig bis {expiry}"
: $"abgelaufen {expiry}";
continue;
}
if (result.FileExists)
{
row.Cells["Expiry"].Value = "Datei da · Kennwort nötig";
continue;
}
row.Cells["Expiry"].Value = result.Success
? "Datei fehlt"
: "nicht erreichbar";
}
}
private List<DeploymentTarget> GetSelectedTargets()
{
List<DeploymentTarget> selected = [];