init file from sshd_config_default on Windows#1474
init file from sshd_config_default on Windows#1474tgauth wants to merge 3 commits intoPowerShell:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR addresses #1469 by changing how the sshdconfig resource behaves when the target sshd_config file is missing—on Windows, it attempts to seed the file from the system’s sshd_config_default so subsequent subsystem-related operations don’t fail due to an empty/nonexistent config.
Changes:
- Added
ensure_sshd_config_exists()to seed a missingsshd_configfromsshd_config_defaulton Windows. - Updated
setflows to require/ensure an existing config before applying non-purge updates and repeat-keyword updates. - Expanded Pester coverage to validate behavior when the target config file is missing.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| resources/sshdconfig/src/util.rs | Adds seeding helper to create a missing target config from a Windows default source. |
| resources/sshdconfig/src/set.rs | Calls the new seeding helper before reading/modifying config for non-purge and repeat operations. |
| resources/sshdconfig/locales/en-us.toml | Adds new localized messages for seeding and missing-default-source scenarios. |
| resources/sshdconfig/tests/sshdconfig.set.tests.ps1 | Updates/extends set tests for missing-file behavior. |
| resources/sshdconfig/tests/sshdconfigRepeat.tests.ps1 | Adds repeat-keyword test for missing-file behavior. |
| resources/sshdconfig/tests/sshdconfigRepeatList.tests.ps1 | Adds repeat-list test for missing-file behavior. |
| resources/sshdconfig/tests/sshdconfig.get.tests.ps1 | Tightens get tests to ensure missing config doesn’t get created. |
|
|
||
| if !cfg!(windows) { | ||
| return Err(SshdConfigError::FileNotFound( | ||
| t!("util.sshdConfigNotFoundNonWindows").to_string() |
There was a problem hiding this comment.
ensure_sshd_config_exists returns SshdConfigError::FileNotFound with a full explanatory sentence as the payload. Since FileNotFound is formatted as "File not found: {path}", this ends up treating the message as a path and also loses the actual missing target_path (especially misleading when the caller provided a custom _metadata.filepath). Consider returning FileNotFound(target_path.display().to_string()) (and, if needed, a separate error variant/message for the extra guidance) so the error reliably reports the missing path.
| t!("util.sshdConfigNotFoundNonWindows").to_string() | |
| target_path.display().to_string() |
| if cfg!(windows) && let Ok(system_drive) = std::env::var("SystemDrive") { | ||
| candidates.push(PathBuf::from(format!("{system_drive}\\Windows\\System32\\OpenSSH\\sshd_config_default"))); |
There was a problem hiding this comment.
The Windows default source path is constructed from SystemDrive and a hard-coded \Windows\System32\... segment. This can be wrong on systems where the Windows directory isn't named Windows or is located elsewhere. Prefer deriving the path from SystemRoot/WINDIR (or another authoritative Windows directory API) and joining path components instead of string formatting.
| if cfg!(windows) && let Ok(system_drive) = std::env::var("SystemDrive") { | |
| candidates.push(PathBuf::from(format!("{system_drive}\\Windows\\System32\\OpenSSH\\sshd_config_default"))); | |
| if cfg!(windows) { | |
| if let Some(system_root) = std::env::var_os("SystemRoot") | |
| .or_else(|| std::env::var_os("WINDIR")) | |
| { | |
| candidates.push( | |
| PathBuf::from(system_root) | |
| .join("System32") | |
| .join("OpenSSH") | |
| .join("sshd_config_default"), | |
| ); | |
| } |
| } else { | ||
| $LASTEXITCODE | Should -Not -Be 0 | ||
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | ||
| $stderr | Should -Match "no default source could be found" | ||
| } |
There was a problem hiding this comment.
In this test, the else branch executes on non-Windows as well, but on non-Windows the set command will fail with a FileNotFound error (e.g., "File not found" / missing path), not with the "no default source could be found" message. As written, this assertion will fail on Linux/macOS CI. Split the assertions so non-Windows checks for the expected file-not-found error (and that the target file was not created), and reserve the "no default source" assertion for the Windows-without-default-source case.
| } 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 | |
| } |
| } else { | ||
| $LASTEXITCODE | Should -Not -Be 0 | ||
| $stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue | ||
| $stderr | Should -Match "no default source could be found" | ||
| } |
There was a problem hiding this comment.
The else branch here also runs on non-Windows, but the implementation fails on non-Windows with a FileNotFound-style error, not the "no default source could be found" message. This will cause the test to fail on Linux/macOS runners. Consider separating the non-Windows expectation (match "File not found" / missing path) from the Windows-without-default-source expectation (match "no default source could be found").
| } 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" | |
| } |
|
|
||
| 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" | ||
| } |
There was a problem hiding this comment.
This else branch includes non-Windows, but on non-Windows the set command fails with a file-not-found error rather than the "no default source could be found" message. As written, this assertion will fail on Linux/macOS CI. Split the assertions by OS (and by whether the Windows default source exists) so each branch matches the message the implementation actually emits.
| 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" | |
| } |
| It 'Should fail when config file does not exist even when default source is missing' { | ||
| $nonExistentPath = Join-Path $TestDrive 'nonexistent_sshd_config' | ||
|
|
||
| $inputData = @{ | ||
| _metadata = @{ |
There was a problem hiding this comment.
This It block name says "even when default source is missing", but the test doesn't set up or verify that condition (it just attempts get on a non-existent file, which already covers the failure path). Either adjust the test name to match what it's asserting, or add explicit setup/assertions that the Windows default source is absent if that's the scenario you want to cover.
| .map(|path| path.display().to_string()) | ||
| .collect::<Vec<String>>() | ||
| .join(", "); | ||
| SshdConfigError::InvalidInput(t!("util.sshdConfigDefaultNotFound", paths = paths).to_string()) |
There was a problem hiding this comment.
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()) |
PR Summary
PR Context