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:
@@ -21,9 +21,27 @@ public sealed class SonicConnection
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public required string ConnectionUrl { get; init; }
|
public required string ConnectionUrl { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Sonic SMC / Domain Manager Login (nicht für WinRM).</summary>
|
||||||
public required string Username { get; init; }
|
public required string Username { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Sonic SMC / Domain Manager Passwort (nicht für WinRM).</summary>
|
||||||
public required string Password { get; init; }
|
public required string Password { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Windows-Konto für WinRM (<c>Invoke-Command -Credential</c>).
|
||||||
|
/// Leer = aktueller Prozess-Benutzer ohne explizite Credentials.
|
||||||
|
/// Nicht mit <see cref="Username"/>/<see cref="Password"/> (Sonic SMC) verwechseln.
|
||||||
|
/// </summary>
|
||||||
|
public string WinRmUsername { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Windows-Passwort für WinRM; nur relevant wenn <see cref="WinRmUsername"/> gesetzt ist.</summary>
|
||||||
|
public string WinRmPassword { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// WinRm = remote via PowerShell Remoting;
|
||||||
|
/// LocalCmd = lokal auf dem Sonic-Server (kein WinRM, App muss dort laufen);
|
||||||
|
/// HttpApi = REST.
|
||||||
|
/// </summary>
|
||||||
public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.WinRm;
|
public SonicManagementMode ManagementMode { get; init; } = SonicManagementMode.WinRm;
|
||||||
|
|
||||||
/// <summary>Sonic-Installationsroot, z.B. "C:\Sonic\MQ10.0".</summary>
|
/// <summary>Sonic-Installationsroot, z.B. "C:\Sonic\MQ10.0".</summary>
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ namespace ZA.CoreService.ESBCertificateManager.Services;
|
|||||||
///
|
///
|
||||||
/// Modus = WinRm (Standard):
|
/// Modus = WinRm (Standard):
|
||||||
/// PowerShell Remoting (Invoke-Command) → CMD stop/startcontainer auf dem Sonic-Server.
|
/// 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:
|
/// 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:
|
/// Modus = HttpApi:
|
||||||
/// HTTP REST API mit automatischer Pfad-Erkennung.
|
/// HTTP REST API mit automatischer Pfad-Erkennung.
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ public sealed class WinRmExecutor
|
|||||||
_connection = connection;
|
_connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool HasExplicitWinRmCredentials
|
||||||
|
=> !string.IsNullOrWhiteSpace(_connection.WinRmUsername);
|
||||||
|
|
||||||
public async Task<(bool Success, string? Error)> TestConnectionAsync(
|
public async Task<(bool Success, string? Error)> TestConnectionAsync(
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
@@ -56,7 +59,7 @@ public sealed class WinRmExecutor
|
|||||||
|
|
||||||
return sessionOk
|
return sessionOk
|
||||||
? (true, null)
|
? (true, null)
|
||||||
: (false, $"WinRM-Verbindung fehlgeschlagen: {sessionError}");
|
: (false, sessionError ?? "WinRM-Verbindung fehlgeschlagen.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(bool Success, string? Output, string? Error)> RunScriptAsync(
|
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 (true, stdout, stderr.Length > 0 ? stderr : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (false, stdout.Length > 0 ? stdout : null,
|
string rawError = stderr.Length > 0
|
||||||
stderr.Length > 0 ? Truncate(stderr) : $"PowerShell ExitCode={process.ExitCode}");
|
? stderr
|
||||||
|
: $"PowerShell ExitCode={process.ExitCode}";
|
||||||
|
|
||||||
|
string error = _connection.ManagementMode == SonicManagementMode.WinRm
|
||||||
|
? FormatWinRmFailure(rawError)
|
||||||
|
: Truncate(rawError);
|
||||||
|
|
||||||
|
return (false, stdout.Length > 0 ? stdout : null, error);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -134,24 +144,99 @@ public sealed class WinRmExecutor
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remote-Payload als Base64 einbetten und per [scriptblock]::Create ausführen.
|
/// Remote-Payload als Base64 einbetten und per [scriptblock]::Create ausführen.
|
||||||
/// Vermeidet verschachteltes ScriptBlock-Brace-Nesting und lokale $-Expansion.
|
/// Vermeidet verschachteltes ScriptBlock-Brace-Nesting und lokale $-Expansion.
|
||||||
|
/// WinRM-Credentials: nur <see cref="SonicConnection.WinRmUsername"/> / WinRmPassword.
|
||||||
|
/// Leer = aktueller Windows-Benutzer (ohne -Credential).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string BuildRemoteScript(string host, string scriptBlock)
|
private string BuildRemoteScript(string host, string scriptBlock)
|
||||||
{
|
{
|
||||||
string escapedPwd = _connection.Password.Replace("'", "''");
|
|
||||||
string escapedUser = _connection.Username.Replace("'", "''");
|
|
||||||
string escapedHost = host.Replace("'", "''");
|
string escapedHost = host.Replace("'", "''");
|
||||||
string remoteB64 = Convert.ToBase64String(Encoding.Unicode.GetBytes(scriptBlock));
|
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
|
return
|
||||||
"$ErrorActionPreference = 'Stop'\n" +
|
"WinRM-Authentifizierung fehlgeschlagen: Windows-Anmeldedaten falsch oder fehlend.\n" +
|
||||||
$"$secPwd = ConvertTo-SecureString '{escapedPwd}' -AsPlainText -Force\n" +
|
"Hinweis: Username/Password in appsettings sind Sonic-SMC-/Domain-Manager-Logins – " +
|
||||||
$"$cred = New-Object System.Management.Automation.PSCredential('{escapedUser}', $secPwd)\n" +
|
"NICHT für WinRM/Windows.\n" +
|
||||||
$"$remoteB64 = '{remoteB64}'\n" +
|
authHint + "\n" +
|
||||||
"$remoteScript = [System.Text.Encoding]::Unicode.GetString(" +
|
"Alternativen:\n" +
|
||||||
"[System.Convert]::FromBase64String($remoteB64))\n" +
|
" • ManagementMode=LocalCmd setzen und die App direkt auf dem Sonic-Server starten (kein WinRM).\n" +
|
||||||
"$sb = [scriptblock]::Create($remoteScript)\n" +
|
" • WinRmUsername/WinRmPassword mit gültigem Windows-Konto befüllen.\n" +
|
||||||
$"Invoke-Command -ComputerName '{escapedHost}' -Port {_connection.WinRmPort} " +
|
$"Details: {truncated}";
|
||||||
"-Credential $cred -ScriptBlock $sb -ErrorAction Stop";
|
}
|
||||||
|
|
||||||
|
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(
|
public static string ApplyScriptTemplate(
|
||||||
|
|||||||
@@ -10,8 +10,13 @@
|
|||||||
"Name": "DE-Test",
|
"Name": "DE-Test",
|
||||||
"DomainName": "proalpha-test",
|
"DomainName": "proalpha-test",
|
||||||
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
|
"ConnectionUrl": "tcp://dekun-painwbdet:13070",
|
||||||
|
// Sonic SMC / Domain Manager (NICHT für WinRM):
|
||||||
"Username": "Administrator",
|
"Username": "Administrator",
|
||||||
"Password": "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",
|
"ManagementMode": "WinRm",
|
||||||
"SonicHome": "C:\\Sonic\\MQ10.0",
|
"SonicHome": "C:\\Sonic\\MQ10.0",
|
||||||
"KnownContainers": [
|
"KnownContainers": [
|
||||||
|
|||||||
Reference in New Issue
Block a user