-
Notifications
You must be signed in to change notification settings - Fork 60
init file from sshd_config_default on Windows #1474
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -107,6 +107,57 @@ pub fn get_default_sshd_config_path(input: Option<PathBuf>) -> Result<PathBuf, S | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| fn get_sshd_config_default_source_candidates() -> Vec<PathBuf> { | ||||||
| let mut candidates: Vec<PathBuf> = Vec::new(); | ||||||
|
|
||||||
| if cfg!(windows) && let Ok(system_drive) = std::env::var("SystemDrive") { | ||||||
| candidates.push(PathBuf::from(format!("{system_drive}\\Windows\\System32\\OpenSSH\\sshd_config_default"))); | ||||||
| } | ||||||
|
|
||||||
| candidates | ||||||
| } | ||||||
|
|
||||||
| /// Ensure the target `sshd_config` exists by seeding it from a platform default source. | ||||||
| /// | ||||||
| /// # Errors | ||||||
| /// | ||||||
| /// This function returns an error if the target cannot be created or no source default config is available. | ||||||
| pub fn ensure_sshd_config_exists(input: Option<PathBuf>) -> Result<PathBuf, SshdConfigError> { | ||||||
| let target_path = get_default_sshd_config_path(input)?; | ||||||
| if target_path.exists() { | ||||||
| return Ok(target_path); | ||||||
| } | ||||||
|
|
||||||
| if !cfg!(windows) { | ||||||
| return Err(SshdConfigError::FileNotFound( | ||||||
| t!("util.sshdConfigNotFoundNonWindows").to_string() | ||||||
|
||||||
| t!("util.sshdConfigNotFoundNonWindows").to_string() | |
| target_path.display().to_string() |
Copilot
AI
Apr 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When no sshd_config_default source is found, ensure_sshd_config_exists returns SshdConfigError::InvalidInput, which will surface as an "Invalid input" error even though the user's input may be fine (the environment is missing a default source file). Consider using FileNotFound (with the missing source/target path) or a dedicated error variant/message so callers can distinguish missing prerequisites from malformed input.
| SshdConfigError::InvalidInput(t!("util.sshdConfigDefaultNotFound", paths = paths).to_string()) | |
| SshdConfigError::FileNotFound(t!("util.sshdConfigDefaultNotFound", paths = paths).to_string()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -148,7 +148,7 @@ PasswordAuthentication no | |
| Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue | ||
| } | ||
|
|
||
| It 'Should fail when config file does not exist' { | ||
| It 'Should fail without creating target config when file does not exist' { | ||
| $nonExistentPath = Join-Path $TestDrive 'nonexistent_sshd_config' | ||
|
|
||
| $inputData = @{ | ||
|
|
@@ -161,6 +161,27 @@ PasswordAuthentication no | |
| sshdconfig get --input $inputData -s sshd-config 2>$stderrFile | ||
| $LASTEXITCODE | Should -Not -Be 0 | ||
|
|
||
| Test-Path $nonExistentPath | Should -Be $false | ||
|
|
||
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | ||
| $stderr | Should -Match "File not found" | ||
|
|
||
| Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue | ||
| } | ||
|
|
||
| It 'Should fail when config file does not exist even when default source is missing' { | ||
| $nonExistentPath = Join-Path $TestDrive 'nonexistent_sshd_config' | ||
|
|
||
| $inputData = @{ | ||
| _metadata = @{ | ||
|
Comment on lines
+172
to
+176
|
||
| filepath = $nonExistentPath | ||
| } | ||
| } | ConvertTo-Json | ||
|
|
||
| $stderrFile = Join-Path $TestDrive "stderr_missing_default_source.txt" | ||
| sshdconfig get --input $inputData -s sshd-config 2>$stderrFile | ||
| $LASTEXITCODE | Should -Not -Be 0 | ||
|
|
||
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | ||
| $stderr | Should -Match "File not found" | ||
| Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,9 @@ Describe 'sshd_config Set Tests' -Skip:($skipTest) { | |||||||||||||||||||||||||||||||||||||
| $TestDir = Join-Path $TestDrive "sshd_test" | ||||||||||||||||||||||||||||||||||||||
| New-Item -Path $TestDir -ItemType Directory -Force | Out-Null | ||||||||||||||||||||||||||||||||||||||
| $TestConfigPath = Join-Path $TestDir "sshd_config" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $script:DefaultSourceExists = $IsWindows -and | ||||||||||||||||||||||||||||||||||||||
| (Test-Path -Path "$env:SystemDrive\Windows\System32\OpenSSH\sshd_config_default" -PathType Leaf -ErrorAction SilentlyContinue) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| AfterEach { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -180,7 +183,7 @@ Describe 'sshd_config Set Tests' -Skip:($skipTest) { | |||||||||||||||||||||||||||||||||||||
| sshdconfig set --input $validConfig -s sshd-config | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| It 'Should fail with purge=false when file does not exist' { | ||||||||||||||||||||||||||||||||||||||
| It 'Should seed missing file from default source when available on Windows, or fail otherwise' { | ||||||||||||||||||||||||||||||||||||||
| $nonExistentPath = Join-Path $TestDrive "nonexistent_sshd_config" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $inputConfig = @{ | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -193,12 +196,26 @@ Describe 'sshd_config Set Tests' -Skip:($skipTest) { | |||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $stderrFile = Join-Path $TestDrive "stderr_purgefalse_nofile.txt" | ||||||||||||||||||||||||||||||||||||||
| sshdconfig set --input $inputConfig -s sshd-config 2>$stderrFile | ||||||||||||||||||||||||||||||||||||||
| $LASTEXITCODE | Should -Not -Be 0 | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | ||||||||||||||||||||||||||||||||||||||
| $stderr | Should -Match "_purge=false requires an existing sshd_config file" | ||||||||||||||||||||||||||||||||||||||
| $stderr | Should -Match "Use _purge=true to create a new configuration file" | ||||||||||||||||||||||||||||||||||||||
| if ($IsWindows -and $script:DefaultSourceExists) { | ||||||||||||||||||||||||||||||||||||||
| $LASTEXITCODE | Should -Be 0 | ||||||||||||||||||||||||||||||||||||||
| Test-Path $nonExistentPath | Should -Be $true | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| $getInput = @{ | ||||||||||||||||||||||||||||||||||||||
| _metadata = @{ | ||||||||||||||||||||||||||||||||||||||
| filepath = $nonExistentPath | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ConvertTo-Json | ||||||||||||||||||||||||||||||||||||||
| $result = sshdconfig get --input $getInput -s sshd-config 2>$null | ConvertFrom-Json | ||||||||||||||||||||||||||||||||||||||
| $result.Port | Should -Be "8888" | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| $LASTEXITCODE | Should -Not -Be 0 | ||||||||||||||||||||||||||||||||||||||
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | ||||||||||||||||||||||||||||||||||||||
| $stderr | Should -Match "no default source could be found" | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+211
to
+215
|
||||||||||||||||||||||||||||||||||||||
| } else { | |
| $LASTEXITCODE | Should -Not -Be 0 | |
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | |
| $stderr | Should -Match "no default source could be found" | |
| } | |
| } | |
| elseif ($IsWindows) { | |
| $LASTEXITCODE | Should -Not -Be 0 | |
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | |
| $stderr | Should -Match "no default source could be found" | |
| Test-Path $nonExistentPath | Should -Be $false | |
| } | |
| else { | |
| $LASTEXITCODE | Should -Not -Be 0 | |
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | |
| $stderr | Should -Match "(?i)(file not found|cannot find|no such file|does not exist)" | |
| Test-Path $nonExistentPath | Should -Be $false | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -33,6 +33,9 @@ Describe 'sshd-config-repeat Set Tests' -Skip:($skipTest) { | |||||||||||||||||||||||||||||||||
| $script:DefaultSftpPath = "/usr/lib/openssh/sftp-server" | ||||||||||||||||||||||||||||||||||
| $script:AlternatePath = "/usr/libexec/sftp-server" | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| $script:DefaultSourceExists = $IsWindows -and | ||||||||||||||||||||||||||||||||||
| (Test-Path -Path "$env:SystemDrive\Windows\System32\OpenSSH\sshd_config_default" -PathType Leaf -ErrorAction SilentlyContinue) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| AfterEach { | ||||||||||||||||||||||||||||||||||
|
|
@@ -212,5 +215,35 @@ PasswordAuthentication yes | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| It 'Should seed missing file from default source when available on Windows, or fail otherwise' { | ||||||||||||||||||||||||||||||||||
| $nonExistentPath = Join-Path $TestDrive "nonexistent_sshd_config" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| $inputConfig = @{ | ||||||||||||||||||||||||||||||||||
| _metadata = @{ | ||||||||||||||||||||||||||||||||||
| filepath = $nonExistentPath | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| _exist = $true | ||||||||||||||||||||||||||||||||||
| subsystem = @{ | ||||||||||||||||||||||||||||||||||
| name = "powershell" | ||||||||||||||||||||||||||||||||||
| value = "/usr/bin/pwsh -sshs" | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ConvertTo-Json | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| $stderrFile = Join-Path $TestDrive "stderr_missing_default_repeat.txt" | ||||||||||||||||||||||||||||||||||
| sshdconfig set --input $inputConfig -s sshd-config-repeat 2>$stderrFile | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if ($IsWindows -and $script:DefaultSourceExists) { | ||||||||||||||||||||||||||||||||||
| $LASTEXITCODE | Should -Be 0 | ||||||||||||||||||||||||||||||||||
| Test-Path $nonExistentPath | Should -Be $true | ||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| $LASTEXITCODE | Should -Not -Be 0 | ||||||||||||||||||||||||||||||||||
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | ||||||||||||||||||||||||||||||||||
| $stderr | Should -Match "no default source could be found" | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+239
to
+243
|
||||||||||||||||||||||||||||||||||
| } else { | |
| $LASTEXITCODE | Should -Not -Be 0 | |
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | |
| $stderr | Should -Match "no default source could be found" | |
| } | |
| } | |
| elseif ($IsWindows) { | |
| $LASTEXITCODE | Should -Not -Be 0 | |
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | |
| $stderr | Should -Match "no default source could be found" | |
| } | |
| else { | |
| $LASTEXITCODE | Should -Not -Be 0 | |
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | |
| $stderr | Should -Match "File not found|No such file|cannot find" | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -33,6 +33,9 @@ Describe 'sshd-config-repeat-list Set Tests' -Skip:($skipTest) { | |||||||||||||||||||||||||||||||||||||||||||||||
| $script:DefaultSftpPath = "/usr/lib/openssh/sftp-server" | ||||||||||||||||||||||||||||||||||||||||||||||||
| $script:AlternatePath = "/usr/libexec/sftp-server" | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| $script:DefaultSourceExists = $IsWindows -and | ||||||||||||||||||||||||||||||||||||||||||||||||
| (Test-Path -Path "$env:SystemDrive\Windows\System32\OpenSSH\sshd_config_default" -PathType Leaf -ErrorAction SilentlyContinue) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| AfterEach { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -320,5 +323,37 @@ PasswordAuthentication yes | |||||||||||||||||||||||||||||||||||||||||||||||
| $subsystems = Get-Content $TestConfigPath | Where-Object { $_ -match '^\s*subsystem\s+' } | ||||||||||||||||||||||||||||||||||||||||||||||||
| $subsystems.Count | Should -Be 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| It 'Should seed missing file from default source when available on Windows, or fail otherwise' { | ||||||||||||||||||||||||||||||||||||||||||||||||
| $nonExistentPath = Join-Path $TestDrive "nonexistent_sshd_config" | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| $inputConfig = @{ | ||||||||||||||||||||||||||||||||||||||||||||||||
| _metadata = @{ | ||||||||||||||||||||||||||||||||||||||||||||||||
| filepath = $nonExistentPath | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| _purge = $false | ||||||||||||||||||||||||||||||||||||||||||||||||
| subsystem = @( | ||||||||||||||||||||||||||||||||||||||||||||||||
| @{ | ||||||||||||||||||||||||||||||||||||||||||||||||
| name = "powershell" | ||||||||||||||||||||||||||||||||||||||||||||||||
| value = "/usr/bin/pwsh -sshs" | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ConvertTo-Json -Depth 10 | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| $stderrFile = Join-Path $TestDrive "stderr_missing_default_repeat_list.txt" | ||||||||||||||||||||||||||||||||||||||||||||||||
| sshdconfig set --input $inputConfig -s sshd-config-repeat-list 2>$stderrFile | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if ($IsWindows -and $script:DefaultSourceExists) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| $LASTEXITCODE | Should -Be 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| Test-Path $nonExistentPath | Should -Be $true | ||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||
| $LASTEXITCODE | Should -Not -Be 0 | ||||||||||||||||||||||||||||||||||||||||||||||||
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | ||||||||||||||||||||||||||||||||||||||||||||||||
| $stderr | Should -Match "no default source could be found" | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+345
to
+353
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if ($IsWindows -and $script:DefaultSourceExists) { | |
| $LASTEXITCODE | Should -Be 0 | |
| Test-Path $nonExistentPath | Should -Be $true | |
| } else { | |
| $LASTEXITCODE | Should -Not -Be 0 | |
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | |
| $stderr | Should -Match "no default source could be found" | |
| } | |
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | |
| if ($IsWindows -and $script:DefaultSourceExists) { | |
| $LASTEXITCODE | Should -Be 0 | |
| Test-Path $nonExistentPath | Should -Be $true | |
| } | |
| elseif ($IsWindows) { | |
| $LASTEXITCODE | Should -Not -Be 0 | |
| $stderr | Should -Match "no default source could be found" | |
| } | |
| else { | |
| $LASTEXITCODE | Should -Not -Be 0 | |
| $stderr | Should -Match "file.*not found|no such file" | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Windows default source path is constructed from
SystemDriveand a hard-coded\Windows\System32\...segment. This can be wrong on systems where the Windows directory isn't namedWindowsor is located elsewhere. Prefer deriving the path fromSystemRoot/WINDIR(or another authoritative Windows directory API) and joining path components instead of string formatting.