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 <cursoragent@cursor.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// Remote-Payload als Base64 einbetten und per [scriptblock]::Create ausführen.
|
||||
/// Vermeidet verschachteltes ScriptBlock-Brace-Nesting und lokale $-Expansion.
|
||||
/// WinRM-Credentials: nur <see cref="SonicConnection.WinRmUsername"/> / WinRmPassword.
|
||||
/// Leer = aktueller Windows-Benutzer (ohne -Credential).
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erkennt typische WinRM-/Windows-Auth-Fehler und liefert eine klare Handlungsanweisung.
|
||||
/// Username/Password in appsettings sind Sonic-SMC – nicht Windows/WinRM.
|
||||
/// </summary>
|
||||
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<string> 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(
|
||||
|
||||
Reference in New Issue
Block a user