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:
@@ -268,12 +268,35 @@ BEGIN
|
|||||||
1;
|
1;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
|
/* Demo-Pfad für Zertifikat-Austausch (UNC server.p12) – immer sicherstellen */
|
||||||
|
DECLARE @DemoDirectory [nvarchar](500) =
|
||||||
|
N'\\dekun-painwbdet\e$\proalpha-inwb\test\Containers\proalpha-test.ct-ZADBService\certs';
|
||||||
|
DECLARE @DemoFileName [nvarchar](260) = N'server.p12';
|
||||||
|
|
||||||
|
UPDATE [dbo].[CertificateTarget]
|
||||||
|
SET
|
||||||
|
[TargetDirectory] = @DemoDirectory,
|
||||||
|
[TargetFileName] = @DemoFileName,
|
||||||
|
[BackupEnabled] = 1,
|
||||||
|
[BackupDirectoryName] = N'Backup',
|
||||||
|
[IsActive] = 1,
|
||||||
|
[ModifiedDateTime] = SYSUTCDATETIME(),
|
||||||
|
[ModifiedBy] = SUSER_SNAME()
|
||||||
|
WHERE [SonicContainerId] = @SonicContainerId
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
[TargetFileName] IN (N'server.crt', N'server.p12')
|
||||||
|
OR [TargetDirectory] LIKE N'C:\ESB\Certificates%'
|
||||||
|
);
|
||||||
|
|
||||||
IF NOT EXISTS
|
IF NOT EXISTS
|
||||||
(
|
(
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM [dbo].[CertificateTarget] AS target
|
FROM [dbo].[CertificateTarget] AS target
|
||||||
WHERE target.[SonicContainerId] = @SonicContainerId
|
WHERE target.[SonicContainerId] = @SonicContainerId
|
||||||
AND target.[TargetFileName] = N'server.crt'
|
AND target.[TargetDirectory] = @DemoDirectory
|
||||||
|
AND target.[TargetFileName] = @DemoFileName
|
||||||
|
AND target.[IsActive] = 1
|
||||||
)
|
)
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO [dbo].[CertificateTarget]
|
INSERT INTO [dbo].[CertificateTarget]
|
||||||
@@ -290,8 +313,8 @@ BEGIN
|
|||||||
VALUES
|
VALUES
|
||||||
(
|
(
|
||||||
@SonicContainerId,
|
@SonicContainerId,
|
||||||
N'C:\ESB\Certificates',
|
@DemoDirectory,
|
||||||
N'server.crt',
|
@DemoFileName,
|
||||||
1,
|
1,
|
||||||
N'Backup',
|
N'Backup',
|
||||||
1,
|
1,
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
COMMENT: Stellt den Demo-Zertifikatspfad für DE-Test / ct-ZADBService sicher.
|
||||||
|
Kann allein gegen eine bestehende EsbZertifikatManager-DB laufen
|
||||||
|
(ohne Drop/Create). EnvironmentCode muss TEST sein (appsettings).
|
||||||
|
*/
|
||||||
|
USE [EsbZertifikatManager];
|
||||||
|
GO
|
||||||
|
|
||||||
|
SET NOCOUNT ON;
|
||||||
|
SET XACT_ABORT ON;
|
||||||
|
|
||||||
|
DECLARE @SonicContainerId [int];
|
||||||
|
DECLARE @TargetDirectory [nvarchar](500) =
|
||||||
|
N'\\dekun-painwbdet\e$\proalpha-inwb\test\Containers\proalpha-test.ct-ZADBService\certs';
|
||||||
|
DECLARE @TargetFileName [nvarchar](260) = N'server.p12';
|
||||||
|
|
||||||
|
SELECT @SonicContainerId = container.[SonicContainerId]
|
||||||
|
FROM [dbo].[SonicContainer] AS container
|
||||||
|
INNER JOIN [dbo].[SonicConnection] AS connection
|
||||||
|
ON connection.[SonicConnectionId] = container.[SonicConnectionId]
|
||||||
|
INNER JOIN [dbo].[Environment] AS environment
|
||||||
|
ON environment.[EnvironmentId] = connection.[EnvironmentId]
|
||||||
|
WHERE environment.[EnvironmentCode] = N'TEST'
|
||||||
|
AND connection.[ConnectionName] = N'DE-Test'
|
||||||
|
AND container.[ContainerName] = N'ct-ZADBService';
|
||||||
|
|
||||||
|
IF @SonicContainerId IS NULL
|
||||||
|
BEGIN
|
||||||
|
THROW 50010,
|
||||||
|
N'Demo-Pfad: Container ct-ZADBService / DE-Test / TEST nicht gefunden. Zuerst 03_Seed.sql ausführen.',
|
||||||
|
1;
|
||||||
|
END;
|
||||||
|
|
||||||
|
/* Alte Platzhalter am gleichen Container auf Demo-Pfad umbiegen */
|
||||||
|
UPDATE [dbo].[CertificateTarget]
|
||||||
|
SET
|
||||||
|
[TargetDirectory] = @TargetDirectory,
|
||||||
|
[TargetFileName] = @TargetFileName,
|
||||||
|
[BackupEnabled] = 1,
|
||||||
|
[BackupDirectoryName] = N'Backup',
|
||||||
|
[IsActive] = 1,
|
||||||
|
[ModifiedDateTime] = SYSUTCDATETIME(),
|
||||||
|
[ModifiedBy] = SUSER_SNAME()
|
||||||
|
WHERE [SonicContainerId] = @SonicContainerId
|
||||||
|
AND
|
||||||
|
(
|
||||||
|
[TargetFileName] IN (N'server.crt', N'server.p12')
|
||||||
|
OR [TargetDirectory] LIKE N'C:\ESB\Certificates%'
|
||||||
|
);
|
||||||
|
|
||||||
|
IF NOT EXISTS
|
||||||
|
(
|
||||||
|
SELECT 1
|
||||||
|
FROM [dbo].[CertificateTarget] AS target
|
||||||
|
WHERE target.[SonicContainerId] = @SonicContainerId
|
||||||
|
AND target.[TargetDirectory] = @TargetDirectory
|
||||||
|
AND target.[TargetFileName] = @TargetFileName
|
||||||
|
AND target.[IsActive] = 1
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO [dbo].[CertificateTarget]
|
||||||
|
(
|
||||||
|
[SonicContainerId],
|
||||||
|
[TargetDirectory],
|
||||||
|
[TargetFileName],
|
||||||
|
[BackupEnabled],
|
||||||
|
[BackupDirectoryName],
|
||||||
|
[IsActive],
|
||||||
|
[CreationDateTime],
|
||||||
|
[CreatedBy]
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@SonicContainerId,
|
||||||
|
@TargetDirectory,
|
||||||
|
@TargetFileName,
|
||||||
|
1,
|
||||||
|
N'Backup',
|
||||||
|
1,
|
||||||
|
SYSUTCDATETIME(),
|
||||||
|
SUSER_SNAME()
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
target.[CertificateTargetId],
|
||||||
|
company.[CompanyCode],
|
||||||
|
environment.[EnvironmentCode],
|
||||||
|
connection.[ConnectionName],
|
||||||
|
container.[ContainerName],
|
||||||
|
target.[TargetDirectory],
|
||||||
|
target.[TargetFileName],
|
||||||
|
target.[IsActive]
|
||||||
|
FROM [dbo].[CertificateTarget] AS target
|
||||||
|
INNER JOIN [dbo].[SonicContainer] AS container
|
||||||
|
ON container.[SonicContainerId] = target.[SonicContainerId]
|
||||||
|
INNER JOIN [dbo].[SonicConnection] AS connection
|
||||||
|
ON connection.[SonicConnectionId] = container.[SonicConnectionId]
|
||||||
|
INNER JOIN [dbo].[Environment] AS environment
|
||||||
|
ON environment.[EnvironmentId] = connection.[EnvironmentId]
|
||||||
|
INNER JOIN [dbo].[Company] AS company
|
||||||
|
ON company.[CompanyId] = container.[CompanyId]
|
||||||
|
WHERE container.[SonicContainerId] = @SonicContainerId;
|
||||||
|
|
||||||
|
PRINT N'Demo-Pfad ist aktiv: \\dekun-painwbdet\e$\...\certs\server.p12';
|
||||||
|
GO
|
||||||
@@ -32,9 +32,20 @@ cd Database\AlwaysEncrypted
|
|||||||
| Environment | TEST2, TEST, PROD |
|
| Environment | TEST2, TEST, PROD |
|
||||||
| SonicConnection | DE-Test → `tcp://dekun-painwbdet:13070`, Domain `proalpha-test`, Env TEST |
|
| SonicConnection | DE-Test → `tcp://dekun-painwbdet:13070`, Domain `proalpha-test`, Env TEST |
|
||||||
| SonicContainer | ct-ZADBService (Company DE) |
|
| SonicContainer | ct-ZADBService (Company DE) |
|
||||||
| CertificateTarget | `C:\ESB\Certificates\server.crt` (Platzhalter) |
|
| CertificateTarget | Demo-UNC `\\dekun-painwbdet\e$\...\certs\server.p12` |
|
||||||
| SonicCredential | **nicht** geseedet (Setup-Wizard / AE) |
|
| SonicCredential | **nicht** geseedet (Setup-Wizard / AE) |
|
||||||
|
|
||||||
|
## Demo-Pfad nachziehen (bestehende DB)
|
||||||
|
|
||||||
|
Wenn der alte Platzhalter noch in SQL liegt:
|
||||||
|
|
||||||
|
```text
|
||||||
|
04_EnsureDemoPath.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
Zusätzlich: in `appsettings.json` ist `"Demo": { "Enabled": true, ... }` –
|
||||||
|
die App zeigt den Demo-Pfad auch ohne SQL-Update in der Zielliste (Kopieren testen).
|
||||||
|
|
||||||
## Always Encrypted
|
## Always Encrypted
|
||||||
|
|
||||||
| Objekt | Name |
|
| Objekt | Name |
|
||||||
|
|||||||
@@ -900,7 +900,7 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
|
|
||||||
Label hint = new Label
|
Label hint = new Label
|
||||||
{
|
{
|
||||||
Text = "Unterstützte Formate: .cer, .crt, .pem, .pfx",
|
Text = "Unterstützte Formate: .cer, .crt, .pem, .pfx, .p12",
|
||||||
ForeColor = MutedTextColor,
|
ForeColor = MutedTextColor,
|
||||||
Font = new Font("Segoe UI", 8.5f),
|
Font = new Font("Segoe UI", 8.5f),
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
@@ -920,11 +920,11 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
{
|
{
|
||||||
Title = "Zertifikatsdatei auswählen",
|
Title = "Zertifikatsdatei auswählen",
|
||||||
Filter =
|
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|" +
|
"CER-Zertifikate (*.cer)|*.cer|" +
|
||||||
"CRT-Zertifikate (*.crt)|*.crt|" +
|
"CRT-Zertifikate (*.crt)|*.crt|" +
|
||||||
"PEM-Zertifikate (*.pem)|*.pem|" +
|
"PEM-Zertifikate (*.pem)|*.pem|" +
|
||||||
"PFX-Zertifikate (*.pfx)|*.pfx",
|
"PFX/P12-Zertifikate (*.pfx;*.p12)|*.pfx;*.p12",
|
||||||
FilterIndex = 1,
|
FilterIndex = 1,
|
||||||
Multiselect = false,
|
Multiselect = false,
|
||||||
CheckFileExists = true,
|
CheckFileExists = true,
|
||||||
@@ -947,7 +947,7 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
MessageBox.Show(
|
MessageBox.Show(
|
||||||
this,
|
this,
|
||||||
"Bitte wähle eine Zertifikatsdatei in einem der folgenden Formate aus:\n" +
|
"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",
|
"Nicht unterstütztes Dateiformat",
|
||||||
MessageBoxButtons.OK,
|
MessageBoxButtons.OK,
|
||||||
MessageBoxIcon.Warning);
|
MessageBoxIcon.Warning);
|
||||||
@@ -971,15 +971,17 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
|
|
||||||
private CertificateInfo? TryReadSelectedCertificate(string selectedPath)
|
private CertificateInfo? TryReadSelectedCertificate(string selectedPath)
|
||||||
{
|
{
|
||||||
bool isPfx = Path.GetExtension(selectedPath)
|
string extension = Path.GetExtension(selectedPath);
|
||||||
.Equals(".pfx", StringComparison.OrdinalIgnoreCase);
|
bool needsPassword =
|
||||||
|
extension.Equals(".pfx", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| extension.Equals(".p12", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Zuerst ohne Passwort versuchen
|
// Zuerst ohne Passwort versuchen
|
||||||
return ReadCertificate(selectedPath, null);
|
return ReadCertificate(selectedPath, null);
|
||||||
}
|
}
|
||||||
catch (CryptographicException) when (isPfx)
|
catch (CryptographicException) when (needsPassword)
|
||||||
{
|
{
|
||||||
// Passwort nur abfragen, wenn das Laden ohne Passwort fehlgeschlagen ist
|
// Passwort nur abfragen, wenn das Laden ohne Passwort fehlgeschlagen ist
|
||||||
string? pfxPassword = PromptForPfxPassword();
|
string? pfxPassword = PromptForPfxPassword();
|
||||||
@@ -1141,8 +1143,12 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
|
|
||||||
string extension = Path.GetExtension(certificatePath);
|
string extension = Path.GetExtension(certificatePath);
|
||||||
|
|
||||||
using X509Certificate2 certificate =
|
bool passwordProtected =
|
||||||
extension.Equals(".pfx", StringComparison.OrdinalIgnoreCase)
|
extension.Equals(".pfx", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| extension.Equals(".p12", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
using X509Certificate2 certificate =
|
||||||
|
passwordProtected
|
||||||
? new X509Certificate2(
|
? new X509Certificate2(
|
||||||
certificatePath,
|
certificatePath,
|
||||||
pfxPassword,
|
pfxPassword,
|
||||||
@@ -1245,7 +1251,8 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
".cer",
|
".cer",
|
||||||
".crt",
|
".crt",
|
||||||
".pem",
|
".pem",
|
||||||
".pfx"
|
".pfx",
|
||||||
|
".p12"
|
||||||
};
|
};
|
||||||
|
|
||||||
return supportedExtensions.Contains(
|
return supportedExtensions.Contains(
|
||||||
@@ -1389,9 +1396,9 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
});
|
});
|
||||||
|
|
||||||
dgvTargets.Columns.Add("Target", "Ziel");
|
dgvTargets.Columns.Add("Target", "Ziel");
|
||||||
dgvTargets.Columns.Add("Environment", "Umgebung");
|
dgvTargets.Columns.Add("Path", "Zertifikat-Pfad");
|
||||||
dgvTargets.Columns.Add("Container", "Container / Service");
|
dgvTargets.Columns.Add("Expiry", "Aktuelles Zertifikat");
|
||||||
dgvTargets.Columns.Add("Tls", "TLS-Prüfung");
|
dgvTargets.Columns.Add("Container", "Neustart-Container");
|
||||||
dgvTargets.Columns.Add("Status", "Status");
|
dgvTargets.Columns.Add("Status", "Status");
|
||||||
|
|
||||||
dgvTargets.CurrentCellDirtyStateChanged += (_, _) =>
|
dgvTargets.CurrentCellDirtyStateChanged += (_, _) =>
|
||||||
@@ -1527,10 +1534,12 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
"Bereitstellungsziele werden aus SQL geladen ...",
|
"Bereitstellungsziele werden aus SQL geladen ...",
|
||||||
isError: false);
|
isError: false);
|
||||||
|
|
||||||
_loadedTargets =
|
IReadOnlyList<DeploymentTarget> fromSql =
|
||||||
await _deploymentTargetRepository.GetActiveAsync(
|
await _deploymentTargetRepository.GetActiveAsync(
|
||||||
_settings.EnvironmentCode);
|
_settings.EnvironmentCode);
|
||||||
|
|
||||||
|
_loadedTargets = EnsureDemoTarget(fromSql);
|
||||||
|
|
||||||
BindTargetsToGrid(_loadedTargets);
|
BindTargetsToGrid(_loadedTargets);
|
||||||
|
|
||||||
if (_loadedTargets.Count == 0)
|
if (_loadedTargets.Count == 0)
|
||||||
@@ -1542,9 +1551,38 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Demo: erstes Ziel vorauswählen
|
||||||
|
if (dgvTargets.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
dgvTargets.Rows[0].Cells["Selected"].Value = true;
|
||||||
|
}
|
||||||
|
|
||||||
SetStatus(
|
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);
|
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
|
catch
|
||||||
{
|
{
|
||||||
@@ -1565,23 +1603,118 @@ namespace ZA.CoreService.ESBCertificateManager
|
|||||||
|
|
||||||
foreach (DeploymentTarget target in targets)
|
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(
|
int rowIndex = dgvTargets.Rows.Add(
|
||||||
false,
|
false,
|
||||||
target.Name,
|
target.Name,
|
||||||
target.Environment,
|
target.FullTargetPath,
|
||||||
target.ContainerName,
|
"wird geprüft …",
|
||||||
tls,
|
string.IsNullOrWhiteSpace(target.ContainerName)
|
||||||
|
? "—"
|
||||||
|
: target.ContainerName,
|
||||||
"Bereit");
|
"Bereit");
|
||||||
|
|
||||||
dgvTargets.Rows[rowIndex].Tag = target;
|
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()
|
private List<DeploymentTarget> GetSelectedTargets()
|
||||||
{
|
{
|
||||||
List<DeploymentTarget> selected = [];
|
List<DeploymentTarget> selected = [];
|
||||||
|
|||||||
@@ -11,6 +11,31 @@ public sealed class AppSettings
|
|||||||
public RuntimeSettings Runtime { get; set; } = new();
|
public RuntimeSettings Runtime { get; set; } = new();
|
||||||
|
|
||||||
public AlwaysEncryptedSettings AlwaysEncrypted { get; set; } = new();
|
public AlwaysEncryptedSettings AlwaysEncrypted { get; set; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wenn aktiv, erscheint der Demo-UNC-Pfad auch ohne SQL-Update in der Zielliste.
|
||||||
|
/// </summary>
|
||||||
|
public DemoSettings Demo { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class DemoSettings
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; } = true;
|
||||||
|
|
||||||
|
public string TargetDirectory { get; set; } =
|
||||||
|
DemoCertificatePath.Directory;
|
||||||
|
|
||||||
|
public string TargetFileName { get; set; } =
|
||||||
|
DemoCertificatePath.FileName;
|
||||||
|
|
||||||
|
public string ContainerName { get; set; } =
|
||||||
|
"ct-ZADBService";
|
||||||
|
|
||||||
|
public string DisplayName { get; set; } =
|
||||||
|
"DE / Demo ZADBService";
|
||||||
|
|
||||||
|
public string SonicConnectionName { get; set; } =
|
||||||
|
"DE-Test";
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DatabaseSettings
|
public sealed class DatabaseSettings
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
namespace ZA.CoreService.ESBCertificateManager.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fester Demo-Pfad für den ersten Zertifikat-Austausch-Test (TEST / DE).
|
||||||
|
/// </summary>
|
||||||
|
public static class DemoCertificatePath
|
||||||
|
{
|
||||||
|
public const string Directory =
|
||||||
|
@"\\dekun-painwbdet\e$\proalpha-inwb\test\Containers\proalpha-test.ct-ZADBService\certs";
|
||||||
|
|
||||||
|
public const string FileName = "server.p12";
|
||||||
|
|
||||||
|
public static string FullPath =>
|
||||||
|
Path.Combine(Directory, FileName);
|
||||||
|
}
|
||||||
@@ -9,6 +9,10 @@ public sealed class DeploymentTarget
|
|||||||
public required string TargetDirectory { get; init; }
|
public required string TargetDirectory { get; init; }
|
||||||
public required string CertificateFileName { get; init; }
|
public required string CertificateFileName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Vollständiger Dateipfad (UNC oder lokal) zur Zieldatei.</summary>
|
||||||
|
public string FullTargetPath =>
|
||||||
|
Path.Combine(TargetDirectory, CertificateFileName);
|
||||||
|
|
||||||
public bool BackupEnabled { get; init; } = true;
|
public bool BackupEnabled { get; init; } = true;
|
||||||
|
|
||||||
public string BackupDirectoryName { get; init; } = "Backup";
|
public string BackupDirectoryName { get; init; } = "Backup";
|
||||||
|
|||||||
@@ -63,8 +63,11 @@ public sealed class CertificateTargetOption
|
|||||||
|
|
||||||
public required string CompanyCode { get; init; }
|
public required string CompanyCode { get; init; }
|
||||||
|
|
||||||
|
public string FullTargetPath =>
|
||||||
|
Path.Combine(TargetDirectory, TargetFileName);
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{CompanyCode} / {ContainerName}: {TargetDirectory}\\{TargetFileName}";
|
return $"{CompanyCode} / {ContainerName}: {FullTargetPath}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using ZA.CoreService.ESBCertificateManager.Models;
|
||||||
|
|
||||||
|
namespace ZA.CoreService.ESBCertificateManager.Services;
|
||||||
|
|
||||||
|
public sealed class CertificateProbeService
|
||||||
|
{
|
||||||
|
public static readonly string[] SupportedExtensions =
|
||||||
|
[
|
||||||
|
".cer",
|
||||||
|
".crt",
|
||||||
|
".pem",
|
||||||
|
".pfx",
|
||||||
|
".p12"
|
||||||
|
];
|
||||||
|
|
||||||
|
public CertificateProbeResult Probe(
|
||||||
|
string certificatePath,
|
||||||
|
Func<string?>? passwordPrompt = null)
|
||||||
|
{
|
||||||
|
string? normalized = NormalizePath(certificatePath);
|
||||||
|
|
||||||
|
if (normalized is null)
|
||||||
|
{
|
||||||
|
return CertificateProbeResult.Failed(
|
||||||
|
"Kein gültiger Zertifikatspfad angegeben.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsSupportedExtension(normalized))
|
||||||
|
{
|
||||||
|
return CertificateProbeResult.Failed(
|
||||||
|
"Nicht unterstütztes Format. Erlaubt: "
|
||||||
|
+ string.Join(", ", SupportedExtensions));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(normalized))
|
||||||
|
{
|
||||||
|
return CertificateProbeResult.Missing(
|
||||||
|
normalized,
|
||||||
|
"Am Zielpfad liegt noch keine Zertifikatsdatei.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CertificateInfo info = ReadCertificate(normalized, password: null);
|
||||||
|
return CertificateProbeResult.Found(normalized, info);
|
||||||
|
}
|
||||||
|
catch (CryptographicException) when (IsPasswordProtected(normalized))
|
||||||
|
{
|
||||||
|
if (passwordPrompt is null)
|
||||||
|
{
|
||||||
|
return CertificateProbeResult.PasswordRequired(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
string? password = passwordPrompt.Invoke();
|
||||||
|
|
||||||
|
if (password is null)
|
||||||
|
{
|
||||||
|
return CertificateProbeResult.Failed(
|
||||||
|
"Zum Lesen der PFX/P12-Datei wird ein Kennwort benötigt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CertificateInfo info = ReadCertificate(normalized, password);
|
||||||
|
return CertificateProbeResult.Found(normalized, info);
|
||||||
|
}
|
||||||
|
catch (CryptographicException)
|
||||||
|
{
|
||||||
|
return CertificateProbeResult.Failed(
|
||||||
|
"Das Kennwort ist falsch oder die Datei ist beschädigt.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return CertificateProbeResult.Failed(
|
||||||
|
$"Zertifikat konnte nicht gelesen werden: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TrySplitTargetPath(
|
||||||
|
string fullPath,
|
||||||
|
out string directory,
|
||||||
|
out string fileName,
|
||||||
|
out string? error)
|
||||||
|
{
|
||||||
|
directory = string.Empty;
|
||||||
|
fileName = string.Empty;
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
string? normalized = NormalizePath(fullPath);
|
||||||
|
|
||||||
|
if (normalized is null)
|
||||||
|
{
|
||||||
|
error = "Bitte einen vollständigen Pfad zur Zertifikatsdatei angeben.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsSupportedExtension(normalized))
|
||||||
|
{
|
||||||
|
error =
|
||||||
|
"Dateiendung nicht unterstützt. Erlaubt: "
|
||||||
|
+ string.Join(", ", SupportedExtensions);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? dir = Path.GetDirectoryName(normalized);
|
||||||
|
string file = Path.GetFileName(normalized);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(dir) || string.IsNullOrWhiteSpace(file))
|
||||||
|
{
|
||||||
|
error =
|
||||||
|
"Pfad muss Verzeichnis und Dateiname enthalten, z. B.\n"
|
||||||
|
+ @"\\server\e$\...\certs\server.p12";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Path.GetFileName(file) != file)
|
||||||
|
{
|
||||||
|
error = "Dateiname darf keinen weiteren Verzeichnisanteil enthalten.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
directory = dir;
|
||||||
|
fileName = file;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CombineTargetPath(
|
||||||
|
string directory,
|
||||||
|
string fileName)
|
||||||
|
{
|
||||||
|
return Path.Combine(
|
||||||
|
directory.Trim().TrimEnd('\\', '/'),
|
||||||
|
fileName.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsSupportedExtension(string path)
|
||||||
|
{
|
||||||
|
string extension = Path.GetExtension(path);
|
||||||
|
return SupportedExtensions.Contains(
|
||||||
|
extension,
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsPasswordProtected(string path)
|
||||||
|
{
|
||||||
|
string extension = Path.GetExtension(path);
|
||||||
|
return extension.Equals(".pfx", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| extension.Equals(".p12", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? NormalizePath(string? path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string trimmed = path.Trim().Trim('"');
|
||||||
|
|
||||||
|
if (trimmed.Length == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UNC und absolute lokale Pfade akzeptieren
|
||||||
|
if (!Path.IsPathRooted(trimmed)
|
||||||
|
&& !trimmed.StartsWith(@"\\", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CertificateInfo ReadCertificate(
|
||||||
|
string certificatePath,
|
||||||
|
string? password)
|
||||||
|
{
|
||||||
|
string extension = Path.GetExtension(certificatePath);
|
||||||
|
|
||||||
|
using X509Certificate2 certificate =
|
||||||
|
IsPasswordProtected(certificatePath)
|
||||||
|
? new X509Certificate2(
|
||||||
|
certificatePath,
|
||||||
|
password,
|
||||||
|
X509KeyStorageFlags.EphemeralKeySet)
|
||||||
|
: new X509Certificate2(certificatePath);
|
||||||
|
|
||||||
|
string subject = certificate.GetNameInfo(
|
||||||
|
X509NameType.SimpleName,
|
||||||
|
forIssuer: false);
|
||||||
|
|
||||||
|
string issuer = certificate.GetNameInfo(
|
||||||
|
X509NameType.SimpleName,
|
||||||
|
forIssuer: true);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(subject))
|
||||||
|
{
|
||||||
|
subject = certificate.Subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(issuer))
|
||||||
|
{
|
||||||
|
issuer = certificate.Issuer;
|
||||||
|
}
|
||||||
|
|
||||||
|
string fingerprint = certificate.GetCertHashString(
|
||||||
|
HashAlgorithmName.SHA256);
|
||||||
|
|
||||||
|
fingerprint = string.Join(
|
||||||
|
":",
|
||||||
|
Enumerable.Range(0, fingerprint.Length / 2)
|
||||||
|
.Select(index => fingerprint.Substring(index * 2, 2)));
|
||||||
|
|
||||||
|
DateTimeOffset validFrom = new(certificate.NotBefore);
|
||||||
|
DateTimeOffset validUntil = new(certificate.NotAfter);
|
||||||
|
DateTimeOffset now = DateTimeOffset.Now;
|
||||||
|
|
||||||
|
return new CertificateInfo
|
||||||
|
{
|
||||||
|
Subject = subject,
|
||||||
|
Issuer = issuer,
|
||||||
|
FingerprintSha256 = fingerprint,
|
||||||
|
ValidFrom = validFrom,
|
||||||
|
ValidUntil = validUntil,
|
||||||
|
IsCurrentlyValid = now >= validFrom && now <= validUntil
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class CertificateProbeResult
|
||||||
|
{
|
||||||
|
public bool Success { get; init; }
|
||||||
|
|
||||||
|
public bool FileExists { get; init; }
|
||||||
|
|
||||||
|
public string Path { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
public string Message { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
public CertificateInfo? Certificate { get; init; }
|
||||||
|
|
||||||
|
public static CertificateProbeResult Found(
|
||||||
|
string path,
|
||||||
|
CertificateInfo certificate)
|
||||||
|
{
|
||||||
|
string expiry = certificate.ValidUntil.ToLocalTime()
|
||||||
|
.ToString("dd.MM.yyyy HH:mm");
|
||||||
|
|
||||||
|
string validity = certificate.IsCurrentlyValid
|
||||||
|
? "gültig"
|
||||||
|
: "abgelaufen / ungültig";
|
||||||
|
|
||||||
|
return new CertificateProbeResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
FileExists = true,
|
||||||
|
Path = path,
|
||||||
|
Certificate = certificate,
|
||||||
|
Message =
|
||||||
|
$"Zertifikat gefunden · Subject: {certificate.Subject} · "
|
||||||
|
+ $"Ablauf: {expiry} ({validity})"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CertificateProbeResult Missing(
|
||||||
|
string path,
|
||||||
|
string message)
|
||||||
|
{
|
||||||
|
return new CertificateProbeResult
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
FileExists = false,
|
||||||
|
Path = path,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CertificateProbeResult Failed(string message)
|
||||||
|
{
|
||||||
|
return new CertificateProbeResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
FileExists = false,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CertificateProbeResult PasswordRequired(string path)
|
||||||
|
{
|
||||||
|
return new CertificateProbeResult
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
FileExists = true,
|
||||||
|
Path = path,
|
||||||
|
Message = "PFX/P12 vorhanden – Kennwort nötig zum Lesen des Ablaufs"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,8 @@ public sealed class LocalCertificateDeployer
|
|||||||
".cer",
|
".cer",
|
||||||
".crt",
|
".crt",
|
||||||
".pem",
|
".pem",
|
||||||
".pfx"
|
".pfx",
|
||||||
|
".p12"
|
||||||
};
|
};
|
||||||
|
|
||||||
public async Task<string> DeployAsync(
|
public async Task<string> DeployAsync(
|
||||||
|
|||||||
@@ -1,303 +0,0 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using System.Text;
|
|
||||||
using ZA.CoreService.ESBCertificateManager.Models;
|
|
||||||
|
|
||||||
namespace ZA.CoreService.ESBCertificateManager.Services;
|
|
||||||
|
|
||||||
public sealed class SonicContainerScanner
|
|
||||||
{
|
|
||||||
private readonly RuntimeSettings _runtimeSettings;
|
|
||||||
|
|
||||||
public SonicContainerScanner(RuntimeSettings runtimeSettings)
|
|
||||||
{
|
|
||||||
_runtimeSettings = runtimeSettings
|
|
||||||
?? throw new ArgumentNullException(nameof(runtimeSettings));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ContainerScanResult> ScanAsync(
|
|
||||||
SonicSystemOption system,
|
|
||||||
string userName,
|
|
||||||
string secret,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(system);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(userName))
|
|
||||||
{
|
|
||||||
return ContainerScanResult.Failed(
|
|
||||||
"Der Sonic-Benutzername fehlt.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(secret))
|
|
||||||
{
|
|
||||||
return ContainerScanResult.Failed(
|
|
||||||
"Das Sonic-Kennwort fehlt.");
|
|
||||||
}
|
|
||||||
|
|
||||||
string javaPath =
|
|
||||||
PathResolver.ResolvePath(
|
|
||||||
_runtimeSettings.JavaExecutablePath);
|
|
||||||
|
|
||||||
string libraryPath =
|
|
||||||
PathResolver.ResolvePath(
|
|
||||||
_runtimeSettings.SonicClientLibraryPath);
|
|
||||||
|
|
||||||
string? toolsPath = ResolveToolsDirectory();
|
|
||||||
|
|
||||||
if (!File.Exists(javaPath))
|
|
||||||
{
|
|
||||||
return ContainerScanResult.Failed(
|
|
||||||
$"Java wurde nicht gefunden: {javaPath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Directory.Exists(libraryPath))
|
|
||||||
{
|
|
||||||
return ContainerScanResult.Failed(
|
|
||||||
$"Der Sonic-JAR-Ordner wurde nicht gefunden: {libraryPath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toolsPath is null)
|
|
||||||
{
|
|
||||||
return ContainerScanResult.Failed(
|
|
||||||
"SonicMfContainerTool.class wurde nicht gefunden.");
|
|
||||||
}
|
|
||||||
|
|
||||||
string[] jars =
|
|
||||||
Directory.GetFiles(
|
|
||||||
libraryPath,
|
|
||||||
"*.jar",
|
|
||||||
SearchOption.TopDirectoryOnly);
|
|
||||||
|
|
||||||
if (jars.Length == 0)
|
|
||||||
{
|
|
||||||
return ContainerScanResult.Failed(
|
|
||||||
"Im Sonic-Client-Ordner wurden keine JAR-Dateien gefunden.");
|
|
||||||
}
|
|
||||||
|
|
||||||
string classpath =
|
|
||||||
toolsPath
|
|
||||||
+ Path.PathSeparator
|
|
||||||
+ string.Join(
|
|
||||||
Path.PathSeparator,
|
|
||||||
jars.OrderBy(
|
|
||||||
Path.GetFileName,
|
|
||||||
StringComparer.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
ProcessStartInfo startInfo = new()
|
|
||||||
{
|
|
||||||
FileName = javaPath,
|
|
||||||
WorkingDirectory = toolsPath,
|
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
RedirectStandardError = true,
|
|
||||||
StandardOutputEncoding = Encoding.UTF8,
|
|
||||||
StandardErrorEncoding = Encoding.UTF8,
|
|
||||||
CreateNoWindow = true
|
|
||||||
};
|
|
||||||
|
|
||||||
startInfo.ArgumentList.Add("-cp");
|
|
||||||
startInfo.ArgumentList.Add(classpath);
|
|
||||||
startInfo.ArgumentList.Add("SonicMfContainerTool");
|
|
||||||
startInfo.ArgumentList.Add("list");
|
|
||||||
startInfo.ArgumentList.Add("--domain");
|
|
||||||
startInfo.ArgumentList.Add(system.DomainName);
|
|
||||||
startInfo.ArgumentList.Add("--url");
|
|
||||||
startInfo.ArgumentList.Add(system.ConnectionUrl);
|
|
||||||
startInfo.ArgumentList.Add("--user");
|
|
||||||
startInfo.ArgumentList.Add(userName.Trim());
|
|
||||||
startInfo.ArgumentList.Add("--timeout");
|
|
||||||
startInfo.ArgumentList.Add("45");
|
|
||||||
|
|
||||||
startInfo.Environment["ESB_SONIC_PASSWORD"] = secret;
|
|
||||||
|
|
||||||
using Process process = new()
|
|
||||||
{
|
|
||||||
StartInfo = startInfo
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!process.Start())
|
|
||||||
{
|
|
||||||
return ContainerScanResult.Failed(
|
|
||||||
"Der Java-Prozess konnte nicht gestartet werden.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return ContainerScanResult.Failed(
|
|
||||||
$"Java konnte nicht gestartet werden: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Task<string> standardOutputTask =
|
|
||||||
process.StandardOutput.ReadToEndAsync();
|
|
||||||
|
|
||||||
Task<string> standardErrorTask =
|
|
||||||
process.StandardError.ReadToEndAsync();
|
|
||||||
|
|
||||||
using CancellationTokenSource timeoutSource =
|
|
||||||
CancellationTokenSource.CreateLinkedTokenSource(
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
timeoutSource.CancelAfter(TimeSpan.FromSeconds(50));
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await process.WaitForExitAsync(timeoutSource.Token);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
when (!cancellationToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
TryKill(process);
|
|
||||||
|
|
||||||
return ContainerScanResult.Failed(
|
|
||||||
"Der Container-Scan hat das Zeitlimit überschritten.");
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
TryKill(process);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
string standardOutput =
|
|
||||||
(await standardOutputTask).Trim();
|
|
||||||
|
|
||||||
string standardError =
|
|
||||||
(await standardErrorTask).Trim();
|
|
||||||
|
|
||||||
string combined =
|
|
||||||
string.Join(
|
|
||||||
Environment.NewLine,
|
|
||||||
new[] { standardOutput, standardError }
|
|
||||||
.Where(value => !string.IsNullOrWhiteSpace(value)));
|
|
||||||
|
|
||||||
bool success =
|
|
||||||
process.ExitCode == 0
|
|
||||||
&& combined.Contains(
|
|
||||||
"OK:ContainerListEnd",
|
|
||||||
StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& !combined.Contains(
|
|
||||||
"ERROR:",
|
|
||||||
StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
return ContainerScanResult.Failed(
|
|
||||||
ExtractError(combined)
|
|
||||||
?? $"Container-Scan fehlgeschlagen, ExitCode={process.ExitCode}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<string> containers = [];
|
|
||||||
|
|
||||||
foreach (string line in standardOutput.Split(
|
|
||||||
['\r', '\n'],
|
|
||||||
StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
{
|
|
||||||
const string prefix = "CONTAINER=";
|
|
||||||
|
|
||||||
if (!line.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
string name = line[prefix.Length..].Trim();
|
|
||||||
|
|
||||||
if (name.Length > 0
|
|
||||||
&& !containers.Contains(name, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
containers.Add(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
containers.Sort(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
return ContainerScanResult.Successful(containers);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? ResolveToolsDirectory()
|
|
||||||
{
|
|
||||||
string baseDirectory = AppContext.BaseDirectory;
|
|
||||||
|
|
||||||
string[] candidates =
|
|
||||||
[
|
|
||||||
Path.Combine(baseDirectory, "Tools"),
|
|
||||||
Path.Combine(baseDirectory, "..", "..", "..", "Tools"),
|
|
||||||
Path.Combine(
|
|
||||||
Directory.GetCurrentDirectory(),
|
|
||||||
"Tools")
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach (string candidate in candidates)
|
|
||||||
{
|
|
||||||
string fullPath = Path.GetFullPath(candidate);
|
|
||||||
string classFile = Path.Combine(
|
|
||||||
fullPath,
|
|
||||||
"SonicMfContainerTool.class");
|
|
||||||
|
|
||||||
if (File.Exists(classFile))
|
|
||||||
{
|
|
||||||
return fullPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? ExtractError(string output)
|
|
||||||
{
|
|
||||||
foreach (string line in output.Split(
|
|
||||||
['\r', '\n'],
|
|
||||||
StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
{
|
|
||||||
if (line.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return line["ERROR:".Length..].Trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.IsNullOrWhiteSpace(output) ? null : output;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void TryKill(Process process)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!process.HasExited)
|
|
||||||
{
|
|
||||||
process.Kill(entireProcessTree: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed class ContainerScanResult
|
|
||||||
{
|
|
||||||
public bool Success { get; init; }
|
|
||||||
|
|
||||||
public string Message { get; init; } = string.Empty;
|
|
||||||
|
|
||||||
public IReadOnlyList<string> Containers { get; init; } = [];
|
|
||||||
|
|
||||||
public static ContainerScanResult Successful(
|
|
||||||
IReadOnlyList<string> containers)
|
|
||||||
{
|
|
||||||
return new ContainerScanResult
|
|
||||||
{
|
|
||||||
Success = true,
|
|
||||||
Message = $"{containers.Count} Container gefunden.",
|
|
||||||
Containers = containers
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ContainerScanResult Failed(string message)
|
|
||||||
{
|
|
||||||
return new ContainerScanResult
|
|
||||||
{
|
|
||||||
Success = false,
|
|
||||||
Message = message
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
using ZA.CoreService.ESBCertificateManager.Models;
|
using ZA.CoreService.ESBCertificateManager.Models;
|
||||||
|
using ZA.CoreService.ESBCertificateManager.Services;
|
||||||
|
|
||||||
namespace ZA.CoreService.ESBCertificateManager.Setup;
|
namespace ZA.CoreService.ESBCertificateManager.Setup;
|
||||||
|
|
||||||
public sealed class AddTargetPathForm : Form
|
public sealed class AddTargetPathForm : Form
|
||||||
{
|
{
|
||||||
private readonly SetupCoordinator _coordinator;
|
private readonly SetupCoordinator _coordinator;
|
||||||
|
private readonly CertificateProbeService _probeService = new();
|
||||||
|
|
||||||
private readonly ComboBox _cmbContainer;
|
private readonly ComboBox _cmbContainer;
|
||||||
private readonly TextBox _txtDirectory;
|
private readonly TextBox _txtFullPath;
|
||||||
private readonly TextBox _txtFileName;
|
|
||||||
private readonly TextBox _txtBackupFolder;
|
private readonly TextBox _txtBackupFolder;
|
||||||
private readonly CheckBox _chkBackup;
|
private readonly CheckBox _chkBackup;
|
||||||
|
private readonly Label _lblProbeResult;
|
||||||
|
private readonly Button _btnProbe;
|
||||||
private readonly Button _btnSave;
|
private readonly Button _btnSave;
|
||||||
|
|
||||||
|
private CertificateProbeResult? _lastProbe;
|
||||||
private bool _running;
|
private bool _running;
|
||||||
|
|
||||||
public int? CreatedCertificateTargetId { get; private set; }
|
public int? CreatedCertificateTargetId { get; private set; }
|
||||||
@@ -27,15 +33,15 @@ public sealed class AddTargetPathForm : Form
|
|||||||
MaximizeBox = false;
|
MaximizeBox = false;
|
||||||
MinimizeBox = false;
|
MinimizeBox = false;
|
||||||
ShowInTaskbar = false;
|
ShowInTaskbar = false;
|
||||||
ClientSize = new Size(560, 450);
|
ClientSize = new Size(640, 560);
|
||||||
BackColor = SettingsUi.Background;
|
BackColor = SettingsUi.Background;
|
||||||
ForeColor = SettingsUi.Text;
|
ForeColor = SettingsUi.Text;
|
||||||
Font = new Font("Segoe UI", 10f);
|
Font = new Font("Segoe UI", 10f);
|
||||||
|
|
||||||
Label title = new()
|
Label title = new()
|
||||||
{
|
{
|
||||||
Text = "Neuer Zertifikat-Pfad",
|
Text = "Zertifikat-Datei-Pfad",
|
||||||
Location = new Point(28, 22),
|
Location = new Point(28, 18),
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
Font = new Font("Segoe UI Semibold", 18f, FontStyle.Bold),
|
Font = new Font("Segoe UI Semibold", 18f, FontStyle.Bold),
|
||||||
ForeColor = SettingsUi.Text
|
ForeColor = SettingsUi.Text
|
||||||
@@ -44,23 +50,45 @@ public sealed class AddTargetPathForm : Form
|
|||||||
Label subtitle = new()
|
Label subtitle = new()
|
||||||
{
|
{
|
||||||
Text =
|
Text =
|
||||||
"Zielverzeichnis und Dateiname für die Zertifikatsablage.",
|
"UNC- oder lokaler Dateipfad zur .cer/.crt/.pem/.pfx/.p12-Datei. "
|
||||||
Location = new Point(30, 58),
|
+ "Der Container ist nur das Neustart-Ziel.",
|
||||||
|
Location = new Point(30, 54),
|
||||||
|
MaximumSize = new Size(580, 40),
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
ForeColor = SettingsUi.Muted
|
ForeColor = SettingsUi.Muted
|
||||||
};
|
};
|
||||||
|
|
||||||
Panel card = SettingsUi.CreateCard();
|
Panel card = SettingsUi.CreateCard();
|
||||||
card.Location = new Point(28, 95);
|
card.Location = new Point(28, 100);
|
||||||
card.Size = new Size(504, 270);
|
card.Size = new Size(584, 360);
|
||||||
card.Paint += SettingsUi.PaintCardBorder;
|
card.Paint += SettingsUi.PaintCardBorder;
|
||||||
|
|
||||||
|
_txtFullPath = SettingsUi.CreateTextBox();
|
||||||
|
_txtFullPath.Text = DemoCertificatePath.FullPath;
|
||||||
|
_txtFullPath.PlaceholderText = DemoCertificatePath.FullPath;
|
||||||
|
|
||||||
|
Button browse = SettingsUi.CreateGhostButton("…");
|
||||||
|
browse.Size = new Size(40, 32);
|
||||||
|
browse.Click += (_, _) => BrowseForFile();
|
||||||
|
|
||||||
|
_btnProbe = SettingsUi.CreateGhostButton("Prüfen");
|
||||||
|
_btnProbe.Size = new Size(100, 32);
|
||||||
|
_btnProbe.Click += (_, _) => RunProbe();
|
||||||
|
|
||||||
|
_lblProbeResult = new Label
|
||||||
|
{
|
||||||
|
AutoSize = false,
|
||||||
|
Size = new Size(548, 52),
|
||||||
|
ForeColor = SettingsUi.Muted,
|
||||||
|
Font = new Font("Segoe UI", 9f),
|
||||||
|
Text =
|
||||||
|
"Noch nicht geprüft. Bitte Pfad prüfen – bei vorhandener "
|
||||||
|
+ "Datei wird der Ablauf angezeigt."
|
||||||
|
};
|
||||||
|
|
||||||
_cmbContainer = SettingsUi.CreateComboBox();
|
_cmbContainer = SettingsUi.CreateComboBox();
|
||||||
_txtDirectory = SettingsUi.CreateTextBox();
|
|
||||||
_txtFileName = SettingsUi.CreateTextBox();
|
|
||||||
_txtBackupFolder = SettingsUi.CreateTextBox();
|
_txtBackupFolder = SettingsUi.CreateTextBox();
|
||||||
_txtBackupFolder.Text = "Backup";
|
_txtBackupFolder.Text = "Backup";
|
||||||
_txtFileName.Text = "server.crt";
|
|
||||||
|
|
||||||
_chkBackup = new CheckBox
|
_chkBackup = new CheckBox
|
||||||
{
|
{
|
||||||
@@ -71,21 +99,54 @@ public sealed class AddTargetPathForm : Form
|
|||||||
Font = new Font("Segoe UI", 9.5f)
|
Font = new Font("Segoe UI", 9.5f)
|
||||||
};
|
};
|
||||||
|
|
||||||
AddField(card, "Container", _cmbContainer, 18, 18, 460);
|
Label pathLabel = SettingsUi.CreateFieldLabel(
|
||||||
AddField(card, "Zielverzeichnis", _txtDirectory, 18, 78, 460);
|
"Vollständiger Pfad zur Zertifikatsdatei");
|
||||||
AddField(card, "Dateiname", _txtFileName, 18, 138, 220);
|
pathLabel.Location = new Point(18, 16);
|
||||||
AddField(card, "Backup-Ordner", _txtBackupFolder, 258, 138, 220);
|
_txtFullPath.Location = new Point(18, 38);
|
||||||
|
_txtFullPath.Width = 370;
|
||||||
|
browse.Location = new Point(394, 38);
|
||||||
|
_btnProbe.Location = new Point(440, 38);
|
||||||
|
|
||||||
_chkBackup.Location = new Point(18, 210);
|
_lblProbeResult.Location = new Point(18, 78);
|
||||||
|
|
||||||
|
Label containerLabel = SettingsUi.CreateFieldLabel(
|
||||||
|
"Neustart-Ziel (Sonic-Container) – nicht der Dateipfad");
|
||||||
|
containerLabel.Location = new Point(18, 140);
|
||||||
|
_cmbContainer.Location = new Point(18, 162);
|
||||||
|
_cmbContainer.Width = 548;
|
||||||
|
|
||||||
|
Label hint = new()
|
||||||
|
{
|
||||||
|
Text =
|
||||||
|
"Nach dem Austausch wird dieser Container über MfApi "
|
||||||
|
+ "neu gestartet. Der Pfad oben ist unabhängig davon.",
|
||||||
|
Location = new Point(18, 202),
|
||||||
|
MaximumSize = new Size(548, 36),
|
||||||
|
AutoSize = true,
|
||||||
|
ForeColor = SettingsUi.Muted,
|
||||||
|
Font = new Font("Segoe UI", 8.5f)
|
||||||
|
};
|
||||||
|
|
||||||
|
AddField(card, "Backup-Ordner", _txtBackupFolder, 18, 250, 220);
|
||||||
|
_chkBackup.Location = new Point(258, 272);
|
||||||
card.Controls.Add(_chkBackup);
|
card.Controls.Add(_chkBackup);
|
||||||
|
|
||||||
|
card.Controls.Add(pathLabel);
|
||||||
|
card.Controls.Add(_txtFullPath);
|
||||||
|
card.Controls.Add(browse);
|
||||||
|
card.Controls.Add(_btnProbe);
|
||||||
|
card.Controls.Add(_lblProbeResult);
|
||||||
|
card.Controls.Add(containerLabel);
|
||||||
|
card.Controls.Add(_cmbContainer);
|
||||||
|
card.Controls.Add(hint);
|
||||||
|
|
||||||
_btnSave = SettingsUi.CreatePrimaryButton("Pfad anlegen");
|
_btnSave = SettingsUi.CreatePrimaryButton("Pfad anlegen");
|
||||||
_btnSave.Location = new Point(312, 385);
|
_btnSave.Location = new Point(392, 490);
|
||||||
_btnSave.Size = new Size(220, 40);
|
_btnSave.Size = new Size(220, 40);
|
||||||
_btnSave.Click += async (_, _) => await SaveAsync();
|
_btnSave.Click += async (_, _) => await SaveAsync();
|
||||||
|
|
||||||
Button cancel = SettingsUi.CreateGhostButton("Abbrechen");
|
Button cancel = SettingsUi.CreateGhostButton("Abbrechen");
|
||||||
cancel.Location = new Point(190, 385);
|
cancel.Location = new Point(270, 490);
|
||||||
cancel.Size = new Size(110, 40);
|
cancel.Size = new Size(110, 40);
|
||||||
cancel.Click += (_, _) =>
|
cancel.Click += (_, _) =>
|
||||||
{
|
{
|
||||||
@@ -103,6 +164,64 @@ public sealed class AddTargetPathForm : Form
|
|||||||
await LoadContainersAsync(preferredSonicContainerId);
|
await LoadContainersAsync(preferredSonicContainerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BrowseForFile()
|
||||||
|
{
|
||||||
|
using OpenFileDialog dialog = new()
|
||||||
|
{
|
||||||
|
Title = "Vorhandene Zertifikatsdatei wählen",
|
||||||
|
Filter =
|
||||||
|
"Zertifikate (*.cer;*.crt;*.pem;*.pfx;*.p12)|"
|
||||||
|
+ "*.cer;*.crt;*.pem;*.pfx;*.p12|"
|
||||||
|
+ "Alle Dateien (*.*)|*.*",
|
||||||
|
CheckFileExists = false,
|
||||||
|
Multiselect = false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog(this) != DialogResult.OK)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_txtFullPath.Text = dialog.FileName;
|
||||||
|
RunProbe();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RunProbe()
|
||||||
|
{
|
||||||
|
_lastProbe = _probeService.Probe(
|
||||||
|
_txtFullPath.Text,
|
||||||
|
() => PromptForPassword(this));
|
||||||
|
|
||||||
|
if (!_lastProbe.Success)
|
||||||
|
{
|
||||||
|
_lblProbeResult.ForeColor = Color.FromArgb(220, 100, 100);
|
||||||
|
_lblProbeResult.Text = _lastProbe.Message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_lastProbe.FileExists)
|
||||||
|
{
|
||||||
|
_lblProbeResult.ForeColor = Color.FromArgb(230, 180, 80);
|
||||||
|
_lblProbeResult.Text =
|
||||||
|
_lastProbe.Message
|
||||||
|
+ " Pfad kann trotzdem gespeichert werden "
|
||||||
|
+ "(erstes Deployment).";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CertificateInfo cert = _lastProbe.Certificate!;
|
||||||
|
string expiry = cert.ValidUntil.ToLocalTime()
|
||||||
|
.ToString("dd.MM.yyyy HH:mm");
|
||||||
|
|
||||||
|
_lblProbeResult.ForeColor = cert.IsCurrentlyValid
|
||||||
|
? Color.FromArgb(90, 200, 140)
|
||||||
|
: Color.FromArgb(220, 100, 100);
|
||||||
|
|
||||||
|
_lblProbeResult.Text =
|
||||||
|
$"Datei vorhanden · Subject: {cert.Subject}\n"
|
||||||
|
+ $"Ablauf: {expiry} · Fingerprint: {cert.FingerprintSha256}";
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadContainersAsync(int? preferredSonicContainerId)
|
private async Task LoadContainersAsync(int? preferredSonicContainerId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -151,13 +270,59 @@ public sealed class AddTargetPathForm : Form
|
|||||||
{
|
{
|
||||||
MessageBox.Show(
|
MessageBox.Show(
|
||||||
this,
|
this,
|
||||||
"Bitte einen Container wählen.",
|
"Bitte ein Neustart-Ziel (Sonic-Container) wählen.",
|
||||||
"Zertifikat-Pfad",
|
"Zertifikat-Pfad",
|
||||||
MessageBoxButtons.OK,
|
MessageBoxButtons.OK,
|
||||||
MessageBoxIcon.Warning);
|
MessageBoxIcon.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CertificateProbeService.TrySplitTargetPath(
|
||||||
|
_txtFullPath.Text,
|
||||||
|
out string directory,
|
||||||
|
out string fileName,
|
||||||
|
out string? splitError))
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
this,
|
||||||
|
splitError ?? "Ungültiger Pfad.",
|
||||||
|
"Zertifikat-Pfad",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vor dem Speichern immer prüfen
|
||||||
|
RunProbe();
|
||||||
|
|
||||||
|
if (_lastProbe is null || !_lastProbe.Success)
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
this,
|
||||||
|
_lastProbe?.Message
|
||||||
|
?? "Pfad konnte nicht geprüft werden.",
|
||||||
|
"Zertifikat-Pfad",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_lastProbe.FileExists)
|
||||||
|
{
|
||||||
|
DialogResult confirm = MessageBox.Show(
|
||||||
|
this,
|
||||||
|
"Am Ziel liegt noch keine Zertifikatsdatei.\n\n"
|
||||||
|
+ "Pfad trotzdem speichern?",
|
||||||
|
"Datei fehlt",
|
||||||
|
MessageBoxButtons.YesNo,
|
||||||
|
MessageBoxIcon.Question);
|
||||||
|
|
||||||
|
if (confirm != DialogResult.Yes)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_running = true;
|
_running = true;
|
||||||
_btnSave.Enabled = false;
|
_btnSave.Enabled = false;
|
||||||
Cursor = Cursors.WaitCursor;
|
Cursor = Cursors.WaitCursor;
|
||||||
@@ -167,8 +332,8 @@ public sealed class AddTargetPathForm : Form
|
|||||||
CreatedCertificateTargetId =
|
CreatedCertificateTargetId =
|
||||||
await _coordinator.AddCertificateTargetAsync(
|
await _coordinator.AddCertificateTargetAsync(
|
||||||
container.SonicContainerId,
|
container.SonicContainerId,
|
||||||
_txtDirectory.Text,
|
directory,
|
||||||
_txtFileName.Text,
|
fileName,
|
||||||
_chkBackup.Checked,
|
_chkBackup.Checked,
|
||||||
_txtBackupFolder.Text);
|
_txtBackupFolder.Text);
|
||||||
|
|
||||||
@@ -192,6 +357,57 @@ public sealed class AddTargetPathForm : Form
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? PromptForPassword(IWin32Window owner)
|
||||||
|
{
|
||||||
|
using Form dialog = new()
|
||||||
|
{
|
||||||
|
Text = "PFX/P12-Kennwort",
|
||||||
|
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||||||
|
StartPosition = FormStartPosition.CenterParent,
|
||||||
|
ClientSize = new Size(360, 140),
|
||||||
|
MaximizeBox = false,
|
||||||
|
MinimizeBox = false,
|
||||||
|
ShowInTaskbar = false,
|
||||||
|
BackColor = SettingsUi.Background,
|
||||||
|
ForeColor = SettingsUi.Text
|
||||||
|
};
|
||||||
|
|
||||||
|
Label label = new()
|
||||||
|
{
|
||||||
|
Text = "Kennwort für die Zertifikatsdatei:",
|
||||||
|
Location = new Point(16, 16),
|
||||||
|
AutoSize = true
|
||||||
|
};
|
||||||
|
|
||||||
|
TextBox txtPwd = new()
|
||||||
|
{
|
||||||
|
Location = new Point(16, 44),
|
||||||
|
Width = 320,
|
||||||
|
UseSystemPasswordChar = true
|
||||||
|
};
|
||||||
|
|
||||||
|
Button btnOk = SettingsUi.CreatePrimaryButton("OK");
|
||||||
|
btnOk.Location = new Point(196, 90);
|
||||||
|
btnOk.Size = new Size(140, 32);
|
||||||
|
btnOk.DialogResult = DialogResult.OK;
|
||||||
|
|
||||||
|
Button btnCancel = SettingsUi.CreateGhostButton("Abbrechen");
|
||||||
|
btnCancel.Location = new Point(100, 90);
|
||||||
|
btnCancel.Size = new Size(90, 32);
|
||||||
|
btnCancel.DialogResult = DialogResult.Cancel;
|
||||||
|
|
||||||
|
dialog.Controls.Add(label);
|
||||||
|
dialog.Controls.Add(txtPwd);
|
||||||
|
dialog.Controls.Add(btnOk);
|
||||||
|
dialog.Controls.Add(btnCancel);
|
||||||
|
dialog.AcceptButton = btnOk;
|
||||||
|
dialog.CancelButton = btnCancel;
|
||||||
|
|
||||||
|
return dialog.ShowDialog(owner) == DialogResult.OK
|
||||||
|
? txtPwd.Text
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
private static void AddField(
|
private static void AddField(
|
||||||
Control parent,
|
Control parent,
|
||||||
string label,
|
string label,
|
||||||
|
|||||||
@@ -1,432 +0,0 @@
|
|||||||
using ZA.CoreService.ESBCertificateManager.Models;
|
|
||||||
using ZA.CoreService.ESBCertificateManager.Services;
|
|
||||||
|
|
||||||
namespace ZA.CoreService.ESBCertificateManager.Setup;
|
|
||||||
|
|
||||||
public sealed class SelectContainersForm : Form
|
|
||||||
{
|
|
||||||
private readonly SetupCoordinator _coordinator;
|
|
||||||
private readonly SonicSystemOption _system;
|
|
||||||
private readonly SonicContainerScanner _scanner;
|
|
||||||
|
|
||||||
private readonly ComboBox _cmbCompany;
|
|
||||||
private readonly CheckedListBox _lstContainers;
|
|
||||||
private readonly TextBox _txtManual;
|
|
||||||
private readonly TextBox _txtUser;
|
|
||||||
private readonly TextBox _txtPassword;
|
|
||||||
private readonly Label _lblStatus;
|
|
||||||
private readonly Button _btnScan;
|
|
||||||
private readonly Button _btnSave;
|
|
||||||
private bool _running;
|
|
||||||
|
|
||||||
public int SavedCount { get; private set; }
|
|
||||||
|
|
||||||
public SelectContainersForm(
|
|
||||||
SetupCoordinator coordinator,
|
|
||||||
SonicSystemOption system)
|
|
||||||
{
|
|
||||||
_coordinator = coordinator;
|
|
||||||
_system = system;
|
|
||||||
_scanner = new SonicContainerScanner(coordinator.Settings.Runtime);
|
|
||||||
|
|
||||||
Text = "Container auswählen";
|
|
||||||
StartPosition = FormStartPosition.CenterParent;
|
|
||||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
|
||||||
MaximizeBox = false;
|
|
||||||
MinimizeBox = false;
|
|
||||||
ShowInTaskbar = false;
|
|
||||||
ClientSize = new Size(640, 620);
|
|
||||||
BackColor = SettingsUi.Background;
|
|
||||||
ForeColor = SettingsUi.Text;
|
|
||||||
Font = new Font("Segoe UI", 10f);
|
|
||||||
|
|
||||||
Label title = new()
|
|
||||||
{
|
|
||||||
Text = "Container für System",
|
|
||||||
Location = new Point(28, 20),
|
|
||||||
AutoSize = true,
|
|
||||||
Font = new Font("Segoe UI Semibold", 18f, FontStyle.Bold),
|
|
||||||
ForeColor = SettingsUi.Text
|
|
||||||
};
|
|
||||||
|
|
||||||
Label subtitle = new()
|
|
||||||
{
|
|
||||||
Text =
|
|
||||||
$"{system.ConnectionName} · {system.ConnectionUrl}"
|
|
||||||
+ Environment.NewLine
|
|
||||||
+ "Scan von Sonic oder manuell eintragen, dann übernehmen.",
|
|
||||||
Location = new Point(30, 54),
|
|
||||||
Size = new Size(580, 40),
|
|
||||||
ForeColor = SettingsUi.Muted
|
|
||||||
};
|
|
||||||
|
|
||||||
Panel card = SettingsUi.CreateCard();
|
|
||||||
card.Location = new Point(28, 110);
|
|
||||||
card.Size = new Size(584, 430);
|
|
||||||
card.Paint += SettingsUi.PaintCardBorder;
|
|
||||||
|
|
||||||
_cmbCompany = SettingsUi.CreateComboBox();
|
|
||||||
_lstContainers = new CheckedListBox
|
|
||||||
{
|
|
||||||
BackColor = Color.FromArgb(12, 28, 48),
|
|
||||||
ForeColor = SettingsUi.Text,
|
|
||||||
BorderStyle = BorderStyle.FixedSingle,
|
|
||||||
CheckOnClick = true,
|
|
||||||
Font = new Font("Segoe UI", 10f)
|
|
||||||
};
|
|
||||||
|
|
||||||
_txtManual = SettingsUi.CreateTextBox();
|
|
||||||
_txtUser = SettingsUi.CreateTextBox();
|
|
||||||
_txtPassword = SettingsUi.CreateTextBox();
|
|
||||||
_txtPassword.UseSystemPasswordChar = true;
|
|
||||||
|
|
||||||
AddField(card, "Company", _cmbCompany, 18, 16, 540);
|
|
||||||
AddField(card, "Gefundene / gewählte Container", _lstContainers, 18, 76, 540, 150);
|
|
||||||
|
|
||||||
Label manualLabel = SettingsUi.CreateFieldLabel("Manuell hinzufügen");
|
|
||||||
manualLabel.Location = new Point(18, 240);
|
|
||||||
_txtManual.Location = new Point(18, 262);
|
|
||||||
_txtManual.Width = 400;
|
|
||||||
|
|
||||||
Button btnAddManual = SettingsUi.CreateGhostButton("+");
|
|
||||||
btnAddManual.Location = new Point(430, 260);
|
|
||||||
btnAddManual.Size = new Size(50, 32);
|
|
||||||
btnAddManual.Click += (_, _) => AddManualContainer();
|
|
||||||
|
|
||||||
Button btnSuggest = SettingsUi.CreateGhostButton("ct-ZADBService");
|
|
||||||
btnSuggest.Location = new Point(490, 260);
|
|
||||||
btnSuggest.Size = new Size(68, 32);
|
|
||||||
btnSuggest.Font = new Font("Segoe UI", 7.5f, FontStyle.Bold);
|
|
||||||
btnSuggest.Click += (_, _) =>
|
|
||||||
{
|
|
||||||
_txtManual.Text = "ct-ZADBService";
|
|
||||||
AddManualContainer();
|
|
||||||
};
|
|
||||||
|
|
||||||
AddField(card, "Sonic-Benutzer (für Scan)", _txtUser, 18, 308, 250);
|
|
||||||
AddField(card, "Kennwort", _txtPassword, 288, 308, 270);
|
|
||||||
|
|
||||||
_btnScan = SettingsUi.CreatePrimaryButton("Von Sonic scannen");
|
|
||||||
_btnScan.Location = new Point(18, 375);
|
|
||||||
_btnScan.Size = new Size(200, 36);
|
|
||||||
_btnScan.Click += async (_, _) => await ScanAsync();
|
|
||||||
|
|
||||||
_lblStatus = new Label
|
|
||||||
{
|
|
||||||
Location = new Point(230, 380),
|
|
||||||
Size = new Size(330, 30),
|
|
||||||
ForeColor = SettingsUi.Muted
|
|
||||||
};
|
|
||||||
|
|
||||||
card.Controls.Add(manualLabel);
|
|
||||||
card.Controls.Add(_txtManual);
|
|
||||||
card.Controls.Add(btnAddManual);
|
|
||||||
card.Controls.Add(btnSuggest);
|
|
||||||
card.Controls.Add(_btnScan);
|
|
||||||
card.Controls.Add(_lblStatus);
|
|
||||||
|
|
||||||
_btnSave = SettingsUi.CreatePrimaryButton("Auswahl speichern");
|
|
||||||
_btnSave.Location = new Point(352, 555);
|
|
||||||
_btnSave.Size = new Size(260, 42);
|
|
||||||
_btnSave.Click += async (_, _) => await SaveAsync();
|
|
||||||
|
|
||||||
Button skip = SettingsUi.CreateGhostButton("Später");
|
|
||||||
skip.Location = new Point(230, 555);
|
|
||||||
skip.Size = new Size(110, 42);
|
|
||||||
skip.Click += (_, _) =>
|
|
||||||
{
|
|
||||||
DialogResult = DialogResult.Cancel;
|
|
||||||
Close();
|
|
||||||
};
|
|
||||||
|
|
||||||
Controls.Add(title);
|
|
||||||
Controls.Add(subtitle);
|
|
||||||
Controls.Add(card);
|
|
||||||
Controls.Add(skip);
|
|
||||||
Controls.Add(_btnSave);
|
|
||||||
|
|
||||||
Shown += async (_, _) => await InitializeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InitializeAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IReadOnlyList<CompanyOption> companies =
|
|
||||||
await _coordinator.LoadCompaniesAsync();
|
|
||||||
|
|
||||||
_cmbCompany.Items.Clear();
|
|
||||||
|
|
||||||
foreach (CompanyOption company in companies)
|
|
||||||
{
|
|
||||||
_cmbCompany.Items.Add(company);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_cmbCompany.Items.Count > 0)
|
|
||||||
{
|
|
||||||
int deIndex = 0;
|
|
||||||
|
|
||||||
for (int index = 0; index < companies.Count; index++)
|
|
||||||
{
|
|
||||||
if (string.Equals(
|
|
||||||
companies[index].CompanyCode,
|
|
||||||
"DE",
|
|
||||||
StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
deIndex = index;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_cmbCompany.SelectedIndex = deIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
IReadOnlyList<SonicCredentialProfile> profiles =
|
|
||||||
await _coordinator.LoadCredentialsAsync(
|
|
||||||
_system.SonicConnectionId);
|
|
||||||
|
|
||||||
SonicCredentialProfile? profile =
|
|
||||||
profiles.FirstOrDefault(item => item.IsDefault)
|
|
||||||
?? profiles.FirstOrDefault();
|
|
||||||
|
|
||||||
if (profile is not null)
|
|
||||||
{
|
|
||||||
_txtUser.Text = profile.UserName;
|
|
||||||
_txtPassword.Text = profile.Secret;
|
|
||||||
_lblStatus.Text =
|
|
||||||
"Profil geladen — Scan möglich.";
|
|
||||||
_lblStatus.ForeColor = SettingsUi.Green;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_lblStatus.Text =
|
|
||||||
"Kein Profil — Benutzer/Kennwort für Scan eingeben oder manuell.";
|
|
||||||
_lblStatus.ForeColor = SettingsUi.Gold;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bereits vorhandene Container vorselektieren
|
|
||||||
IReadOnlyList<ContainerOption> existing =
|
|
||||||
await _coordinator.LoadContainersAsync(
|
|
||||||
_system.SonicConnectionId);
|
|
||||||
|
|
||||||
foreach (ContainerOption container in existing)
|
|
||||||
{
|
|
||||||
AddContainerName(container.ContainerName, check: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_lblStatus.Text = ex.Message;
|
|
||||||
_lblStatus.ForeColor = SettingsUi.Red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddManualContainer()
|
|
||||||
{
|
|
||||||
string name = _txtManual.Text.Trim();
|
|
||||||
|
|
||||||
if (name.Length == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AddContainerName(name, check: true);
|
|
||||||
_txtManual.Clear();
|
|
||||||
_txtManual.Focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddContainerName(string name, bool check)
|
|
||||||
{
|
|
||||||
for (int index = 0; index < _lstContainers.Items.Count; index++)
|
|
||||||
{
|
|
||||||
if (string.Equals(
|
|
||||||
_lstContainers.Items[index]?.ToString(),
|
|
||||||
name,
|
|
||||||
StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_lstContainers.SetItemChecked(index, check);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int added = _lstContainers.Items.Add(name);
|
|
||||||
_lstContainers.SetItemChecked(added, check);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ScanAsync()
|
|
||||||
{
|
|
||||||
if (_running)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_running = true;
|
|
||||||
_btnScan.Enabled = false;
|
|
||||||
_btnSave.Enabled = false;
|
|
||||||
Cursor = Cursors.WaitCursor;
|
|
||||||
_lblStatus.Text = "Scanne Domain…";
|
|
||||||
_lblStatus.ForeColor = SettingsUi.Muted;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ContainerScanResult result =
|
|
||||||
await _scanner.ScanAsync(
|
|
||||||
_system,
|
|
||||||
_txtUser.Text,
|
|
||||||
_txtPassword.Text);
|
|
||||||
|
|
||||||
if (!result.Success)
|
|
||||||
{
|
|
||||||
_lblStatus.Text = result.Message;
|
|
||||||
_lblStatus.ForeColor = SettingsUi.Red;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (string container in result.Containers)
|
|
||||||
{
|
|
||||||
AddContainerName(container, check: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
_lblStatus.Text =
|
|
||||||
result.Containers.Count == 0
|
|
||||||
? "Scan OK, aber keine Container gefunden — bitte manuell."
|
|
||||||
: $"{result.Containers.Count} Container gescannt und vorausgewählt.";
|
|
||||||
|
|
||||||
_lblStatus.ForeColor =
|
|
||||||
result.Containers.Count == 0
|
|
||||||
? SettingsUi.Gold
|
|
||||||
: SettingsUi.Green;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_lblStatus.Text = ex.Message;
|
|
||||||
_lblStatus.ForeColor = SettingsUi.Red;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_running = false;
|
|
||||||
_btnScan.Enabled = true;
|
|
||||||
_btnSave.Enabled = true;
|
|
||||||
Cursor = Cursors.Default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SaveAsync()
|
|
||||||
{
|
|
||||||
if (_running)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_cmbCompany.SelectedItem is not CompanyOption company)
|
|
||||||
{
|
|
||||||
MessageBox.Show(
|
|
||||||
this,
|
|
||||||
"Bitte eine Company wählen.",
|
|
||||||
"Container",
|
|
||||||
MessageBoxButtons.OK,
|
|
||||||
MessageBoxIcon.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<string> selected = _lstContainers.CheckedItems
|
|
||||||
.Cast<object>()
|
|
||||||
.Select(item => item.ToString() ?? string.Empty)
|
|
||||||
.Where(name => name.Length > 0)
|
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (selected.Count == 0)
|
|
||||||
{
|
|
||||||
MessageBox.Show(
|
|
||||||
this,
|
|
||||||
"Bitte mindestens einen Container angehakt lassen "
|
|
||||||
+ "oder manuell hinzufügen.",
|
|
||||||
"Container",
|
|
||||||
MessageBoxButtons.OK,
|
|
||||||
MessageBoxIcon.Warning);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_running = true;
|
|
||||||
_btnSave.Enabled = false;
|
|
||||||
Cursor = Cursors.WaitCursor;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IReadOnlyList<ContainerOption> existing =
|
|
||||||
await _coordinator.LoadContainersAsync(
|
|
||||||
_system.SonicConnectionId);
|
|
||||||
|
|
||||||
HashSet<string> existingNames = existing
|
|
||||||
.Select(item => item.ContainerName)
|
|
||||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
int created = 0;
|
|
||||||
|
|
||||||
foreach (string containerName in selected)
|
|
||||||
{
|
|
||||||
if (existingNames.Contains(containerName))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _coordinator.AddSonicContainerAsync(
|
|
||||||
company.CompanyId,
|
|
||||||
_system.SonicConnectionId,
|
|
||||||
containerName,
|
|
||||||
containerDisplayName: null,
|
|
||||||
restartTimeoutSeconds: 180);
|
|
||||||
|
|
||||||
created++;
|
|
||||||
}
|
|
||||||
|
|
||||||
SavedCount = created;
|
|
||||||
|
|
||||||
MessageBox.Show(
|
|
||||||
this,
|
|
||||||
created == 0
|
|
||||||
? "Alle gewählten Container waren bereits vorhanden."
|
|
||||||
: $"{created} Container gespeichert und geprüft.",
|
|
||||||
"Prüfung OK",
|
|
||||||
MessageBoxButtons.OK,
|
|
||||||
MessageBoxIcon.Information);
|
|
||||||
|
|
||||||
DialogResult = DialogResult.OK;
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
MessageBox.Show(
|
|
||||||
this,
|
|
||||||
ex.Message,
|
|
||||||
"Container",
|
|
||||||
MessageBoxButtons.OK,
|
|
||||||
MessageBoxIcon.Error);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_running = false;
|
|
||||||
_btnSave.Enabled = true;
|
|
||||||
Cursor = Cursors.Default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddField(
|
|
||||||
Control parent,
|
|
||||||
string label,
|
|
||||||
Control input,
|
|
||||||
int x,
|
|
||||||
int y,
|
|
||||||
int width,
|
|
||||||
int height = 30)
|
|
||||||
{
|
|
||||||
Label fieldLabel = SettingsUi.CreateFieldLabel(label);
|
|
||||||
fieldLabel.Location = new Point(x, y);
|
|
||||||
input.Location = new Point(x, y + 22);
|
|
||||||
input.Width = width;
|
|
||||||
input.Height = height;
|
|
||||||
parent.Controls.Add(fieldLabel);
|
|
||||||
parent.Controls.Add(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using ZA.CoreService.ESBCertificateManager.Models;
|
using ZA.CoreService.ESBCertificateManager.Models;
|
||||||
|
using ZA.CoreService.ESBCertificateManager.Services;
|
||||||
|
|
||||||
namespace ZA.CoreService.ESBCertificateManager.Setup;
|
namespace ZA.CoreService.ESBCertificateManager.Setup;
|
||||||
|
|
||||||
@@ -381,9 +382,7 @@ public sealed class SetupWizardForm : Form
|
|||||||
|
|
||||||
Panel toolbar = CreateToolbar(
|
Panel toolbar = CreateToolbar(
|
||||||
"+ Container hinzufügen",
|
"+ Container hinzufügen",
|
||||||
async () => await AddContainerAsync(),
|
async () => await AddContainerAsync());
|
||||||
secondaryText: "Von Sonic scannen",
|
|
||||||
onSecondary: async () => await ScanContainersForSelectedSystemAsync());
|
|
||||||
|
|
||||||
_lvContainers = SettingsUi.CreateListView();
|
_lvContainers = SettingsUi.CreateListView();
|
||||||
_lvContainers.Dock = DockStyle.Fill;
|
_lvContainers.Dock = DockStyle.Fill;
|
||||||
@@ -400,20 +399,22 @@ public sealed class SetupWizardForm : Form
|
|||||||
{
|
{
|
||||||
_panelPaths = CreateContentPanel(
|
_panelPaths = CreateContentPanel(
|
||||||
"Zertifikat-Pfade",
|
"Zertifikat-Pfade",
|
||||||
"Zielverzeichnisse und Dateinamen für Deployments.");
|
"Dateipfade (UNC/lokal) zum Austausch. Container = nur Neustart-Ziel.");
|
||||||
|
|
||||||
Panel toolbar = CreateToolbar(
|
Panel toolbar = CreateToolbar(
|
||||||
"+ Pfad hinzufügen",
|
"+ Pfad hinzufügen",
|
||||||
async () => await AddPathAsync());
|
async () => await AddPathAsync(),
|
||||||
|
"Pfad prüfen",
|
||||||
|
async () => await ProbeSelectedPathAsync());
|
||||||
|
|
||||||
_lvPaths = SettingsUi.CreateListView();
|
_lvPaths = SettingsUi.CreateListView();
|
||||||
_lvPaths.Dock = DockStyle.Fill;
|
_lvPaths.Dock = DockStyle.Fill;
|
||||||
_lvPaths.Columns.Add("Company", 70);
|
_lvPaths.Columns.Add("Company", 70);
|
||||||
_lvPaths.Columns.Add("Container", 140);
|
_lvPaths.Columns.Add("Neustart-Container", 140);
|
||||||
_lvPaths.Columns.Add("Verzeichnis", 280);
|
_lvPaths.Columns.Add("Zertifikat-Pfad", 360);
|
||||||
_lvPaths.Columns.Add("Datei", 120);
|
_lvPaths.Columns.Add("Ablauf", 140);
|
||||||
_lvPaths.Columns.Add("Backup", 80);
|
_lvPaths.Columns.Add("Backup", 80);
|
||||||
_lvPaths.Columns.Add("System", 140);
|
_lvPaths.Columns.Add("System", 120);
|
||||||
|
|
||||||
FinishPanelLayout(_panelPaths, _lvPaths, toolbar);
|
FinishPanelLayout(_panelPaths, _lvPaths, toolbar);
|
||||||
}
|
}
|
||||||
@@ -644,13 +645,14 @@ public sealed class SetupWizardForm : Form
|
|||||||
await _coordinator.LoadCertificateTargetsAsync();
|
await _coordinator.LoadCertificateTargetsAsync();
|
||||||
|
|
||||||
_lvPaths.Items.Clear();
|
_lvPaths.Items.Clear();
|
||||||
|
CertificateProbeService probe = new();
|
||||||
|
|
||||||
foreach (CertificateTargetOption target in targets)
|
foreach (CertificateTargetOption target in targets)
|
||||||
{
|
{
|
||||||
ListViewItem item = new(target.CompanyCode);
|
ListViewItem item = new(target.CompanyCode);
|
||||||
item.SubItems.Add(target.ContainerName);
|
item.SubItems.Add(target.ContainerName);
|
||||||
item.SubItems.Add(target.TargetDirectory);
|
item.SubItems.Add(target.FullTargetPath);
|
||||||
item.SubItems.Add(target.TargetFileName);
|
item.SubItems.Add("…");
|
||||||
item.SubItems.Add(
|
item.SubItems.Add(
|
||||||
target.BackupEnabled
|
target.BackupEnabled
|
||||||
? target.BackupDirectoryName
|
? target.BackupDirectoryName
|
||||||
@@ -658,6 +660,11 @@ public sealed class SetupWizardForm : Form
|
|||||||
item.SubItems.Add(target.ConnectionName);
|
item.SubItems.Add(target.ConnectionName);
|
||||||
item.Tag = target;
|
item.Tag = target;
|
||||||
_lvPaths.Items.Add(item);
|
_lvPaths.Items.Add(item);
|
||||||
|
|
||||||
|
CertificateProbeResult result = await Task.Run(
|
||||||
|
() => probe.Probe(target.FullTargetPath));
|
||||||
|
|
||||||
|
item.SubItems[3].Text = FormatProbeExpiry(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -666,6 +673,110 @@ public sealed class SetupWizardForm : Form
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ProbeSelectedPathAsync()
|
||||||
|
{
|
||||||
|
if (_lvPaths.SelectedItems.Count == 0
|
||||||
|
|| _lvPaths.SelectedItems[0].Tag is not CertificateTargetOption target)
|
||||||
|
{
|
||||||
|
MessageBox.Show(
|
||||||
|
this,
|
||||||
|
"Bitte zuerst einen Zertifikat-Pfad in der Liste wählen.",
|
||||||
|
"Pfad prüfen",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CertificateProbeService probe = new();
|
||||||
|
CertificateProbeResult result = probe.Probe(
|
||||||
|
target.FullTargetPath,
|
||||||
|
() => PromptCertificatePassword(this));
|
||||||
|
|
||||||
|
_lvPaths.SelectedItems[0].SubItems[3].Text =
|
||||||
|
FormatProbeExpiry(result);
|
||||||
|
|
||||||
|
MessageBoxIcon icon = result.Success && result.Certificate is not null
|
||||||
|
? (result.Certificate.IsCurrentlyValid
|
||||||
|
? MessageBoxIcon.Information
|
||||||
|
: MessageBoxIcon.Warning)
|
||||||
|
: MessageBoxIcon.Warning;
|
||||||
|
|
||||||
|
MessageBox.Show(
|
||||||
|
this,
|
||||||
|
result.Message + "\n\n" + target.FullTargetPath,
|
||||||
|
"Pfad prüfen",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatProbeExpiry(CertificateProbeResult result)
|
||||||
|
{
|
||||||
|
if (result.Success && result.Certificate is not null)
|
||||||
|
{
|
||||||
|
return result.Certificate.ValidUntil
|
||||||
|
.ToLocalTime()
|
||||||
|
.ToString("dd.MM.yyyy HH:mm");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.FileExists)
|
||||||
|
{
|
||||||
|
return "Kennwort nötig";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Success ? "fehlt" : "n/a";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? PromptCertificatePassword(IWin32Window owner)
|
||||||
|
{
|
||||||
|
using Form dialog = new()
|
||||||
|
{
|
||||||
|
Text = "PFX/P12-Kennwort",
|
||||||
|
FormBorderStyle = FormBorderStyle.FixedDialog,
|
||||||
|
StartPosition = FormStartPosition.CenterParent,
|
||||||
|
ClientSize = new Size(360, 140),
|
||||||
|
MaximizeBox = false,
|
||||||
|
MinimizeBox = false,
|
||||||
|
ShowInTaskbar = false,
|
||||||
|
BackColor = SettingsUi.Background,
|
||||||
|
ForeColor = SettingsUi.Text
|
||||||
|
};
|
||||||
|
|
||||||
|
Label label = new()
|
||||||
|
{
|
||||||
|
Text = "Kennwort für die Zertifikatsdatei:",
|
||||||
|
Location = new Point(16, 16),
|
||||||
|
AutoSize = true
|
||||||
|
};
|
||||||
|
|
||||||
|
TextBox txtPwd = new()
|
||||||
|
{
|
||||||
|
Location = new Point(16, 44),
|
||||||
|
Width = 320,
|
||||||
|
UseSystemPasswordChar = true
|
||||||
|
};
|
||||||
|
|
||||||
|
Button btnOk = SettingsUi.CreatePrimaryButton("OK");
|
||||||
|
btnOk.Location = new Point(196, 90);
|
||||||
|
btnOk.Size = new Size(140, 32);
|
||||||
|
btnOk.DialogResult = DialogResult.OK;
|
||||||
|
|
||||||
|
Button btnCancel = SettingsUi.CreateGhostButton("Abbrechen");
|
||||||
|
btnCancel.Location = new Point(100, 90);
|
||||||
|
btnCancel.Size = new Size(90, 32);
|
||||||
|
btnCancel.DialogResult = DialogResult.Cancel;
|
||||||
|
|
||||||
|
dialog.Controls.Add(label);
|
||||||
|
dialog.Controls.Add(txtPwd);
|
||||||
|
dialog.Controls.Add(btnOk);
|
||||||
|
dialog.Controls.Add(btnCancel);
|
||||||
|
dialog.AcceptButton = btnOk;
|
||||||
|
dialog.CancelButton = btnCancel;
|
||||||
|
|
||||||
|
return dialog.ShowDialog(owner) == DialogResult.OK
|
||||||
|
? txtPwd.Text
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task AddSystemAsync()
|
private async Task AddSystemAsync()
|
||||||
{
|
{
|
||||||
using AddSonicSystemForm dialog = new(_coordinator);
|
using AddSonicSystemForm dialog = new(_coordinator);
|
||||||
@@ -706,78 +817,6 @@ public sealed class SetupWizardForm : Form
|
|||||||
"Prüfung OK",
|
"Prüfung OK",
|
||||||
MessageBoxButtons.OK,
|
MessageBoxButtons.OK,
|
||||||
MessageBoxIcon.Information);
|
MessageBoxIcon.Information);
|
||||||
|
|
||||||
SonicSystemOption? createdSystem = _systems
|
|
||||||
.FirstOrDefault(system =>
|
|
||||||
system.SonicConnectionId == createdId);
|
|
||||||
|
|
||||||
if (createdSystem is null
|
|
||||||
&& _lvSystems.SelectedItems.Count > 0)
|
|
||||||
{
|
|
||||||
createdSystem =
|
|
||||||
_lvSystems.SelectedItems[0].Tag as SonicSystemOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (createdSystem is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DialogResult next = MessageBox.Show(
|
|
||||||
this,
|
|
||||||
"Container jetzt per Sonic-Scan oder manuell zuordnen?",
|
|
||||||
"Container",
|
|
||||||
MessageBoxButtons.YesNo,
|
|
||||||
MessageBoxIcon.Question);
|
|
||||||
|
|
||||||
if (next != DialogResult.Yes)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using SelectContainersForm selectForm =
|
|
||||||
new(_coordinator, createdSystem);
|
|
||||||
|
|
||||||
if (selectForm.ShowDialog(this) == DialogResult.OK)
|
|
||||||
{
|
|
||||||
await LoadContainersListAsync();
|
|
||||||
await RefreshAllAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ScanContainersForSelectedSystemAsync()
|
|
||||||
{
|
|
||||||
SonicSystemOption? system = null;
|
|
||||||
|
|
||||||
if (_lvSystems.SelectedItems.Count > 0)
|
|
||||||
{
|
|
||||||
system = _lvSystems.SelectedItems[0].Tag as SonicSystemOption;
|
|
||||||
}
|
|
||||||
|
|
||||||
system ??= _systems.FirstOrDefault();
|
|
||||||
|
|
||||||
if (system is null)
|
|
||||||
{
|
|
||||||
// Systeme-Liste ggf. nachladen
|
|
||||||
await LoadSystemsListAsync();
|
|
||||||
system = _systems.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (system is null)
|
|
||||||
{
|
|
||||||
ShowWarning(
|
|
||||||
"Bitte zuerst ein Sonic-System anlegen oder auswählen.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using SelectContainersForm selectForm =
|
|
||||||
new(_coordinator, system);
|
|
||||||
|
|
||||||
if (selectForm.ShowDialog(this) == DialogResult.OK)
|
|
||||||
{
|
|
||||||
await LoadContainersListAsync();
|
|
||||||
await RefreshAllAsync();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddContainerAsync()
|
private async Task AddContainerAsync()
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.management.ObjectName;
|
import javax.management.ObjectName;
|
||||||
|
|
||||||
@@ -11,14 +9,13 @@ import javax.management.ObjectName;
|
|||||||
* Befehle:
|
* Befehle:
|
||||||
* validate - Anmeldung + lesender Container-Zugriff
|
* validate - Anmeldung + lesender Container-Zugriff
|
||||||
* restart - Container neu starten (stop/restart)
|
* restart - Container neu starten (stop/restart)
|
||||||
* list - Container der Domain auflisten
|
|
||||||
*
|
*
|
||||||
* javac -source 8 -target 8 SonicMfContainerTool.java
|
* javac -source 8 -target 8 SonicMfContainerTool.java
|
||||||
*/
|
*/
|
||||||
public final class SonicMfContainerTool
|
public final class SonicMfContainerTool
|
||||||
{
|
{
|
||||||
public static final String TOOL_VERSION =
|
public static final String TOOL_VERSION =
|
||||||
"2026-07-29-validate-restart-list";
|
"2026-07-29-validate-restart";
|
||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
{
|
||||||
@@ -29,16 +26,15 @@ public final class SonicMfContainerTool
|
|||||||
Args parsedArguments = Args.parse(args);
|
Args parsedArguments = Args.parse(args);
|
||||||
|
|
||||||
if (!"validate".equals(parsedArguments.command)
|
if (!"validate".equals(parsedArguments.command)
|
||||||
&& !"restart".equals(parsedArguments.command)
|
&& !"restart".equals(parsedArguments.command))
|
||||||
&& !"list".equals(parsedArguments.command))
|
|
||||||
{
|
{
|
||||||
fail(
|
fail(
|
||||||
"Usage: SonicMfContainerTool "
|
"Usage: SonicMfContainerTool "
|
||||||
+ "<validate|restart|list> "
|
+ "<validate|restart> "
|
||||||
+ "--domain D "
|
+ "--domain D "
|
||||||
+ "--url U "
|
+ "--url U "
|
||||||
+ "--user U "
|
+ "--user U "
|
||||||
+ "[--container C] "
|
+ "--container C "
|
||||||
+ "[--timeout SEC]");
|
+ "[--timeout SEC]");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -51,12 +47,7 @@ public final class SonicMfContainerTool
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean needsContainer =
|
if (isBlank(parsedArguments.container))
|
||||||
"validate".equals(parsedArguments.command)
|
|
||||||
|| "restart".equals(parsedArguments.command);
|
|
||||||
|
|
||||||
if (needsContainer
|
|
||||||
&& isBlank(parsedArguments.container))
|
|
||||||
{
|
{
|
||||||
fail("container ist Pflicht fuer validate/restart.");
|
fail("container ist Pflicht fuer validate/restart.");
|
||||||
return;
|
return;
|
||||||
@@ -82,13 +73,7 @@ public final class SonicMfContainerTool
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if ("list".equals(parsedArguments.command))
|
if ("validate".equals(
|
||||||
{
|
|
||||||
listContainers(
|
|
||||||
connector,
|
|
||||||
parsedArguments.domain);
|
|
||||||
}
|
|
||||||
else if ("validate".equals(
|
|
||||||
parsedArguments.command))
|
parsedArguments.command))
|
||||||
{
|
{
|
||||||
validateConnection(
|
validateConnection(
|
||||||
@@ -126,115 +111,6 @@ public final class SonicMfContainerTool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void listContainers(
|
|
||||||
Object connector,
|
|
||||||
String domain)
|
|
||||||
throws Exception
|
|
||||||
{
|
|
||||||
Method queryNamesMethod =
|
|
||||||
findMethod(
|
|
||||||
connector.getClass(),
|
|
||||||
"queryNames",
|
|
||||||
new Class[]
|
|
||||||
{
|
|
||||||
ObjectName.class,
|
|
||||||
javax.management.QueryExp.class
|
|
||||||
});
|
|
||||||
|
|
||||||
if (queryNamesMethod == null)
|
|
||||||
{
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"queryNames(ObjectName, QueryExp) wurde nicht gefunden.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectName pattern =
|
|
||||||
new ObjectName("*:ID=AGENT");
|
|
||||||
|
|
||||||
Object rawResult = queryNamesMethod.invoke(
|
|
||||||
connector,
|
|
||||||
new Object[]
|
|
||||||
{
|
|
||||||
pattern,
|
|
||||||
null
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!(rawResult instanceof Set))
|
|
||||||
{
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"queryNames lieferte kein Set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Set names = (Set) rawResult;
|
|
||||||
String domainPrefix = domain + ".";
|
|
||||||
int count = 0;
|
|
||||||
|
|
||||||
System.out.println("OK:ContainerListBegin");
|
|
||||||
|
|
||||||
Iterator iterator = names.iterator();
|
|
||||||
|
|
||||||
while (iterator.hasNext())
|
|
||||||
{
|
|
||||||
Object entry = iterator.next();
|
|
||||||
|
|
||||||
if (!(entry instanceof ObjectName))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectName objectName = (ObjectName) entry;
|
|
||||||
String canonical = objectName.getCanonicalName();
|
|
||||||
|
|
||||||
if (canonical == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Erwartet: domain.containerName:ID=AGENT
|
|
||||||
if (!canonical.regionMatches(
|
|
||||||
true,
|
|
||||||
0,
|
|
||||||
domainPrefix,
|
|
||||||
0,
|
|
||||||
domainPrefix.length()))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int colon = canonical.indexOf(':');
|
|
||||||
|
|
||||||
if (colon <= domainPrefix.length())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String containerName =
|
|
||||||
canonical.substring(
|
|
||||||
domainPrefix.length(),
|
|
||||||
colon);
|
|
||||||
|
|
||||||
if (isBlank(containerName))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println(
|
|
||||||
"CONTAINER="
|
|
||||||
+ containerName.trim());
|
|
||||||
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println(
|
|
||||||
"OK:ContainerListEnd count="
|
|
||||||
+ count
|
|
||||||
+ " domain="
|
|
||||||
+ domain
|
|
||||||
+ " tool="
|
|
||||||
+ TOOL_VERSION);
|
|
||||||
|
|
||||||
System.out.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object connect(
|
private static Object connect(
|
||||||
String url,
|
String url,
|
||||||
String user,
|
String user,
|
||||||
|
|||||||
@@ -14,5 +14,13 @@
|
|||||||
"CertificateThumbprint": "95E906CCC9C5F227969877CF3BD46D2AD7168853",
|
"CertificateThumbprint": "95E906CCC9C5F227969877CF3BD46D2AD7168853",
|
||||||
"StoreName": "My",
|
"StoreName": "My",
|
||||||
"StoreLocation": "CurrentUser"
|
"StoreLocation": "CurrentUser"
|
||||||
|
},
|
||||||
|
"Demo": {
|
||||||
|
"Enabled": true,
|
||||||
|
"TargetDirectory": "\\\\dekun-painwbdet\\e$\\proalpha-inwb\\test\\Containers\\proalpha-test.ct-ZADBService\\certs",
|
||||||
|
"TargetFileName": "server.p12",
|
||||||
|
"ContainerName": "ct-ZADBService",
|
||||||
|
"DisplayName": "DE / Demo ZADBService",
|
||||||
|
"SonicConnectionName": "DE-Test"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user