# Invoke-OpenSSHConfiguration A PowerShell toolkit for automated installation and configuration of OpenSSH on Windows systems. Provides idempotent configuration of SSH server settings, public key authentication, certificate-based authentication using Windows certificate stores, and key pair generation. ## Features - **Automated Installation**: Downloads and installs the latest Win32-OpenSSH MSI from GitHub, with dynamic OS architecture detection - **Public Key Authentication**: Idempotently manages `administrators_authorized_keys` with deduplication - **Certificate Authentication**: Exports Windows Root and Intermediate CA certificates to PEM format for OpenSSH trust - **Key Pair Generation**: Creates SSH host keys with configurable algorithm and key size - **Configuration Management**: Idempotently modifies `sshd_config` with proper encoding (ASCII, no BOM) - **Elevation Handling**: Automatically re-launches with elevated privileges when required ## Requirements - Windows 10/11 or Windows Server 2016+ - PowerShell 5.1 or later - Administrator privileges - Internet connectivity (for initial OpenSSH download) ## Installation Clone the repository and run the script directly: ```powershell git clone https://github.com/yourusername/Invoke-OpenSSHConfiguration.git cd Invoke-OpenSSHConfiguration .\Invoke-OpenSSHConfiguration.ps1 -Mode Server ``` ## Parameters | Parameter | Type | Description | |-----------|------|-------------| | `-Mode` | String | Installation mode: `Client` or `Server` | | `-PublicKeyList` | String[] | Array of public keys to add to `administrators_authorized_keys` | | `-GenerateKeyPair` | Switch | Generate SSH host key pair | | `-KeyType` | String | Key algorithm: `rsa`, `ecdsa`, `ed25519` (default: `ed25519`) | | `-KeyBits` | Int | Key size: 256, 384, 521 (ECDSA) or 2048, 3072, 4096 (RSA) | | `-EnablePubkeyAuthentication` | Switch | Enable `PubkeyAuthentication yes` in sshd_config | | `-DisablePasswordAuthentication` | Switch | Disable password authentication (`PasswordAuthentication no`) | | `-EnableCertificateAuthentication` | Switch | Configure `TrustedUserCAKeys` for certificate auth | | `-TrustWindowsCertificateAuthorities` | Switch | Export Windows CA certificates to PEM bundle | | `-CAInclusionExpression` | String | Regex to filter CAs by Subject or Thumbprint (include matches) | | `-CAExclusionExpression` | String | Regex to filter CAs by Subject or Thumbprint (exclude matches) | | `-SFTPSubsystemPath` | String | Custom SFTP server path (default: `sftp-server.exe`, configured automatically) | | `-EnableService` | Switch | Set sshd service to automatic startup | | `-StartService` | Switch | Start sshd service if not running | | `-LogDirectory` | DirectoryInfo | Custom log directory path | | `-ContinueOnError` | Switch | Continue execution on non-fatal errors | ## Usage Examples ### Basic Server Installation ```powershell .\Invoke-OpenSSHConfiguration.ps1 -Mode Server ``` ### Server with Public Key Authentication ```powershell $PublicKeys = @( 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample1... user1@host', 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample2... user2@host' ) .\Invoke-OpenSSHConfiguration.ps1 -Mode Server ` -PublicKeyList $PublicKeys ` -EnablePubkeyAuthentication ``` ### Server with Certificate Authentication (Windows CA Trust) ```powershell .\Invoke-OpenSSHConfiguration.ps1 -Mode Server ` -TrustWindowsCertificateAuthorities ` -EnableCertificateAuthentication ` -EnablePubkeyAuthentication ``` This exports all unexpired Root and Intermediate CA certificates from the Windows certificate store to `C:\ProgramData\ssh\OpenSSH-TrustedCertificateAuthorities-LocalMachine.pem` and configures sshd to trust them. ### Generate Host Keys ```powershell # Generate Ed25519 key (recommended) .\Invoke-OpenSSHConfiguration.ps1 -Mode Server -GenerateKeyPair # Generate RSA 4096-bit key .\Invoke-OpenSSHConfiguration.ps1 -Mode Server -GenerateKeyPair -KeyType rsa -KeyBits 4096 # Generate ECDSA 384-bit key .\Invoke-OpenSSHConfiguration.ps1 -Mode Server -GenerateKeyPair -KeyType ecdsa -KeyBits 384 ``` ### Full Server Configuration ```powershell .\Invoke-OpenSSHConfiguration.ps1 -Mode Server ` -PublicKeyList @('ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... admin@corp') ` -GenerateKeyPair ` -KeyType ed25519 ` -TrustWindowsCertificateAuthorities ` -EnableCertificateAuthentication ` -EnablePubkeyAuthentication ` -DisablePasswordAuthentication ` -EnableService ` -StartService ` -LogDirectory 'C:\Logs\OpenSSH' ``` ### Hardened Server (Keys Only, No Passwords) ```powershell .\Invoke-OpenSSHConfiguration.ps1 -Mode Server ` -EnablePubkeyAuthentication ` -DisablePasswordAuthentication ` -EnableService ` -StartService ``` ### Selective CA Trust (Regex Filtering) ```powershell # Only trust CAs with "Corp" or "Enterprise" in the subject .\Invoke-OpenSSHConfiguration.ps1 -Mode Server ` -TrustWindowsCertificateAuthorities ` -CAInclusionExpression '(Corp|Enterprise)' ` -EnableCertificateAuthentication # Trust all CAs except Microsoft root CAs .\Invoke-OpenSSHConfiguration.ps1 -Mode Server ` -TrustWindowsCertificateAuthorities ` -CAExclusionExpression 'Microsoft' ` -EnableCertificateAuthentication # Trust specific CA by thumbprint pattern .\Invoke-OpenSSHConfiguration.ps1 -Mode Server ` -TrustWindowsCertificateAuthorities ` -CAInclusionExpression '^A1B2C3' ` -EnableCertificateAuthentication ``` ### Custom SFTP Server Path SFTP subsystem is configured automatically. To use a custom path: ```powershell .\Invoke-OpenSSHConfiguration.ps1 -Mode Server ` -SFTPSubsystemPath 'C:\Program Files\OpenSSH\sftp-server.exe' ``` ## File Locations | File | Path | Description | |------|------|-------------| | sshd_config | `C:\ProgramData\ssh\sshd_config` | SSH server configuration | | sshd_config.backup.* | `C:\ProgramData\ssh\sshd_config.backup.*` | Timestamped config backups | | administrators_authorized_keys | `C:\ProgramData\ssh\administrators_authorized_keys` | Authorized keys for admin users | | CA Bundle | `C:\ProgramData\ssh\OpenSSH-TrustedCertificateAuthorities-LocalMachine.pem` | Trusted CA certificates | | Generated Keys | `C:\ProgramData\ssh\keys\` | Generated host key pairs | | Logs | `%WINDIR%\Temp\Invoke-OpenSSHConfiguration\Logs\` | Script execution logs | ## Certificate Authentication The script bridges Windows certificate trust to OpenSSH by exporting CA certificates: 1. Reads unexpired CA certificates from `Cert:\LocalMachine\Root` and `Cert:\LocalMachine\CA` 2. Applies optional inclusion/exclusion regex filters on Subject and Thumbprint 3. Filters for certificates with Basic Constraints CA flag 4. Exports to PEM format bundle 5. Configures `TrustedUserCAKeys` in sshd_config **Important**: OpenSSH does NOT accept expired certificates. When CA certificates are renewed, re-run the script with `-TrustWindowsCertificateAuthorities` to update the PEM bundle. ### Periodic CA Refresh For automated CA refresh, create a scheduled task: ```powershell $Action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-ExecutionPolicy Bypass -File "C:\Scripts\Invoke-OpenSSHConfiguration.ps1" -TrustWindowsCertificateAuthorities' $Trigger = New-ScheduledTaskTrigger -Daily -At '3:00AM' $Principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest Register-ScheduledTask -TaskName 'OpenSSH-CA-Refresh' -Action $Action -Trigger $Trigger -Principal $Principal ``` ## Toolkit Functions The toolkit includes reusable functions in `Toolkit\Functions\`: | Function | Description | |----------|-------------| | `ConvertFrom-CertificateToPEM` | Converts X.509 certificates to PEM format | | `Get-GitRepositoryRelease` | Downloads releases from GitHub repositories | | `Get-CurrentUser` | Retrieves current user session information | | `Start-ProcessWithOutput` | Executes processes with output capture and logging | | `Get-InstalledSoftware` | Queries installed software from registry | | `Get-MSIPropertyList` | Reads MSI file properties | ## Known Limitations ### Current Gaps (Not Yet Implemented) **Server Configuration** - Port configuration (change from default 22) - User/group restrictions (AllowUsers, AllowGroups) - SSH banner/MOTD configuration - ListenAddress binding to specific interfaces **Security** - No automatic ACL/permission setting on generated keys - No Windows Firewall rule creation **Management** - No uninstall/rollback capability - No certificate expiry warnings ### Features Implemented - ✅ Password authentication control (`-DisablePasswordAuthentication`) - ✅ Service auto-start (`-EnableService`, `-StartService`) - ✅ Config backup before modification (automatic timestamped backups) - ✅ Config validation (`sshd -t`) before restart with automatic rollback - ✅ CA selection via regex (`-CAInclusionExpression`, `-CAExclusionExpression`) - ✅ SFTP subsystem (configured automatically, custom path via `-SFTPSubsystemPath`) ## Troubleshooting ### Script Re-launches in New Window The script automatically elevates to administrator. This is expected behavior. ### Public Keys Not Working 1. Verify the key format is correct (starts with `ssh-rsa`, `ssh-ed25519`, etc.) 2. Check `C:\ProgramData\ssh\administrators_authorized_keys` file permissions 3. Ensure `PubkeyAuthentication yes` is in sshd_config ### Certificate Authentication Not Working 1. Verify CA certificates are not expired 2. Check the PEM bundle exists at `C:\ProgramData\ssh\OpenSSH-TrustedCertificateAuthorities-LocalMachine.pem` 3. Verify `TrustedUserCAKeys` path in sshd_config 4. Run `sshd -t` to validate configuration ### Service Won't Start 1. Check Windows Event Viewer for sshd errors 2. Validate config: `& 'C:\Program Files\OpenSSH\sshd.exe' -t` 3. Ensure no port conflicts on TCP 22 ## Project Structure ``` Invoke-OpenSSHConfiguration/ ├── Invoke-OpenSSHConfiguration.ps1 # Main script ├── README.md # This file ├── Content/ # Additional content files ├── Toolkit/ │ ├── Toolkit.ps1 # Core toolkit initialization │ ├── Functions/ # Reusable PowerShell functions │ │ ├── ConvertFrom-CertificateToPEM.ps1 │ │ ├── Get-GitRepositoryRelease.ps1 │ │ ├── Get-CurrentUser.ps1 │ │ └── ... │ ├── Modules/ # PowerShell modules │ └── Tools/ # External tools └── .augment/ └── rules/ # Coding standards ``` ## Contributing 1. Follow the coding standards in `.augment/rules/PowershellScripts.md` 2. Use the established patterns (OrderedDictionary, Switch statements, etc.) 3. Test changes with `-Verbose` flag 4. Update documentation for new parameters ## License [Add your license here] ## Acknowledgments - [Win32-OpenSSH](https://github.com/PowerShell/Win32-OpenSSH) - Microsoft's OpenSSH port for Windows