mirror of
https://github.com/Grace-Solutions/Invoke-OpenSSHConfiguration.git
synced 2026-07-26 11:38:14 +00:00
Enhanced OpenSSH configuration with certificate auth, service management, and security features
Features added: - Certificate authentication with Windows CA trust bridge - CA filtering via regex inclusion/exclusion expressions - Password authentication disable option - Service auto-start and start management - Config backup before modification (timestamped) - Config validation (sshd -t) with automatic rollback on failure - SFTP subsystem configured by default - Public key management with deduplication - Key pair generation (ed25519, rsa, ecdsa) Toolkit enhancements: - ConvertFrom-CertificateToPEM: Added -IncludePrivateKey, -ThumbprintList, -CreateCABundle - Updated coding standards in PowershellScripts.md Documentation: - Comprehensive README with examples and troubleshooting - Added .augment folder to .gitignore
This commit is contained in:
+547
-16
@@ -43,22 +43,64 @@
|
||||
|
||||
[CmdletBinding(SupportsShouldProcess=$True)]
|
||||
Param
|
||||
(
|
||||
(
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[Alias('TSVars', 'TSVs')]
|
||||
[String[]]$TaskSequenceVariables,
|
||||
[Alias('M')]
|
||||
[ValidateSet('Client', 'Server')]
|
||||
[String]$Mode,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[Alias('TSVD', 'TSVDL')]
|
||||
[String[]]$TSVariableDecodeList,
|
||||
|
||||
[Alias('PKL')]
|
||||
[String[]]$PublicKeyList,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Switch]$GenerateKeyPair,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateSet('rsa', 'ecdsa', 'ed25519')]
|
||||
[String]$KeyType = 'ed25519',
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateSet(256, 384, 521, 2048, 3072, 4096)]
|
||||
[Int]$KeyBits,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Switch]$EnablePubkeyAuthentication,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Switch]$DisablePasswordAuthentication,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Switch]$EnableCertificateAuthentication,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Switch]$TrustWindowsCertificateAuthorities,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String]$CAInclusionExpression,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String]$CAExclusionExpression,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[String]$SFTPSubsystemPath,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Switch]$EnableService,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Switch]$StartService,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[Alias('LogDir', 'LogPath')]
|
||||
[System.IO.DirectoryInfo]$LogDirectory,
|
||||
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Switch]$ContinueOnError
|
||||
)
|
||||
@@ -110,9 +152,9 @@ Switch (Test-ProcessElevationStatus)
|
||||
#region Set default parameter values
|
||||
Switch ($True)
|
||||
{
|
||||
{([System.String]::IsNullOrEmpty($ExampleVariable) -eq $True) -or ([System.String]::IsNullOrWhiteSpace($ExampleVariable) -eq $True)}
|
||||
{([System.String]::IsNullOrEmpty($Mode) -eq $True) -or ([System.String]::IsNullOrWhiteSpace($Mode) -eq $True)}
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -230,13 +272,21 @@ Switch (Test-ProcessElevationStatus)
|
||||
|
||||
$StartProcessWithOutputParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
||||
$StartProcessWithOutputParameters.FilePath = 'msiexec.exe'
|
||||
#$StartProcessWithOutputParameters.WorkingDirectory = "$($Env:SystemDrive)\Users\Public\Documents"
|
||||
$StartProcessWithOutputParameters.ArgumentList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
||||
$StartProcessWithOutputParameters.ArgumentList.Add('/i')
|
||||
$StartProcessWithOutputParameters.ArgumentList.Add("`"$($GitRepositoryReleaseAssetListItem.DestinationPath.FullName)`"")
|
||||
|
||||
Switch ($True)
|
||||
{
|
||||
{([System.String]::IsNullOrEmpty($Mode) -eq $False) -and ([System.String]::IsNullOrWhiteSpace($Mode) -eq $False)}
|
||||
{
|
||||
$StartProcessWithOutputParameters.ArgumentList.Add("ADDLOCAL=$($Mode)")
|
||||
}
|
||||
}
|
||||
|
||||
$StartProcessWithOutputParameters.ArgumentList.Add('/qn')
|
||||
$StartProcessWithOutputParameters.ArgumentList.Add('/norestart')
|
||||
$StartProcessWithOutputParameters.ArgumentList.Add("/l*v `"$($ExecutionLogPath.FullName)`"")
|
||||
$StartProcessWithOutputParameters.ArgumentList.Add("/l*v `"$($ExecutionLogPath.FullName)`"")
|
||||
$StartProcessWithOutputParameters.AcceptableExitCodeList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
||||
$StartProcessWithOutputParameters.AcceptableExitCodeList.Add('0')
|
||||
$StartProcessWithOutputParameters.AcceptableExitCodeList.Add('3010')
|
||||
@@ -251,7 +301,7 @@ Switch (Test-ProcessElevationStatus)
|
||||
|
||||
$MSIProductInfo = Get-MSIPropertyList -Path ($GitRepositoryReleaseAssetListItem.DestinationPath.FullName)
|
||||
|
||||
$WriteLogMessage.Invoke(0, @("Attempting to release binary $($GitRepositoryReleaseAssetListCounter) of $($GitRepositoryReleaseAssetListCount). Please Wait...", "Manufacturer: $($MSIProductInfo.Manufacturer)", "ProductName: $($MSIProductInfo.ProductName)", "ProductVersion: $($MSIProductInfo.ProductVersion)", "Product Language: $($MSIProductInfo.ProductLanguage)"))
|
||||
$WriteLogMessage.Invoke(0, @("Attempting to install release binary $($GitRepositoryReleaseAssetListCounter) of $($GitRepositoryReleaseAssetListCount). Please Wait...", "Manufacturer: $($MSIProductInfo.Manufacturer)", "ProductName: $($MSIProductInfo.ProductName)", "ProductVersion: $($MSIProductInfo.ProductVersion)", "Product Language: $($MSIProductInfo.ProductLanguage)"))
|
||||
|
||||
$StartProcessWithOutputResult = Start-ProcessWithOutput @StartProcessWithOutputParameters
|
||||
|
||||
@@ -268,11 +318,492 @@ Switch (Test-ProcessElevationStatus)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
###Do configuration stuff here
|
||||
#PublicKeys - New and from list idepotent
|
||||
#Certs - From windows store to PEM
|
||||
#Options etc
|
||||
#region Define OpenSSH Paths and Settings
|
||||
[System.IO.DirectoryInfo]$OpenSSHProgramDataDirectory = [System.IO.Path]::Combine($Env:ProgramData, 'ssh')
|
||||
[System.IO.FileInfo]$SSHDConfigPath = [System.IO.Path]::Combine($OpenSSHProgramDataDirectory.FullName, 'sshd_config')
|
||||
[System.IO.FileInfo]$AdministratorsAuthorizedKeysPath = [System.IO.Path]::Combine($OpenSSHProgramDataDirectory.FullName, 'administrators_authorized_keys')
|
||||
[System.Text.Encoding]$OpenSSHFileEncoding = [System.Text.Encoding]::ASCII
|
||||
#endregion
|
||||
|
||||
#region Generate Key Pair
|
||||
Switch ($GenerateKeyPair.IsPresent)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Attempting to generate SSH key pair. Please Wait..."))
|
||||
|
||||
[System.IO.DirectoryInfo]$SSHKeyDirectory = [System.IO.Path]::Combine($OpenSSHProgramDataDirectory.FullName, 'keys')
|
||||
|
||||
Switch ($SSHKeyDirectory.Exists)
|
||||
{
|
||||
{($_ -eq $False)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Attempting to create directory `"$($SSHKeyDirectory.FullName)`". Please Wait..."))
|
||||
|
||||
$Null = [System.IO.Directory]::CreateDirectory($SSHKeyDirectory.FullName)
|
||||
}
|
||||
}
|
||||
|
||||
$KeyFileName = "ssh_host_$($KeyType)_key"
|
||||
|
||||
[System.IO.FileInfo]$PrivateKeyPath = [System.IO.Path]::Combine($SSHKeyDirectory.FullName, $KeyFileName)
|
||||
|
||||
[System.IO.FileInfo]$PublicKeyPath = [System.IO.Path]::Combine($SSHKeyDirectory.FullName, "$($KeyFileName).pub")
|
||||
|
||||
Switch ($PrivateKeyPath.Exists)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("SSH key pair already exists at `"$($PrivateKeyPath.FullName)`". Skipping generation."))
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$SSHKeygenArgumentList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
||||
$SSHKeygenArgumentList.Add('-t')
|
||||
$SSHKeygenArgumentList.Add($KeyType)
|
||||
|
||||
Switch (($Null -ine $KeyBits) -and ($KeyBits -gt 0))
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$SSHKeygenArgumentList.Add('-b')
|
||||
$SSHKeygenArgumentList.Add($KeyBits.ToString())
|
||||
}
|
||||
}
|
||||
|
||||
$SSHKeygenArgumentList.Add('-f')
|
||||
$SSHKeygenArgumentList.Add("`"$($PrivateKeyPath.FullName)`"")
|
||||
$SSHKeygenArgumentList.Add('-N')
|
||||
$SSHKeygenArgumentList.Add('""')
|
||||
$SSHKeygenArgumentList.Add('-C')
|
||||
$SSHKeygenArgumentList.Add("`"$($Env:COMPUTERNAME)`"")
|
||||
|
||||
$StartProcessWithOutputParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
||||
$StartProcessWithOutputParameters.FilePath = 'ssh-keygen.exe'
|
||||
$StartProcessWithOutputParameters.ArgumentList = $SSHKeygenArgumentList
|
||||
$StartProcessWithOutputParameters.AcceptableExitCodeList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
||||
$StartProcessWithOutputParameters.AcceptableExitCodeList.Add('0')
|
||||
$StartProcessWithOutputParameters.CreateNoWindow = $True
|
||||
$StartProcessWithOutputParameters.LogOutput = $True
|
||||
$StartProcessWithOutputParameters.ContinueOnError = $False
|
||||
$StartProcessWithOutputParameters.Verbose = $True
|
||||
|
||||
$WriteLogMessage.Invoke(0, @("Generating $($KeyType) key pair at `"$($PrivateKeyPath.FullName)`". Please Wait..."))
|
||||
|
||||
$StartProcessWithOutputResult = Start-ProcessWithOutput @StartProcessWithOutputParameters
|
||||
|
||||
$WriteLogMessage.Invoke(0, @("SSH key pair generated successfully."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Add Public Keys
|
||||
Switch (($Null -ine $PublicKeyList) -and ($PublicKeyList.Count -gt 0))
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Attempting to add $($PublicKeyList.Count) public key(s) to authorized_keys. Please Wait..."))
|
||||
|
||||
$ExistingAuthorizedKeys = New-Object -TypeName 'System.Collections.Generic.HashSet[System.String]'
|
||||
|
||||
Switch ($AdministratorsAuthorizedKeysPath.Exists)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$ExistingContent = [System.IO.File]::ReadAllLines($AdministratorsAuthorizedKeysPath.FullName)
|
||||
|
||||
ForEach ($Line In $ExistingContent)
|
||||
{
|
||||
Switch (([System.String]::IsNullOrEmpty($Line) -eq $False) -and ([System.String]::IsNullOrWhiteSpace($Line) -eq $False))
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$Null = $ExistingAuthorizedKeys.Add($Line.Trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$KeysToAdd = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
||||
|
||||
$PublicKeyListCounter = 1
|
||||
|
||||
ForEach ($PublicKey In $PublicKeyList)
|
||||
{
|
||||
$PublicKeyTrimmed = $PublicKey.Trim()
|
||||
|
||||
Switch ($ExistingAuthorizedKeys.Contains($PublicKeyTrimmed))
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Public key $($PublicKeyListCounter) of $($PublicKeyList.Count) already exists. Skipping."))
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Adding public key $($PublicKeyListCounter) of $($PublicKeyList.Count)."))
|
||||
$KeysToAdd.Add($PublicKeyTrimmed)
|
||||
}
|
||||
}
|
||||
|
||||
$PublicKeyListCounter++
|
||||
}
|
||||
|
||||
Switch ($KeysToAdd.Count -gt 0)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Appending $($KeysToAdd.Count) new public key(s) to `"$($AdministratorsAuthorizedKeysPath.FullName)`". Please Wait..."))
|
||||
|
||||
$Null = [System.IO.File]::AppendAllLines($AdministratorsAuthorizedKeysPath.FullName, $KeysToAdd, $OpenSSHFileEncoding)
|
||||
|
||||
$WriteLogMessage.Invoke(0, @("Public keys added successfully."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Export Windows CA Certificates to PEM
|
||||
Switch ($TrustWindowsCertificateAuthorities.IsPresent)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Attempting to export Windows CA certificates to PEM format. Please Wait..."))
|
||||
|
||||
$CertificateExpirationThreshold = Get-Date
|
||||
|
||||
$CertificateStorePathList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
||||
$CertificateStorePathList.Add('Cert:\LocalMachine\Root')
|
||||
$CertificateStorePathList.Add('Cert:\LocalMachine\CA')
|
||||
|
||||
$CACertificates = Get-ChildItem -Path $CertificateStorePathList | Where-Object {($_.NotAfter -gt $CertificateExpirationThreshold) -and ($_.Extensions | Where-Object {($_.Oid.FriendlyName -iin @('Basic Constraints')) -and ($_.CertificateAuthority -eq $True)})}
|
||||
|
||||
#region Apply CA Inclusion Filter
|
||||
Switch (([System.String]::IsNullOrEmpty($CAInclusionExpression) -eq $False) -and ([System.String]::IsNullOrWhiteSpace($CAInclusionExpression) -eq $False))
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Applying CA inclusion filter: `"$($CAInclusionExpression)`""))
|
||||
$CACertificates = $CACertificates | Where-Object {($_.Subject -imatch $CAInclusionExpression) -or ($_.Thumbprint -imatch $CAInclusionExpression)}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Apply CA Exclusion Filter
|
||||
Switch (([System.String]::IsNullOrEmpty($CAExclusionExpression) -eq $False) -and ([System.String]::IsNullOrWhiteSpace($CAExclusionExpression) -eq $False))
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Applying CA exclusion filter: `"$($CAExclusionExpression)`""))
|
||||
$CACertificates = $CACertificates | Where-Object {($_.Subject -inotmatch $CAExclusionExpression) -and ($_.Thumbprint -inotmatch $CAExclusionExpression)}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
$CACertificatesCount = ($CACertificates | Measure-Object).Count
|
||||
|
||||
$WriteLogMessage.Invoke(0, @("Found $($CACertificatesCount) CA certificate(s) matching criteria."))
|
||||
|
||||
Switch ($CACertificatesCount -gt 0)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$ConvertFromCertificateToPEMParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
||||
$ConvertFromCertificateToPEMParameters.CertificateList = $CACertificates
|
||||
$ConvertFromCertificateToPEMParameters.Export = $True
|
||||
$ConvertFromCertificateToPEMParameters.ExportRootDirectory = $OpenSSHProgramDataDirectory.FullName
|
||||
$ConvertFromCertificateToPEMParameters.CreateCABundle = $True
|
||||
$ConvertFromCertificateToPEMParameters.CABundleFileName = 'OpenSSH-TrustedCertificateAuthorities-LocalMachine.pem'
|
||||
$ConvertFromCertificateToPEMParameters.ContinueOnError = $False
|
||||
$ConvertFromCertificateToPEMParameters.Verbose = $True
|
||||
|
||||
$ConvertFromCertificateToPEMResult = ConvertFrom-CertificateToPEM @ConvertFromCertificateToPEMParameters
|
||||
|
||||
$CABundleResult = $ConvertFromCertificateToPEMResult | Where-Object {($_.Type -ieq 'CABundle')}
|
||||
|
||||
Switch ($Null -ine $CABundleResult)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("CA bundle exported successfully to `"$($CABundleResult.Path)`" with $($CABundleResult.CertificateCount) certificate(s)."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Configure sshd_config
|
||||
Switch ($SSHDConfigPath.Exists)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Attempting to configure sshd_config at `"$($SSHDConfigPath.FullName)`". Please Wait..."))
|
||||
|
||||
#region Backup Existing Configuration
|
||||
$SSHDConfigBackupPath = [System.IO.FileInfo][System.IO.Path]::Combine($OpenSSHProgramDataDirectory.FullName, "sshd_config.backup.$($GetCurrentDateTimeFileFormat.Invoke())")
|
||||
|
||||
$WriteLogMessage.Invoke(0, @("Creating backup of sshd_config to `"$($SSHDConfigBackupPath.FullName)`". Please Wait..."))
|
||||
|
||||
$Null = [System.IO.File]::Copy($SSHDConfigPath.FullName, $SSHDConfigBackupPath.FullName, $True)
|
||||
|
||||
$WriteLogMessage.Invoke(0, @("Backup created successfully."))
|
||||
#endregion
|
||||
|
||||
$SSHDConfigContent = [System.IO.File]::ReadAllText($SSHDConfigPath.FullName)
|
||||
$SSHDConfigModified = $False
|
||||
|
||||
#region Enable PubkeyAuthentication
|
||||
Switch ($EnablePubkeyAuthentication.IsPresent -eq $True)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$PubkeyAuthPattern = '(?m)^#?\s*PubkeyAuthentication\s+.*$'
|
||||
$PubkeyAuthReplacement = 'PubkeyAuthentication yes'
|
||||
|
||||
Switch ($SSHDConfigContent -imatch $PubkeyAuthPattern)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$SSHDConfigContent = [System.Text.RegularExpressions.Regex]::Replace($SSHDConfigContent, $PubkeyAuthPattern, $PubkeyAuthReplacement)
|
||||
$SSHDConfigModified = $True
|
||||
$WriteLogMessage.Invoke(0, @("Enabled PubkeyAuthentication."))
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$SSHDConfigContent = $SSHDConfigContent + "`r`n$($PubkeyAuthReplacement)"
|
||||
$SSHDConfigModified = $True
|
||||
$WriteLogMessage.Invoke(0, @("Added PubkeyAuthentication setting."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Disable PasswordAuthentication
|
||||
Switch ($DisablePasswordAuthentication.IsPresent -eq $True)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$PasswordAuthPattern = '(?m)^#?\s*PasswordAuthentication\s+.*$'
|
||||
$PasswordAuthReplacement = 'PasswordAuthentication no'
|
||||
|
||||
Switch ($SSHDConfigContent -imatch $PasswordAuthPattern)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$SSHDConfigContent = [System.Text.RegularExpressions.Regex]::Replace($SSHDConfigContent, $PasswordAuthPattern, $PasswordAuthReplacement)
|
||||
$SSHDConfigModified = $True
|
||||
$WriteLogMessage.Invoke(0, @("Disabled PasswordAuthentication."))
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$SSHDConfigContent = $SSHDConfigContent + "`r`n$($PasswordAuthReplacement)"
|
||||
$SSHDConfigModified = $True
|
||||
$WriteLogMessage.Invoke(0, @("Added PasswordAuthentication setting (disabled)."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Configure SFTP Subsystem (Default)
|
||||
$SFTPPath = $SFTPSubsystemPath
|
||||
|
||||
Switch (([System.String]::IsNullOrEmpty($SFTPPath) -eq $True) -or ([System.String]::IsNullOrWhiteSpace($SFTPPath) -eq $True))
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$SFTPPath = 'sftp-server.exe'
|
||||
}
|
||||
}
|
||||
|
||||
$SubsystemPattern = '(?m)^#?\s*Subsystem\s+sftp\s+.*$'
|
||||
$SubsystemReplacement = "Subsystem sftp $($SFTPPath)"
|
||||
|
||||
Switch ($SSHDConfigContent -imatch $SubsystemPattern)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$CurrentSubsystemMatch = [System.Text.RegularExpressions.Regex]::Match($SSHDConfigContent, $SubsystemPattern)
|
||||
|
||||
Switch ($CurrentSubsystemMatch.Value -ine $SubsystemReplacement)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$SSHDConfigContent = [System.Text.RegularExpressions.Regex]::Replace($SSHDConfigContent, $SubsystemPattern, $SubsystemReplacement)
|
||||
$SSHDConfigModified = $True
|
||||
$WriteLogMessage.Invoke(0, @("Configured SFTP subsystem: $($SFTPPath)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$SSHDConfigContent = $SSHDConfigContent + "`r`n$($SubsystemReplacement)"
|
||||
$SSHDConfigModified = $True
|
||||
$WriteLogMessage.Invoke(0, @("Added SFTP subsystem configuration."))
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Configure TrustedUserCAKeys for Certificate Authentication
|
||||
Switch (($EnableCertificateAuthentication.IsPresent -eq $True) -and ($TrustWindowsCertificateAuthorities.IsPresent -eq $True))
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
[System.IO.FileInfo]$CABundlePath = [System.IO.Path]::Combine($OpenSSHProgramDataDirectory.FullName, 'OpenSSH-TrustedCertificateAuthorities-LocalMachine.pem')
|
||||
|
||||
Switch ($CABundlePath.Exists)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$TrustedCAKeysPattern = '(?m)^#?\s*TrustedUserCAKeys\s+.*$'
|
||||
$TrustedCAKeysReplacement = "TrustedUserCAKeys $($CABundlePath.FullName)"
|
||||
|
||||
Switch ($SSHDConfigContent -imatch $TrustedCAKeysPattern)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$SSHDConfigContent = [System.Text.RegularExpressions.Regex]::Replace($SSHDConfigContent, $TrustedCAKeysPattern, $TrustedCAKeysReplacement)
|
||||
$SSHDConfigModified = $True
|
||||
$WriteLogMessage.Invoke(0, @("Configured TrustedUserCAKeys to `"$($CABundlePath.FullName)`"."))
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$SSHDConfigContent = $SSHDConfigContent + "`r`n$($TrustedCAKeysReplacement)"
|
||||
$SSHDConfigModified = $True
|
||||
$WriteLogMessage.Invoke(0, @("Added TrustedUserCAKeys setting."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Write sshd_config if Modified
|
||||
Switch ($SSHDConfigModified -eq $True)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Writing updated sshd_config. Please Wait..."))
|
||||
[System.IO.File]::WriteAllText($SSHDConfigPath.FullName, $SSHDConfigContent, $OpenSSHFileEncoding)
|
||||
$WriteLogMessage.Invoke(0, @("sshd_config updated successfully."))
|
||||
|
||||
#region Validate Configuration
|
||||
$WriteLogMessage.Invoke(0, @("Validating sshd_config with sshd -t. Please Wait..."))
|
||||
|
||||
$SSHDExePath = [System.IO.FileInfo][System.IO.Path]::Combine($Env:ProgramFiles, 'OpenSSH', 'sshd.exe')
|
||||
|
||||
Switch ($SSHDExePath.Exists -eq $True)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$ValidateConfigParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary'
|
||||
$ValidateConfigParameters.FilePath = $SSHDExePath.FullName
|
||||
$ValidateConfigParameters.ArgumentList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
||||
$ValidateConfigParameters.ArgumentList.Add('-t')
|
||||
$ValidateConfigParameters.AcceptableExitCodeList = New-Object -TypeName 'System.Collections.Generic.List[System.String]'
|
||||
$ValidateConfigParameters.AcceptableExitCodeList.Add('0')
|
||||
$ValidateConfigParameters.CreateNoWindow = $True
|
||||
$ValidateConfigParameters.LogOutput = $True
|
||||
$ValidateConfigParameters.ContinueOnError = $True
|
||||
$ValidateConfigParameters.Verbose = $True
|
||||
|
||||
$ValidateConfigResult = Start-ProcessWithOutput @ValidateConfigParameters
|
||||
|
||||
Switch ($ValidateConfigResult.ExitCode -eq 0)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("sshd_config validation passed."))
|
||||
|
||||
$WriteLogMessage.Invoke(0, @("Attempting to restart sshd service to apply configuration changes. Please Wait..."))
|
||||
$Null = Restart-Service -Name 'sshd' -Force -ErrorAction SilentlyContinue
|
||||
$WriteLogMessage.Invoke(0, @("sshd service restarted."))
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$WriteLogMessage.Invoke(2, @("sshd_config validation failed. Restoring backup..."))
|
||||
$Null = [System.IO.File]::Copy($SSHDConfigBackupPath.FullName, $SSHDConfigPath.FullName, $True)
|
||||
$WriteLogMessage.Invoke(0, @("Backup restored. Configuration changes reverted."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$WriteLogMessage.Invoke(1, @("sshd.exe not found at `"$($SSHDExePath.FullName)`". Skipping validation."))
|
||||
|
||||
$WriteLogMessage.Invoke(0, @("Attempting to restart sshd service to apply configuration changes. Please Wait..."))
|
||||
$Null = Restart-Service -Name 'sshd' -Force -ErrorAction SilentlyContinue
|
||||
$WriteLogMessage.Invoke(0, @("sshd service restarted."))
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("No changes required to sshd_config."))
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Service Management
|
||||
Switch ($EnableService.IsPresent -eq $True)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Configuring sshd service to start automatically. Please Wait..."))
|
||||
$Null = Set-Service -Name 'sshd' -StartupType Automatic -ErrorAction SilentlyContinue
|
||||
$WriteLogMessage.Invoke(0, @("sshd service configured for automatic startup."))
|
||||
}
|
||||
}
|
||||
|
||||
Switch ($StartService.IsPresent -eq $True)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$SSHDService = Get-Service -Name 'sshd' -ErrorAction SilentlyContinue
|
||||
|
||||
Switch ($Null -ine $SSHDService)
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
Switch ($SSHDService.Status -ine 'Running')
|
||||
{
|
||||
{($_ -eq $True)}
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("Starting sshd service. Please Wait..."))
|
||||
$Null = Start-Service -Name 'sshd' -ErrorAction SilentlyContinue
|
||||
$WriteLogMessage.Invoke(0, @("sshd service started."))
|
||||
}
|
||||
|
||||
Default
|
||||
{
|
||||
$WriteLogMessage.Invoke(0, @("sshd service is already running."))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
Reference in New Issue
Block a user