Skip to content

Commit e5116ae

Browse files
authored
Add helper functions for SSH remoting tests (PowerShell#11955)
1 parent 2db8516 commit e5116ae

2 files changed

Lines changed: 334 additions & 4 deletions

File tree

test/tools/Modules/HelpersRemoting/HelpersRemoting.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.'
1616

1717
Description = 'Temporary module for remoting tests'
1818

19-
FunctionsToExport = 'New-RemoteRunspace', 'New-RemoteSession', 'Enter-RemoteSession', 'Invoke-RemoteCommand', 'Connect-RemoteSession', 'New-RemoteRunspacePool', 'Get-PipePath'
19+
FunctionsToExport = 'New-RemoteRunspace', 'New-RemoteSession', 'Enter-RemoteSession', 'Invoke-RemoteCommand', 'Connect-RemoteSession', 'New-RemoteRunspacePool', 'Get-PipePath', 'Install-SSHRemoting'
2020

2121
AliasesToExport = @()
2222

test/tools/Modules/HelpersRemoting/HelpersRemoting.psm1

Lines changed: 333 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3-
#
4-
# This module include help functions for writing remoting tests
5-
#
3+
4+
##
5+
## WinRM Remoting helper functions for writing remoting tests
6+
##
67

78
$Script:CIRemoteCred = $null
89

@@ -258,3 +259,332 @@ function Get-PipePath {
258259
}
259260
"$([System.IO.Path]::GetTempPath())CoreFxPipe_$PipeName"
260261
}
262+
263+
##
264+
## SSH Remoting helper functions for writing remoting tests
265+
##
266+
267+
function Get-WindowsOpenSSHLink
268+
{
269+
# From the Win OpenSSH Wiki page (https://github.com/PowerShell/Win32-OpenSSH/wiki/How-to-retrieve-links-to-latest-packages)
270+
$origSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol
271+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
272+
try
273+
{
274+
$url = 'https://github.com/PowerShell/Win32-OpenSSH/releases/latest/'
275+
$request = [System.Net.WebRequest]::Create($url)
276+
$request.AllowAutoRedirect = $false
277+
$response = & { $request.GetResponse() } 2>$null
278+
279+
if ($null -ne $response)
280+
{
281+
$location = [string] $response.GetResponseHeader("Location")
282+
if (! [string]::IsNullOrEmpty($location))
283+
{
284+
return $location.Replace('tag', 'download') + '/OpenSSH-Win64.zip'
285+
}
286+
}
287+
}
288+
finally
289+
{
290+
[Net.ServicePointManager]::SecurityProtocol = $origSecurityProtocol
291+
}
292+
293+
# Default to last known latest release
294+
Write-Warning "Unable to get latest OpenSSH release link. Using default release link."
295+
return 'https://github.com/PowerShell/Win32-OpenSSH/releases/download/v8.1.0.0p1-Beta/OpenSSH-Win64.zip'
296+
}
297+
298+
function Install-WindowsOpenSSH
299+
{
300+
param (
301+
[switch] $Force
302+
)
303+
304+
$destPath = Join-Path -Path $env:ProgramFiles -ChildPath 'OpenSSH-Win64'
305+
if (Test-Path -Path $destPath)
306+
{
307+
if (! $Force)
308+
{
309+
Write-Verbose -Verbose "OpenSSH-Win64 already exists, skipping install step"
310+
return
311+
}
312+
313+
Write-Verbose -Verbose "Force re-install OpenSSH-Win64 ..."
314+
Stop-Service -Name sshd -ErrorAction SilentlyContinue
315+
Remove-Item -Path $destPath -Recurse -Force
316+
}
317+
318+
# Get link to latest OpenSSH release
319+
Write-Verbose -Verbose "Downloading latest OpenSSH-Win64 package link ..."
320+
$downLoadLink = Get-WindowsOpenSSHLink
321+
322+
# Download and extract OpenSSH package
323+
Write-Verbose -Verbose "Downloading OpenSSH-Win64 zip package ..."
324+
$packageFilePath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath 'OpenSSH-Win64.zip'
325+
$oldProgressPreference = $ProgressPreference
326+
$ProgressPreference = 'SilentlyContinue'
327+
try
328+
{
329+
Invoke-WebRequest -Uri $downLoadLink -OutFile $packageFilePath
330+
Expand-Archive -Path $packageFilePath -DestinationPath $env:ProgramFiles
331+
}
332+
finally
333+
{
334+
$ProgressPreference = $oldProgressPreference
335+
}
336+
337+
# Install and start SSHD service
338+
Push-Location $destPath
339+
try
340+
{
341+
Write-Verbose -Verbose "Running install-sshd.ps1 ..."
342+
.\install-sshd.ps1
343+
344+
$netRule = Get-NetFirewallRule -Name sshd -ErrorAction SilentlyContinue
345+
if ($null -eq $netRule)
346+
{
347+
Write-Verbose -Verbose "Creating firewall rule for SSHD ..."
348+
New-NetFirewallRule -Name sshd -DisplayName "OpenSSH Server (sshd)" -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
349+
}
350+
351+
Write-Verbose -Verbose "Starting SSHD service ..."
352+
Restart-Service -Name sshd
353+
}
354+
finally
355+
{
356+
Pop-Location
357+
}
358+
359+
# Current release of Windows OpenSSH configures SSHD to change AuthorizedKeyFiles for administrators
360+
# Comment it out so that normal key based authentication works per user as with Linux platforms.
361+
# Match Group administrators
362+
# AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
363+
$sshdFilePath = "$env:ProgramData\ssh\sshd_config"
364+
$sshdContent = Get-Content $sshdFilePath
365+
$sshdNewContent = [string[]] @()
366+
$modified = $false
367+
foreach ($item in $sshdContent)
368+
{
369+
if ($item.TrimStart().StartsWith('Match Group administrators') -or
370+
$item.TrimStart().StartsWith('AuthorizedKeysFile __PROGRAMDATA'))
371+
{
372+
if (!$modified) { $modified = $true }
373+
$sshdNewContent += "#" + $item
374+
}
375+
else
376+
{
377+
$sshdNewContent += $item
378+
}
379+
}
380+
if ($modified)
381+
{
382+
$sshdNewContent | Set-Content -Path $sshdFilePath -Force
383+
}
384+
}
385+
386+
function Install-SSHRemotingOnWindows
387+
{
388+
param (
389+
[Parameter(Mandatory=$true)]
390+
[string] $PowerShellPath
391+
)
392+
393+
# Install sshd service
394+
if ($null -eq (Get-Command -Name sshd -ErrorAction SilentlyContinue))
395+
{
396+
Write-Verbose -Verbose "Installing SSHD service ..."
397+
Install-WindowsOpenSSH -Force
398+
}
399+
400+
if (! (Test-Path -Path "$env:ProgramData\ssh\sshd_config"))
401+
{
402+
throw "Unable to install SSH service. Config file $env:ProgramData\ssh\sshd_config does not exist."
403+
}
404+
405+
# Configure SSH to authenticate with keys for this user.
406+
# PubkeyAuthentication should be enabled by default.
407+
# RSA keys should be enabled by default.
408+
409+
# Create user .ssh directory.
410+
if (! (Test-Path -Path "$HOME\.ssh"))
411+
{
412+
Write-Verbose -Verbose "Creating $HOME\.ssh directory ..."
413+
New-Item -Path "$HOME\.ssh" -ItemType Directory -Force
414+
}
415+
416+
# Create new rsa keys for current user.
417+
if ( !(Test-Path "$HOME\.ssh\id_rsa"))
418+
{
419+
Write-Verbose -Verbose "Creating rsa keys ..."
420+
cmd /c "ssh-keygen -t rsa -f $HOME\.ssh\id_rsa -q -N `"`""
421+
}
422+
if (! (Test-Path "$HOME\.ssh\id_rsa"))
423+
{
424+
throw "id_rsa private key file was not created."
425+
}
426+
if (! (Test-Path "$HOME\.ssh\id_rsa.pub"))
427+
{
428+
throw "id_rsa.pub public key file was not created."
429+
}
430+
431+
# Create authorized keys file.
432+
Write-Verbose -Verbose "Creating authorized_keys ..."
433+
Get-Content -Path "$HOME\.ssh\id_rsa.pub" | Set-Content -Path "$HOME\.ssh\authorized_keys" -Force
434+
435+
# Create known_hosts file for 'localhost' connection.
436+
Write-Verbose -Verbose "Creating known_hosts ..."
437+
ssh-keyscan -H localhost | Set-Content -Path "$HOME\.ssh\known_hosts" -Force
438+
439+
# Install Microsoft.PowerShell.RemotingTools module.
440+
if ($null -eq (Get-Module -Name Microsoft.PowerShell.RemotingTools -ListAvailable))
441+
{
442+
Write-Verbose -Verbose "Installing Microsoft.PowerShell.RemotingTools ..."
443+
Install-Module -Name Microsoft.PowerShell.RemotingTools -Force -SkipPublisherCheck
444+
}
445+
446+
# Add PowerShell endpoint to SSHD.
447+
Write-Verbose -Verbose "Running Enable-SSHRemoting ..."
448+
Enable-SSHRemoting -SSHDConfigFilePath "$env:ProgramData\ssh\sshd_config" -PowerShellFilePath $PowerShellPath -Force
449+
450+
Write-Verbose -Verbose "Restarting sshd service ..."
451+
Restart-Service -Name sshd
452+
453+
# Test SSH remoting.
454+
Write-Verbose -Verbose "Testing SSH remote connection ..."
455+
$session = New-PSSession -HostName localhost
456+
try
457+
{
458+
if ($null -eq $session)
459+
{
460+
throw "Could not successfully create SSH remoting connection."
461+
}
462+
}
463+
finally
464+
{
465+
Remove-PSSession $session
466+
}
467+
}
468+
469+
function Install-SSHRemotingOnLinux
470+
{
471+
param (
472+
[Parameter(Mandatory=$true)]
473+
[string] $PowerShellPath
474+
)
475+
476+
# Install ssh daemon.
477+
if (! (Test-Path -Path /etc/ssh/sshd_config))
478+
{
479+
Write-Verbose -Verbose "Installing openssh-server ..."
480+
sudo apt-get install --yes openssh-server
481+
sudo systemctl restart ssh
482+
}
483+
if (! (Test-Path -Path /etc/ssh/sshd_config))
484+
{
485+
throw "Unable to install SSH daemon. Config file /etc/ssh/sshd_config does not exist."
486+
}
487+
488+
# Configure SSH to authenticate with keys for this user.
489+
# PubkeyAuthentication should be enabled by default.
490+
# RSA keys should be enabled by default.
491+
492+
# Create user .ssh directory.
493+
if (! (Test-Path -Path "$HOME/.ssh"))
494+
{
495+
Write-Verbose -Verbose "Creating $HOME/.ssh directory ..."
496+
New-Item -Path "$HOME/.ssh" -ItemType Directory -Force
497+
}
498+
499+
# Create new rsa keys for current user.
500+
if ( !(Test-Path "$HOME/.ssh/id_rsa"))
501+
{
502+
Write-Verbose -Verbose "Creating rsa keys ..."
503+
bash -c "ssh-keygen -t rsa -f $HOME/.ssh/id_rsa -q -N ''"
504+
}
505+
if (! (Test-Path "$HOME/.ssh/id_rsa"))
506+
{
507+
throw "id_rsa private key file was not created."
508+
}
509+
if (! (Test-Path "$HOME/.ssh/id_rsa.pub"))
510+
{
511+
throw "id_rsa.pub public key file was not created."
512+
}
513+
514+
# Create authorized keys file.
515+
Write-Verbose -Verbose "Creating authorized_keys ..."
516+
Get-Content -Path "$HOME/.ssh/id_rsa.pub" | Set-Content -Path "$HOME/.ssh/authorized_keys" -Force
517+
518+
# Create known_hosts file for 'localhost' connection.
519+
Write-Verbose -Verbose "Updating known_hosts ..."
520+
ssh-keyscan -H localhost | Set-Content -Path "$HOME/.ssh/known_hosts" -Force
521+
522+
# Install Microsoft.PowerShell.RemotingTools module.
523+
if ($null -eq (Get-Module -Name Microsoft.PowerShell.RemotingTools -ListAvailable))
524+
{
525+
Write-Verbose -Verbose "Installing Microsoft.PowerShell.RemotingTools ..."
526+
Install-Module -Name Microsoft.PowerShell.RemotingTools -Force -SkipPublisherCheck
527+
}
528+
529+
# Add PowerShell endpoint to SSHD.
530+
Write-Verbose -Verbose "Running Enable-SSHRemoting ..."
531+
sudo pwsh -c 'Enable-SSHRemoting -SSHDConfigFilePath /etc/ssh/sshd_config -PowerShellFilePath $PowerShellPath -Force'
532+
533+
Write-Verbose -Verbose "Restarting sshd ..."
534+
sudo systemctl restart ssh
535+
536+
# Test SSH remoting.
537+
Write-Verbose -Verbose "Testing SSH remote connection ..."
538+
$session = New-PSSession -HostName localhost
539+
try
540+
{
541+
if ($null -eq $session)
542+
{
543+
throw "Could not successfully create SSH remoting connection."
544+
}
545+
}
546+
finally
547+
{
548+
Remove-PSSession $session
549+
}
550+
}
551+
552+
<#
553+
.Synopsis
554+
Installs and configures SSH components, and creates an SSH PowerShell remoting endpoint.
555+
.Description
556+
This cmdlet assumes SSH client is installed on the machine, but will check for SSHD service and
557+
install it if needed.
558+
Next, it will configure SSHD for key based user authentication, for the current user context.
559+
Then it configures SSHD for a PowerShell endpoint based on the provided PowerShell file path.
560+
If no PowerShell file path is provided, the current PowerShell instance ($PSHOME) is used.
561+
Finally, it will test the new SSH remoting endpoint connection to ensure it works.
562+
Currently, only Ubuntu and Windows platforms are supported.
563+
.Parameter PowerShellPath
564+
Specifies a PowerShell, pwsh(.exe), executable path that will be used for the remoting endpoint.
565+
#>
566+
function Install-SSHRemoting
567+
{
568+
param (
569+
[string] $PowerShellFilePath
570+
)
571+
572+
if ($IsWindows)
573+
{
574+
if ([string]::IsNullOrEmpty($PowerShellFilePath)) { $PowerShellFilePath = "$PSHOME/pwsh.exe" }
575+
Install-SSHRemotingOnWindows -PowerShellPath $PowerShellFilePath
576+
return
577+
}
578+
elseif ($IsLinux)
579+
{
580+
$LinuxInfo = Get-Content /etc/os-release -Raw | ConvertFrom-StringData
581+
if ($LinuxInfo.ID -match 'ubuntu')
582+
{
583+
if ([string]::IsNullOrEmpty($PowerShellFilePath)) { $PowerShellFilePath = "$PSHOME/pwsh" }
584+
Install-SSHRemotingOnLinux -PowerShellPath $PowerShellFilePath
585+
return
586+
}
587+
}
588+
589+
Write-Error "Platform not supported. Only Windows and Ubuntu plaforms are currently supported."
590+
}

0 commit comments

Comments
 (0)