Public/Ssh/New-VmSshClient.ps1
|
# --------------------------------------------------------------------------- # New-VmSshClient # Creates and connects a Renci.SshNet.SshClient using password # authentication. Returns the connected client; the caller is responsible # for calling Disconnect() and Dispose() in a finally block. # # SSH.NET is used directly rather than Posh-SSH cmdlets because # ConnectionInfoGenerator in Posh-SSH 3.x drops algorithm entries from # the SSH.NET ConnectionInfo, breaking key exchange against OpenSSH 9.x # (Ubuntu 24.04). Posh-SSH must still be installed - it ships the # Renci.SshNet.dll the function depends on. # # Renci types are referenced inside the function body (not as parameter # types) so the module imports cleanly on hosts without Posh-SSH. The # Assert-SshNetLoaded guard turns the otherwise opaque "type not found" # error into an actionable message naming the missing prerequisite. # # -Timeout caps the total Connect() wall-clock. SSH.NET applies the # same value to both socket-read and KEX, so a long Timeout is the # right knob when a slow server-side responder (e.g. sshd held off # by cloud-config until users are created) needs more than SSH.NET's # 30s default. The Connect call is synchronous - the caller pays a # single block of up to -Timeout with no console output in between. # Consumers waiting on multi-minute connects should print a leading # "this may take a few minutes" line so the silence does not read # like a hang. # # Security: # - SSH.NET accepts any host key by default (no HostKeyReceived handler). # Equivalent to Posh-SSH's -AcceptKey. Acceptable on a private Hyper-V # network with statically provisioned IPs; do NOT use on untrusted # networks without supplying a fingerprint check. # - Password is required as a plain string by SSH.NET's # PasswordAuthenticationMethod constructor. Callers should source the # value from SecretManagement and avoid logging it. # --------------------------------------------------------------------------- function New-VmSshClient { [Diagnostics.CodeAnalysis.SuppressMessageAttribute( 'PSAvoidUsingPlainTextForPassword', 'Password')] [CmdletBinding()] param( [Parameter(Mandatory)] [string] $IpAddress, [Parameter(Mandatory)] [string] $Username, # Plain string required by SSH.NET PasswordAuthenticationMethod. [Parameter(Mandatory)] [string] $Password, # Total Connect() wall-clock budget. Default 30s matches SSH.NET's # built-in default so existing callers are unaffected. Callers # waiting on a slow server-side responder (e.g. provisioning, # where sshd is ordered after cloud-config) pass a generous value. [TimeSpan] $Timeout = [TimeSpan]::FromSeconds(30) ) Assert-SshNetLoaded $auth = [Renci.SshNet.PasswordAuthenticationMethod]::new($Username, $Password) $connInfo = [Renci.SshNet.ConnectionInfo]::new($IpAddress, $Username, @($auth)) # SSH.NET applies ConnectionInfo.Timeout to both the TCP socket read # and the KEX exchange. $connInfo.Timeout = $Timeout $client = [Renci.SshNet.SshClient]::new($connInfo) $client.Connect() $client } |