Public/Install-Sftp.ps1
|
#!/usr/bin/env pwsh Set-StrictMode -Version Latest if ($IsWindows) { function Install-Sftp { <# .SYNOPSIS Installs the Windows OpenSSH server as a chrooted SFTP drop point. .DESCRIPTION Installs the OpenSSH.Server Windows capability, sets the sshd service to start automatically as LocalSystem from System32\OpenSSH, and copies sshd_config and banner.txt from Config/Sshd to C:\ProgramData\ssh. The config confines the sftp-service account to SFTP in C:\sftp-service, landing in its ingest directory, which this function creates. The sftp-service account must already exist. Generates any missing host keys, validates the config with sshd -t, then replaces the default OpenSSH firewall rule with one that only allows TCP 22 on one interface and address, from the given remote addresses, on the Private profile. Starts sshd last, after every check has passed. Requires an elevated session. Supports -WhatIf and -Confirm. Windows only. .PARAMETER InterfaceAlias The network interface that accepts SFTP connections, such as 'Ethernet 3'. .PARAMETER LocalAddress The address on that interface that accepts SFTP connections. .PARAMETER RemoteAddress The client addresses allowed to connect. .INPUTS None. You can't pipe objects to this function. .OUTPUTS System.Management.Automation.PSCustomObject. The Name, Status, and StartType of the sshd service. .EXAMPLE Install-Sftp -InterfaceAlias 'Ethernet 3' -LocalAddress 10.177.1.10 -RemoteAddress 192.168.177.138 -WhatIf Previews the installation. .EXAMPLE $sftpParams = @{ InterfaceAlias = 'Ethernet 3' LocalAddress = '10.177.1.10' RemoteAddress = '192.168.177.138', '192.168.177.139' } Install-Sftp @sftpParams Installs the SFTP server and allows two clients to connect. #> [CmdletBinding(SupportsShouldProcess)] [OutputType([PSCustomObject])] param( [Parameter(Mandatory, HelpMessage = 'Enter the network interface that accepts SFTP connections.')] [ValidateNotNullOrEmpty()] [string]$InterfaceAlias, [Parameter(Mandatory, HelpMessage = 'Enter the local address that accepts SFTP connections.')] [ValidateNotNullOrEmpty()] [string]$LocalAddress, [Parameter(Mandatory, HelpMessage = 'Enter the client addresses allowed to connect.')] [ValidateNotNullOrEmpty()] [string[]]$RemoteAddress ) Assert-Administrator $configSource = Join-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -ChildPath 'Config' -AdditionalChildPath 'Sshd' $openSshPath = Join-Path -Path $env:WINDIR -ChildPath 'System32\OpenSSH' $ruleName = 'PSSecurity SFTP' if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, "Install SFTP on $InterfaceAlias $LocalAddress for $($RemoteAddress -join ', ')")) { return } $ErrorActionPreference = 'Stop' [void](Add-WindowsCapability -Online -Name 'OpenSSH.Server~~~~0.0.1.0') $sshdService = Get-Service -Name sshd if ($sshdService.Status -eq 'StartPending') { $sshdService.WaitForStatus('Running', [System.TimeSpan]::FromSeconds(60)) } Stop-Service -Name sshd Set-Service -Name sshd -StartupType Automatic # Set-Service cannot change the binary path, so sc.exe points the service at System32. (sc.exe config sshd binPath= "`"$openSshPath\sshd.exe`"" obj= LocalSystem) | ForEach-Object { Write-Verbose -Message $_ } if ($LASTEXITCODE -ne 0) { throw 'Install-Sftp could not configure the sshd service executable and identity.' } [void](New-Item -ItemType Directory -Path C:\ProgramData\ssh, C:\sftp-service\ingest -Force) Copy-Item -LiteralPath "$configSource\sshd_config", "$configSource\banner.txt" -Destination C:\ProgramData\ssh -Force (& "$openSshPath\ssh-keygen.exe" -A) | ForEach-Object { Write-Verbose -Message $_ } if ($LASTEXITCODE -ne 0) { throw 'Install-Sftp failed to generate the host keys.' } (& "$openSshPath\sshd.exe" -t) | ForEach-Object { Write-Verbose -Message $_ } if ($LASTEXITCODE -ne 0) { throw 'Install-Sftp found sshd_config invalid; sshd -t failed.' } Get-NetFirewallRule -Name 'OpenSSH-Server-In-TCP' -ErrorAction SilentlyContinue | Disable-NetFirewallRule Get-NetFirewallRule -Name $ruleName -ErrorAction SilentlyContinue | Remove-NetFirewallRule $firewallParams = @{ Name = $ruleName DisplayName = $ruleName Direction = 'Inbound' Action = 'Allow' Enabled = 'True' Profile = 'Private' InterfaceAlias = $InterfaceAlias LocalAddress = $LocalAddress RemoteAddress = $RemoteAddress Protocol = 'TCP' LocalPort = 22 Service = 'sshd' EdgeTraversalPolicy = 'Block' } [void](New-NetFirewallRule @firewallParams) Start-Service -Name sshd Get-Service -Name sshd | Select-Object -Property Name, Status, StartType } } |