From d71d6990adaa3ae4c1abe18a40259583cb696ca6 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 24 Jul 2026 08:47:35 +0200 Subject: [PATCH] Separate WinRM Windows credentials from Sonic SMC logins. WinRM no longer reuses Administrator/Administrator; empty WinRm* uses the current Windows user, with clearer auth errors and LocalCmd guidance. Co-authored-by: Cursor --- .../Models/SonicConnection.cs | 18 +++ .../Services/SonicManagementClient.cs | 4 +- .../Services/WinRmExecutor.cs | 113 +++++++++++++++--- .../appsettings.json | 5 + 4 files changed, 125 insertions(+), 15 deletions(-) diff --git a/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs b/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs index 62305e6..ff4a63b 100644 --- a/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs +++ b/ZA.CoreService.ESBCertificateManager/Models/SonicConnection.cs @@ -21,9 +21,27 @@ public sealed class SonicConnection /// public required string ConnectionUrl { get; init; } + /// Sonic SMC / Domain Manager Login (nicht für WinRM). public required string Username { get; init; } + + /// Sonic SMC / Domain Manager Passwort (nicht für WinRM). public required string Password { get; init; } + /// + /// Windows-Konto für WinRM (Invoke-Command -Credential). + /// Leer = aktueller Prozess-Benutzer ohne explizite Credentials. + /// Nicht mit / (Sonic SMC) verwechseln. + /// + public string WinRmUsername { get; init; } = string.Empty; + + /// Windows-Passwort für WinRM; nur relevant wenn gesetzt ist. + public string WinRmPassword { get; init; } = string.Empty; + + /// + /// WinRm = remote via PowerShell Remoting; + /// LocalCmd = lokal auf dem Sonic-Server (kein WinRM, App muss dort laufen); + /// HttpApi = REST. + /// public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.WinRm; /// Sonic-Installationsroot, z.B. "C:\Sonic\MQ10.0". diff --git a/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs b/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs index 11f633b..eca3359 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/SonicManagementClient.cs @@ -11,9 +11,11 @@ namespace ZA.CoreService.ESBCertificateManager.Services; /// /// Modus = WinRm (Standard): /// PowerShell Remoting (Invoke-Command) → CMD stop/startcontainer auf dem Sonic-Server. +/// WinRM nutzt WinRmUsername/WinRmPassword (Windows); leer = aktueller Benutzer. +/// Username/Password bleiben Sonic-SMC-/Domain-Manager-Logins. /// /// Modus = LocalCmd: -/// Dieselbe CMD/PowerShell-Logik lokal (Test direkt auf dem Sonic-PC). +/// Dieselbe CMD/PowerShell-Logik lokal (App muss auf dem Sonic-PC laufen; kein WinRM). /// /// Modus = HttpApi: /// HTTP REST API mit automatischer Pfad-Erkennung. diff --git a/ZA.CoreService.ESBCertificateManager/Services/WinRmExecutor.cs b/ZA.CoreService.ESBCertificateManager/Services/WinRmExecutor.cs index 7cbd386..858775c 100644 --- a/ZA.CoreService.ESBCertificateManager/Services/WinRmExecutor.cs +++ b/ZA.CoreService.ESBCertificateManager/Services/WinRmExecutor.cs @@ -21,6 +21,9 @@ public sealed class WinRmExecutor _connection = connection; } + private bool HasExplicitWinRmCredentials + => !string.IsNullOrWhiteSpace(_connection.WinRmUsername); + public async Task<(bool Success, string? Error)> TestConnectionAsync( CancellationToken cancellationToken = default) { @@ -56,7 +59,7 @@ public sealed class WinRmExecutor return sessionOk ? (true, null) - : (false, $"WinRM-Verbindung fehlgeschlagen: {sessionError}"); + : (false, sessionError ?? "WinRM-Verbindung fehlgeschlagen."); } public async Task<(bool Success, string? Output, string? Error)> RunScriptAsync( @@ -119,8 +122,15 @@ public sealed class WinRmExecutor return (true, stdout, stderr.Length > 0 ? stderr : null); } - return (false, stdout.Length > 0 ? stdout : null, - stderr.Length > 0 ? Truncate(stderr) : $"PowerShell ExitCode={process.ExitCode}"); + string rawError = stderr.Length > 0 + ? stderr + : $"PowerShell ExitCode={process.ExitCode}"; + + string error = _connection.ManagementMode == SonicManagementMode.WinRm + ? FormatWinRmFailure(rawError) + : Truncate(rawError); + + return (false, stdout.Length > 0 ? stdout : null, error); } finally { @@ -134,24 +144,99 @@ public sealed class WinRmExecutor /// /// Remote-Payload als Base64 einbetten und per [scriptblock]::Create ausführen. /// Vermeidet verschachteltes ScriptBlock-Brace-Nesting und lokale $-Expansion. + /// WinRM-Credentials: nur / WinRmPassword. + /// Leer = aktueller Windows-Benutzer (ohne -Credential). /// private string BuildRemoteScript(string host, string scriptBlock) { - string escapedPwd = _connection.Password.Replace("'", "''"); - string escapedUser = _connection.Username.Replace("'", "''"); string escapedHost = host.Replace("'", "''"); string remoteB64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock)); + StringBuilder sb = new(); + sb.Append("$ErrorActionPreference = 'Stop'\n"); + + if (HasExplicitWinRmCredentials) + { + string escapedPwd = (_connection.WinRmPassword ?? string.Empty).Replace("'", "''"); + string escapedUser = _connection.WinRmUsername.Replace("'", "''"); + sb.Append($"$secPwd = ConvertTo-SecureString '{escapedPwd}' -AsPlainText -Force\n"); + sb.Append($"$cred = New-Object System.Management.Automation.PSCredential('{escapedUser}', $secPwd)\n"); + } + + sb.Append($"$remoteB64 = '{remoteB64}'\n"); + sb.Append("$remoteScript = [System.Text.Encoding]::Unicode.GetString("); + sb.Append("[System.Convert]::FromBase64String($remoteB64))\n"); + sb.Append("$sb = [scriptblock]::Create($remoteScript)\n"); + sb.Append($"Invoke-Command -ComputerName '{escapedHost}' -Port {_connection.WinRmPort} "); + + if (HasExplicitWinRmCredentials) + { + sb.Append("-Credential $cred "); + } + + sb.Append("-ScriptBlock $sb -ErrorAction Stop"); + return sb.ToString(); + } + + /// + /// Erkennt typische WinRM-/Windows-Auth-Fehler und liefert eine klare Handlungsanweisung. + /// Username/Password in appsettings sind Sonic-SMC – nicht Windows/WinRM. + /// + private string FormatWinRmFailure(string rawError) + { + string truncated = Truncate(rawError); + if (!LooksLikeWinRmAuthFailure(rawError)) + { + return truncated; + } + + string authHint = HasExplicitWinRmCredentials + ? "WinRmUsername/WinRmPassword prüfen (Windows-Konto mit WinRM-Rechten auf dem Zielrechner)." + : "Aktueller Windows-Benutzer hat keine WinRM-Berechtigung auf dem Zielrechner " + + "(oder Kerberos/CredSSP fehlt). WinRmUsername/WinRmPassword setzen " + + "oder App unter einem berechtigten Windows-Konto starten."; + return - "$ErrorActionPreference = 'Stop'\n" + - $"$secPwd = ConvertTo-SecureString '{escapedPwd}' -AsPlainText -Force\n" + - $"$cred = New-Object System.Management.Automation.PSCredential('{escapedUser}', $secPwd)\n" + - $"$remoteB64 = '{remoteB64}'\n" + - "$remoteScript = [System.Text.Encoding]::Unicode.GetString(" + - "[System.Convert]::FromBase64String($remoteB64))\n" + - "$sb = [scriptblock]::Create($remoteScript)\n" + - $"Invoke-Command -ComputerName '{escapedHost}' -Port {_connection.WinRmPort} " + - "-Credential $cred -ScriptBlock $sb -ErrorAction Stop"; + "WinRM-Authentifizierung fehlgeschlagen: Windows-Anmeldedaten falsch oder fehlend.\n" + + "Hinweis: Username/Password in appsettings sind Sonic-SMC-/Domain-Manager-Logins – " + + "NICHT für WinRM/Windows.\n" + + authHint + "\n" + + "Alternativen:\n" + + " • ManagementMode=LocalCmd setzen und die App direkt auf dem Sonic-Server starten (kein WinRM).\n" + + " • WinRmUsername/WinRmPassword mit gültigem Windows-Konto befüllen.\n" + + $"Details: {truncated}"; + } + + private static bool LooksLikeWinRmAuthFailure(string error) + { + if (string.IsNullOrEmpty(error)) + { + return false; + } + + ReadOnlySpan markers = + [ + "Benutzername oder das Kennwort ist falsch", + "username or password is incorrect", + "Access is denied", + "Zugriff verweigert", + "Logon failure", + "Anmeldefehler", + "PSRemotingTransportException", + "UnauthorizedAccess", + "WinRM cannot process the request", + "der remotecomputer hat den netzwerkdatenverkehr verweigert" + ]; + + foreach (string marker in markers) + { + if (error.Contains(marker, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; } public static string ApplyScriptTemplate( diff --git a/ZA.CoreService.ESBCertificateManager/appsettings.json b/ZA.CoreService.ESBCertificateManager/appsettings.json index 5d7e440..528b2cb 100644 --- a/ZA.CoreService.ESBCertificateManager/appsettings.json +++ b/ZA.CoreService.ESBCertificateManager/appsettings.json @@ -10,8 +10,13 @@ "Name": "DE-Test", "DomainName": "proalpha-test", "ConnectionUrl": "tcp://dekun-painwbdet:13070", + // Sonic SMC / Domain Manager (NICHT für WinRM): "Username": "Administrator", "Password": "Administrator", + // Windows für WinRM: leer = aktueller Benutzer (z.B. gisler). Nie Sonic-SMC-Logins hier eintragen. + // Lokal auf dem Sonic-Server testen: ManagementMode auf "LocalCmd" setzen (kein WinRM nötig). + "WinRmUsername": "", + "WinRmPassword": "", "ManagementMode": "WinRm", "SonicHome": "C:\\Sonic\\MQ10.0", "KnownContainers": [