Public/Connect-SSHTunnel.ps1
|
function Connect-SSHTunnel { <# .SYNOPSIS Create an SSH tunnel for database or service access. .PARAMETER Target Server alias (from config.json) or hostname. .PARAMETER RemotePort Remote port number or database type name (mysql, postgres, etc.). .PARAMETER LocalPort Local port to bind (defaults to same as remote). .PARAMETER RemoteHost Remote host for the tunnel (default: localhost). .EXAMPLE Connect-SSHTunnel myserver postgres tunnel myserver mysql 3307 #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [string]$Target, [Parameter(Position = 1)] [string]$RemotePort = "3306", [Parameter(Position = 2)] [int]$LocalPort = 0, [Parameter()] [string]$RemoteHost = "localhost" ) $config = Get-ScriptConfig if (-not $config) { Write-Host "Please configure config.json before using SSH commands." -ForegroundColor Red return } $dbPorts = @{} if ($config.ssh.databasePorts) { foreach ($key in $config.ssh.databasePorts.PSObject.Properties.Name) { $dbPorts[$key] = $config.ssh.databasePorts.$key } } if ($dbPorts.ContainsKey($RemotePort.ToLower())) { $RemotePort = $dbPorts[$RemotePort.ToLower()] if ($LocalPort -eq 0) { $LocalPort = $RemotePort } } else { try { $RemotePort = [int]$RemotePort } catch { Write-Host "Invalid port: $RemotePort" -ForegroundColor Red Write-Host "Use a port number or one of: $($dbPorts.Keys -join ', ')" -ForegroundColor Yellow return } } if ($LocalPort -eq 0) { $LocalPort = $RemotePort } $ssh = Resolve-SSHTarget -Target $Target -Config $config if (-not $ssh) { return } $Server = $ssh.Server $username = $ssh.UserName $password = $ssh.Password $cred = $ssh.Credential $keyFilePath = $ssh.KeyFilePath $keyFile = [bool]$keyFilePath Write-Host "Tunnel: localhost:$LocalPort -> ${RemoteHost}:${RemotePort} (via $Server)" -ForegroundColor Cyan $useWSL = Test-WslAvailable if ($keyFile) { $winSsh = Get-Command ssh.exe -ErrorAction SilentlyContinue if ($winSsh) { & ssh.exe -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i $keyFilePath -N -L "${LocalPort}:${RemoteHost}:${RemotePort}" "$username@$Server" } elseif ($useWSL) { $escapedPath = $keyFilePath -replace '\\', '/' $wslKeyPath = (wsl wslpath -u "'$escapedPath'").Trim() wsl bash -c "ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i '$wslKeyPath' -N -L ${LocalPort}:${RemoteHost}:${RemotePort} $username@$Server" } else { try { Import-Module Posh-SSH -ErrorAction Stop $session = New-SSHSession -ComputerName $Server -KeyFile $keyFilePath -AcceptKey New-SSHLocalPortForward -SSHSession $session -BoundHost "127.0.0.1" -BoundPort $LocalPort -RemoteAddress $RemoteHost -RemotePort $RemotePort | Out-Null Start-Sleep -Seconds 999999 } catch { Write-Host "Tunnel setup failed: $($_.Exception.Message)" -ForegroundColor Red return } finally { if ($session) { Remove-SSHSession -SessionId $session.SessionId | Out-Null } } } } elseif ($useWSL) { $hasSshpass = wsl bash -c "command -v sshpass >/dev/null 2>&1 && echo 'yes' || echo 'no'" if ($hasSshpass -match 'no') { Write-Host "Installing sshpass in WSL..." -ForegroundColor Yellow wsl bash -c "sudo apt-get update && sudo apt-get install -y sshpass" } wsl bash -c "SSHPASS='$password' sshpass -e ssh -o StrictHostKeyChecking=no -N -L ${LocalPort}:${RemoteHost}:${RemotePort} $username@$Server" } else { $nativeSsh = Get-Command ssh.exe -ErrorAction SilentlyContinue if ($nativeSsh) { $sshArgs = @( "-o", "StrictHostKeyChecking=no", "-o", "IdentitiesOnly=yes", "-N", "-L", "${LocalPort}:${RemoteHost}:${RemotePort}", "$username@$Server" ) & ssh.exe @sshArgs } else { try { Import-Module Posh-SSH -ErrorAction Stop $session = New-SSHSession -ComputerName $Server -Credential $cred -AcceptKey New-SSHLocalPortForward -SSHSession $session -BoundHost "127.0.0.1" -BoundPort $LocalPort -RemoteAddress $RemoteHost -RemotePort $RemotePort | Out-Null Start-Sleep -Seconds 999999 } catch { Write-Host "Tunnel setup failed: $($_.Exception.Message)" -ForegroundColor Red return } finally { if ($session) { Remove-SSHSession -SessionId $session.SessionId | Out-Null } } } } } |