mirror of
https://github.com/PowerShell/Win32-OpenSSH.git
synced 2025-11-22 23:01:02 +01:00
446 lines
16 KiB
PowerShell
446 lines
16 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
||
Import-Module $PSScriptRoot\OpenSSHCommonUtils.psm1 -DisableNameChecking
|
||
|
||
[System.IO.DirectoryInfo] $repositoryRoot = Get-RepositoryRoot
|
||
# test environment parameters initialized with defaults
|
||
$E2ETestResultsFileName = "E2ETestResults.xml"
|
||
$UnitTestResultsFileName = "UnitTestResults.txt"
|
||
$TestSetupLogFileName = "TestSetupLog.txt"
|
||
$SSOUser = "sshtest_ssouser"
|
||
$PubKeyUser = "sshtest_pubkeyuser"
|
||
$PasswdUser = "sshtest_passwduser"
|
||
$OpenSSHTestAccountsPassword = "P@ssw0rd_1"
|
||
$OpenSSHTestAccounts = $Script:SSOUser, $Script:PubKeyUser, $Script:PasswdUser
|
||
|
||
$Script:TestDataPath = "$env:SystemDrive\OpenSSHTests"
|
||
$Script:E2ETestResultsFile = Join-Path $TestDataPath $E2ETestResultsFileName
|
||
$Script:UnitTestResultsFile = Join-Path $TestDataPath $UnitTestResultsFileName
|
||
$Script:TestSetupLogFile = Join-Path $TestDataPath $TestSetupLogFileName
|
||
$Script:E2ETestDirectory = Join-Path $repositoryRoot.FullName -ChildPath "regress\pesterTests"
|
||
|
||
<#
|
||
.Synopsis
|
||
Setup-OpenSSHTestEnvironment
|
||
TODO - split these steps into client and server side
|
||
#>
|
||
function Setup-OpenSSHTestEnvironment
|
||
{
|
||
[CmdletBinding()]
|
||
param
|
||
(
|
||
[switch] $Quiet,
|
||
[string] $OpenSSHBinPath,
|
||
[string] $TestDataPath = "$env:SystemDrive\OpenSSHTests",
|
||
[Boolean] $DebugMode = $false
|
||
)
|
||
|
||
if($Global:OpenSSHTestInfo -ne $null)
|
||
{
|
||
$Global:OpenSSHTestInfo.Clear()
|
||
$Global:OpenSSHTestInfo = $null
|
||
}
|
||
$Script:TestDataPath = $TestDataPath;
|
||
$Script:E2ETestResultsFile = Join-Path $TestDataPath "E2ETestResults.xml"
|
||
$Script:UnitTestResultsFile = Join-Path $TestDataPath "UnitTestResults.txt"
|
||
$Script:TestSetupLogFile = Join-Path $TestDataPath "TestSetupLog.txt"
|
||
$Script:UnitTestDirectory = Get-UnitTestDirectory
|
||
|
||
|
||
$Global:OpenSSHTestInfo = @{
|
||
"Target"= "localhost"; # test listener name
|
||
"Port"= "47002"; # test listener port
|
||
"SSOUser"= $SSOUser; # test user with single sign on capability
|
||
"PubKeyUser"= $PubKeyUser; # test user to be used with explicit key for key auth
|
||
"PasswdUser"= $PasswdUser; # common password for all test accounts
|
||
"TestAccountPW"= $OpenSSHTestAccountsPassword; # common password for all test accounts
|
||
"TestDataPath" = $TestDataPath; # openssh tests path
|
||
"TestSetupLogFile" = $Script:TestSetupLogFile; # openssh test setup log file
|
||
"E2ETestResultsFile" = $Script:E2ETestResultsFile; # openssh E2E test results file
|
||
"UnitTestResultsFile" = $Script:UnitTestResultsFile; # openssh unittest test results file
|
||
"E2ETestDirectory" = $Script:E2ETestDirectory # the directory of E2E tests
|
||
"UnitTestDirectory" = $Script:UnitTestDirectory # the directory of unit tests
|
||
"DebugMode" = $DebugMode # run openssh E2E in debug mode
|
||
}
|
||
|
||
#if user does not set path, pick it up
|
||
if([string]::IsNullOrEmpty($OpenSSHBinPath))
|
||
{
|
||
$sshcmd = get-command ssh.exe -ErrorAction Ignore
|
||
if($sshcmd -eq $null)
|
||
{
|
||
Throw "Cannot find ssh.exe. Please specify -OpenSSHBinPath to the OpenSSH installed location."
|
||
}
|
||
elseif($Quiet)
|
||
{
|
||
$dirToCheck = split-path $sshcmd.Path
|
||
$script:OpenSSHBinPath = $dirToCheck
|
||
}
|
||
else
|
||
{
|
||
$dirToCheck = split-path $sshcmd.Path
|
||
$message = "Do you want to test openssh installed at $($dirToCheck)? [Yes] Y; [No] N (default is `"Y`")"
|
||
$response = Read-Host -Prompt $message
|
||
if( ($response -eq "") -or ($response -ieq "Y") -or ($response -ieq "Yes") )
|
||
{
|
||
$script:OpenSSHBinPath = $dirToCheck
|
||
}
|
||
elseif( ($response -ieq "N") -or ($response -ieq "No") )
|
||
{
|
||
Write-Host "User decided not to pick up ssh.exe from $dirToCheck. Please specify -OpenSSHBinPath to the OpenSSH installed location."
|
||
return
|
||
}
|
||
else
|
||
{
|
||
Throw "User entered invalid option ($response). Please specify -OpenSSHBinPath to the OpenSSH installed location"
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (-not (Test-Path (Join-Path $OpenSSHBinPath ssh.exe) -PathType Leaf))
|
||
{
|
||
Throw "Cannot find OpenSSH binaries under $OpenSSHBinPath. Please specify -OpenSSHBinPathto the OpenSSH installed location"
|
||
}
|
||
else
|
||
{
|
||
$script:OpenSSHBinPath = $OpenSSHBinPath
|
||
}
|
||
}
|
||
|
||
$Global:OpenSSHTestInfo.Add("OpenSSHBinPath", $script:OpenSSHBinPath)
|
||
|
||
$warning = @"
|
||
WARNING: Following changes will be made to OpenSSH configuration
|
||
- sshd_config will be backed up as sshd_config.ori
|
||
- will be replaced with a test sshd_config
|
||
- $HOME\.ssh\known_hosts will be backed up as known_hosts.ori
|
||
- will be replaced with a test known_hosts
|
||
- sshd test listener will be on port 47002
|
||
- $HOME\.ssh\known_hosts will be modified with test host key entry
|
||
- test accounts - ssouser, pubkeyuser, and passwduser will be added
|
||
- Setup single signon for ssouser
|
||
- To cleanup - Run Cleanup-OpenSSHTestEnvironment
|
||
"@
|
||
|
||
if (-not $Quiet) {
|
||
Write-Warning $warning
|
||
$continue = Read-Host -Prompt "Do you want to continue with the above changes? [Yes] Y; [No] N (default is `"Y`")"
|
||
if( ($continue -eq "") -or ($continue -ieq "Y") -or ($continue -ieq "Yes") )
|
||
{
|
||
}
|
||
elseif( ($continue -ieq "N") -or ($continue -ieq "No") )
|
||
{
|
||
Write-Host "User decided not to make the changes."
|
||
return
|
||
}
|
||
else
|
||
{
|
||
Throw "User entered invalid option ($continue). Exit now."
|
||
}
|
||
}
|
||
|
||
Install-OpenSSHTestDependencies
|
||
|
||
if(-not (Test-path $TestDataPath -PathType Container))
|
||
{
|
||
New-Item -ItemType Directory -Path $TestDataPath -Force -ErrorAction SilentlyContinue | out-null
|
||
}
|
||
|
||
#Backup existing OpenSSH configuration
|
||
$backupConfigPath = Join-Path $script:OpenSSHBinPath sshd_config.ori
|
||
if (-not (Test-Path $backupConfigPath -PathType Leaf)) {
|
||
Copy-Item (Join-Path $script:OpenSSHBinPath sshd_config) $backupConfigPath -Force
|
||
}
|
||
|
||
# copy new sshd_config
|
||
Copy-Item (Join-Path $Script:E2ETestDirectory sshd_config) (Join-Path $script:OpenSSHBinPath sshd_config) -Force
|
||
Copy-Item "$($Script:E2ETestDirectory)\sshtest*hostkey*" $script:OpenSSHBinPath -Force
|
||
Restart-Service sshd -Force
|
||
|
||
#Backup existing known_hosts and replace with test version
|
||
#TODO - account for custom known_hosts locations
|
||
$knowHostsDirectoryPath = Join-Path $home .ssh
|
||
$knowHostsFilePath = Join-Path $knowHostsDirectoryPath known_hosts
|
||
if(-not (Test-Path $knowHostsDirectoryPath -PathType Container))
|
||
{
|
||
New-Item -ItemType Directory -Path $knowHostsDirectoryPath -Force -ErrorAction SilentlyContinue | out-null
|
||
}
|
||
if ((Test-Path $knowHostsFilePath -PathType Leaf) -and (-not (Test-Path (Join-Path $knowHostsDirectoryPath known_hosts.ori) -PathType Leaf))) {
|
||
Copy-Item $knowHostsFilePath (Join-Path $knowHostsDirectoryPath known_hosts.ori) -Force
|
||
}
|
||
Copy-Item (Join-Path $Script:E2ETestDirectory known_hosts) $knowHostsFilePath -Force
|
||
|
||
# create test accounts
|
||
#TODO - this is Windows specific. Need to be in PAL
|
||
foreach ($user in $OpenSSHTestAccounts)
|
||
{
|
||
try
|
||
{
|
||
$objUser = New-Object System.Security.Principal.NTAccount($user)
|
||
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
|
||
}
|
||
catch
|
||
{
|
||
#only add the local user when it does not exists on the machine
|
||
net user $user $Script:OpenSSHTestAccountsPassword /ADD 2>&1 >> $Script:TestSetupLogFile
|
||
}
|
||
}
|
||
|
||
#setup single sign on for ssouser
|
||
#TODO - this is Windows specific. Need to be in PAL
|
||
$ssousersid = Get-UserSID -User sshtest_ssouser
|
||
$ssouserProfileRegistry = Join-Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $ssousersid
|
||
if (-not (Test-Path $ssouserProfileRegistry) ) {
|
||
#create profile
|
||
if (-not($env:DISPLAY)) { $env:DISPLAY = 1 }
|
||
$env:SSH_ASKPASS="$($env:ComSpec) /c echo $($OpenSSHTestAccountsPassword)"
|
||
cmd /c "ssh -p 47002 sshtest_ssouser@localhost echo %userprofile% > profile.txt"
|
||
if ($env:DISPLAY -eq 1) { Remove-Item env:\DISPLAY }
|
||
remove-item "env:SSH_ASKPASS" -ErrorAction SilentlyContinue
|
||
}
|
||
$ssouserProfile = (Get-ItemProperty -Path $ssouserProfileRegistry -Name 'ProfileImagePath').ProfileImagePath
|
||
New-Item -ItemType Directory -Path (Join-Path $ssouserProfile .ssh) -Force -ErrorAction SilentlyContinue | out-null
|
||
$authorizedKeyPath = Join-Path $ssouserProfile .ssh\authorized_keys
|
||
$testPubKeyPath = Join-Path $Script:E2ETestDirectory sshtest_userssokey_ed25519.pub
|
||
#workaround for the cariggage new line added by git
|
||
(Get-Content $testPubKeyPath -Raw).Replace("`r`n","`n") | Set-Content $testPubKeyPath -Force
|
||
Copy-Item $testPubKeyPath $authorizedKeyPath -Force -ErrorAction SilentlyContinue
|
||
$acl = get-acl $authorizedKeyPath
|
||
$ar = New-Object System.Security.AccessControl.FileSystemAccessRule("NT Service\sshd", "Read", "Allow")
|
||
$acl.SetAccessRule($ar)
|
||
Set-Acl $authorizedKeyPath $acl
|
||
$testPriKeypath = Join-Path $Script:E2ETestDirectory sshtest_userssokey_ed25519
|
||
(Get-Content $testPriKeypath -Raw).Replace("`r`n","`n") | Set-Content $testPriKeypath -Force
|
||
cmd /c "ssh-add $testPriKeypath 2>&1 >> $Script:TestSetupLogFile"
|
||
}
|
||
|
||
<#
|
||
.SYNOPSIS
|
||
This function installs the tools required by our tests
|
||
1) Pester for running the tests
|
||
2) sysinternals required by the tests on windows.
|
||
#>
|
||
function Install-OpenSSHTestDependencies
|
||
{
|
||
[CmdletBinding()]
|
||
param ()
|
||
|
||
# Install chocolatey
|
||
if(-not (Get-Command "choco" -ErrorAction SilentlyContinue))
|
||
{
|
||
Write-Log -Message "Chocolatey not present. Installing chocolatey."
|
||
Invoke-Expression ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1')) 2>&1 >> $Script:TestSetupLogFile
|
||
}
|
||
|
||
$isModuleAvailable = Get-Module 'Pester' -ListAvailable
|
||
if (-not ($isModuleAvailable))
|
||
{
|
||
Write-Log -Message "Installing Pester..."
|
||
choco install Pester -y --force --limitoutput 2>&1 >> $Script:TestSetupLogFile
|
||
}
|
||
|
||
if ( -not (Test-Path "$env:ProgramData\chocolatey\lib\sysinternals\tools" ) ) {
|
||
Write-Log -Message "sysinternals not present. Installing sysinternals."
|
||
choco install sysinternals -y --force --limitoutput 2>&1 >> $Script:TestSetupLogFile
|
||
}
|
||
}
|
||
<#
|
||
.Synopsis
|
||
Get-UserSID
|
||
#>
|
||
function Get-UserSID
|
||
{
|
||
param
|
||
(
|
||
[string]$Domain,
|
||
[string]$User
|
||
)
|
||
if([string]::IsNullOrEmpty($Domain))
|
||
{
|
||
$objUser = New-Object System.Security.Principal.NTAccount($User)
|
||
}
|
||
else
|
||
{
|
||
$objUser = New-Object System.Security.Principal.NTAccount($Domain, $User)
|
||
}
|
||
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
|
||
$strSID.Value
|
||
}
|
||
|
||
<#
|
||
.Synopsis
|
||
Cleanup-OpenSSHTestEnvironment
|
||
#>
|
||
function Cleanup-OpenSSHTestEnvironment
|
||
{
|
||
# .exe - Windows specific. TODO - PAL
|
||
if (-not (Test-Path (Join-Path $script:OpenSSHBinPath ssh.exe) -PathType Leaf))
|
||
{
|
||
Throw "Cannot find OpenSSH binaries under $script:OpenSSHBinPath. "
|
||
}
|
||
|
||
#Restore sshd_config
|
||
$backupConfigPath = Join-Path $Script:OpenSSHBinPath sshd_config.ori
|
||
if (Test-Path $backupConfigPath -PathType Leaf) {
|
||
Copy-Item $backupConfigPath (Join-Path $Script:OpenSSHBinPath sshd_config) -Force -ErrorAction SilentlyContinue
|
||
Remove-Item (Join-Path $Script:OpenSSHBinPath sshd_config.ori) -Force -ErrorAction SilentlyContinue
|
||
Remove-Item $Script:OpenSSHBinPath\sshtest*hostkey* -Force -ErrorAction SilentlyContinue
|
||
Restart-Service sshd
|
||
}
|
||
|
||
#Restore known_hosts
|
||
$originKnowHostsPath = Join-Path $home .ssh\known_hosts.ori
|
||
if (Test-Path $originKnowHostsPath)
|
||
{
|
||
Copy-Item $originKnowHostsPath (Join-Path $home .ssh\known_hosts) -Force -ErrorAction SilentlyContinue
|
||
Remove-Item $originKnowHostsPath -Force -ErrorAction SilentlyContinue
|
||
}
|
||
|
||
#Delete accounts
|
||
foreach ($user in $OpenSSHTestAccounts)
|
||
{
|
||
net user $user /delete
|
||
}
|
||
|
||
# remove registered keys
|
||
cmd /c "ssh-add -d (Join-Path $Script:E2ETestDirectory sshtest_userssokey_ed25519) 2>&1 >> $Script:TestSetupLogFile"
|
||
|
||
if($Global:OpenSSHTestInfo -ne $null)
|
||
{
|
||
$Global:OpenSSHTestInfo.Clear()
|
||
$Global:OpenSSHTestInfo = $null
|
||
}
|
||
}
|
||
|
||
<#
|
||
.Synopsis
|
||
Get-UnitTestDirectory.
|
||
#>
|
||
function Get-UnitTestDirectory
|
||
{
|
||
[CmdletBinding()]
|
||
param
|
||
(
|
||
[ValidateSet('Debug', 'Release')]
|
||
[string]$Configuration = "Release",
|
||
|
||
[ValidateSet('x86', 'x64', '')]
|
||
[string]$NativeHostArch = ""
|
||
)
|
||
|
||
[string] $platform = $env:PROCESSOR_ARCHITECTURE
|
||
if(-not [String]::IsNullOrEmpty($NativeHostArch))
|
||
{
|
||
$folderName = $NativeHostArch
|
||
if($NativeHostArch -eq 'x86')
|
||
{
|
||
$folderName = "Win32"
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if($platform -ieq "AMD64")
|
||
{
|
||
$folderName = "x64"
|
||
}
|
||
else
|
||
{
|
||
$folderName = "Win32"
|
||
}
|
||
}
|
||
|
||
if([String]::IsNullOrEmpty($Configuration))
|
||
{
|
||
if( $folderName -ieq "Win32" )
|
||
{
|
||
$RealConfiguration = "Debug"
|
||
}
|
||
else
|
||
{
|
||
$RealConfiguration = "Release"
|
||
}
|
||
}
|
||
else
|
||
{
|
||
$RealConfiguration = $Configuration
|
||
}
|
||
$unitTestdir = Join-Path $repositoryRoot.FullName -ChildPath "bin\$folderName\$RealConfiguration"
|
||
$unitTestDir
|
||
}
|
||
|
||
<#
|
||
.Synopsis
|
||
Run OpenSSH pester tests.
|
||
#>
|
||
function Run-OpenSSHE2ETest
|
||
{
|
||
# Discover all CI tests and run them.
|
||
Push-Location $Script:E2ETestDirectory
|
||
Write-Log -Message "Running OpenSSH E2E tests..."
|
||
$testFolders = Get-ChildItem *.tests.ps1 -Recurse -Exclude SSHDConfig.tests.ps1, SSH.Tests.ps1 | ForEach-Object{ Split-Path $_.FullName} | Sort-Object -Unique
|
||
Invoke-Pester $testFolders -OutputFormat NUnitXml -OutputFile $Script:E2ETestResultsFile -Tag 'CI'
|
||
Pop-Location
|
||
}
|
||
|
||
<#
|
||
.Synopsis
|
||
Run openssh unit tests.
|
||
#>
|
||
function Run-OpenSSHUnitTest
|
||
{
|
||
# Discover all CI tests and run them.
|
||
Push-Location $Script:UnitTestDirectory
|
||
Write-Log -Message "Running OpenSSH unit tests..."
|
||
if (Test-Path $Script:UnitTestResultsFile)
|
||
{
|
||
$null = Remove-Item -Path $Script:UnitTestResultsFile -Force -ErrorAction SilentlyContinue
|
||
}
|
||
$testFolders = Get-ChildItem unittest-*.exe -Recurse -Exclude unittest-sshkey.exe,unittest-kex.exe |
|
||
ForEach-Object{ Split-Path $_.FullName} |
|
||
Sort-Object -Unique
|
||
$testfailed = $false
|
||
if ($testFolders -ne $null)
|
||
{
|
||
$testFolders | % {
|
||
Push-Location $_
|
||
$unittestFile = "$(Split-Path $_ -Leaf).exe"
|
||
Write-log "Running OpenSSH unit $unittestFile ..."
|
||
& .\$unittestFile >> $Script:UnitTestResultsFile
|
||
|
||
$errorCode = $LASTEXITCODE
|
||
if ($errorCode -ne 0)
|
||
{
|
||
$testfailed = $true
|
||
$errorMessage = "$($_.FullName) test failed for OpenSSH.`nExitCode: $errorCode. Detail test log is at $($Script:UnitTestResultsFile)."
|
||
Write-Warning $errorMessage
|
||
}
|
||
Pop-Location
|
||
}
|
||
}
|
||
Pop-Location
|
||
$testfailed
|
||
}
|
||
|
||
<#
|
||
Write-Log
|
||
#>
|
||
function Write-Log
|
||
{
|
||
param
|
||
(
|
||
[Parameter(Mandatory=$true)]
|
||
[ValidateNotNullOrEmpty()]
|
||
[string] $Message
|
||
)
|
||
if(-not (Test-Path (Split-Path $Script:TestSetupLogFile) -PathType Container))
|
||
{
|
||
$null = New-Item -ItemType Directory -Path (Split-Path $Script:TestSetupLogFile) -Force -ErrorAction SilentlyContinue | out-null
|
||
}
|
||
if (-not ([string]::IsNullOrEmpty($Script:TestSetupLogFile)))
|
||
{
|
||
Add-Content -Path $Script:TestSetupLogFile -Value $Message
|
||
}
|
||
}
|
||
|
||
Export-ModuleMember -Function Setup-OpenSSHTestEnvironment, Cleanup-OpenSSHTestEnvironment, Run-OpenSSHUnitTest, Run-OpenSSHE2ETest
|