Skip to content

init file from sshd_config_default on Windows#1474

Open
tgauth wants to merge 3 commits intoPowerShell:mainfrom
tgauth:fix-file-init
Open

init file from sshd_config_default on Windows#1474
tgauth wants to merge 3 commits intoPowerShell:mainfrom
tgauth:fix-file-init

Conversation

@tgauth
Copy link
Copy Markdown
Collaborator

@tgauth tgauth commented Apr 9, 2026

PR Summary

  • On Windows, if config file does not exist (and set command is specified), initialize the config file based on sshd_config_defaults file (from FOD install location).
  • Previously, in this scenario, the global sshd_config resource would initialize an empty file and the subsystem/subsystemList resources would error.

PR Context

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 missing sshd_config from sshd_config_default on Windows.
  • Updated set flows 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()
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
t!("util.sshdConfigNotFoundNonWindows").to_string()
target_path.display().to_string()

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +114
if cfg!(windows) && let Ok(system_drive) = std::env::var("SystemDrive") {
candidates.push(PathBuf::from(format!("{system_drive}\\Windows\\System32\\OpenSSH\\sshd_config_default")));
Copy link

Copilot AI Apr 9, 2026

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 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.

Suggested change
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"),
);
}

Copilot uses AI. Check for mistakes.
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"
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
} 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
}

Copilot uses AI. Check for mistakes.
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"
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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").

Suggested change
} 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"
}

Copilot uses AI. Check for mistakes.
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"
}
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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"
}

Copilot uses AI. Check for mistakes.
Comment on lines +172 to +176
It 'Should fail when config file does not exist even when default source is missing' {
$nonExistentPath = Join-Path $TestDrive 'nonexistent_sshd_config'

$inputData = @{
_metadata = @{
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
.map(|path| path.display().to_string())
.collect::<Vec<String>>()
.join(", ");
SshdConfigError::InvalidInput(t!("util.sshdConfigDefaultNotFound", paths = paths).to_string())
Copy link

Copilot AI Apr 9, 2026

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.

Suggested change
SshdConfigError::InvalidInput(t!("util.sshdConfigDefaultNotFound", paths = paths).to_string())
SshdConfigError::FileNotFound(t!("util.sshdConfigDefaultNotFound", paths = paths).to_string())

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sshd resource fails if sshd_config doesn't exist

2 participants