Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions resources/sshdconfig/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ writingTempConfig = "Writing temporary sshd_config file"
[util]
cleanupFailed = "Failed to clean up temporary file %{path}: %{error}"
getIgnoresInputFilters = "get command does not support filtering based on input settings, provided input will be ignored"
seededConfigFromDefault = "Seeded missing sshd_config from '%{source}' to '%{target}'"
sshdConfigDefaultNotFound = "sshd_config file does not exist and no default source could be found. Checked: %{paths}"
sshdConfigNotFoundNonWindows = "sshd_config file does not exist. Start sshd once to initialize the configuration file, then retry."
sshdConfigReadFailed = "failed to read sshd_config at path: '%{path}'"
sshdElevation = "elevated security context required"
tempFileCreated = "temporary file created at: %{path}"
Expand Down
23 changes: 5 additions & 18 deletions resources/sshdconfig/src/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::repeat_keyword::{
RepeatInput, RepeatListInput, NameValueEntry,
add_or_update_entry, extract_single_keyword, remove_entry, parse_and_validate_entries
};
use crate::util::{build_command_info, get_default_sshd_config_path, invoke_sshd_config_validation};
use crate::util::{build_command_info, ensure_sshd_config_exists, get_default_sshd_config_path, invoke_sshd_config_validation};

/// Invoke the set command.
///
Expand Down Expand Up @@ -189,16 +189,9 @@ fn set_sshd_config(cmd_info: &mut CommandInfo) -> Result<(), SshdConfigError> {
let mut get_cmd_info = cmd_info.clone();
get_cmd_info.include_defaults = false;
get_cmd_info.input = Map::new();
ensure_sshd_config_exists(get_cmd_info.metadata.filepath.clone())?;

let mut existing_config = match get_sshd_settings(&get_cmd_info, true) {
Ok(config) => config,
Err(SshdConfigError::FileNotFound(_)) => {
return Err(SshdConfigError::InvalidInput(
t!("set.purgeFalseRequiresExistingFile").to_string()
));
}
Err(e) => return Err(e),
};
let mut existing_config = get_sshd_settings(&get_cmd_info, true)?;
for (key, value) in &cmd_info.input {
if value.is_null() {
existing_config.remove(key);
Expand Down Expand Up @@ -281,12 +274,6 @@ fn get_existing_config(cmd_info: &CommandInfo) -> Result<Map<String, Value>, Ssh
let mut get_cmd_info = cmd_info.clone();
get_cmd_info.include_defaults = false;
get_cmd_info.input = Map::new();
match get_sshd_settings(&get_cmd_info, false) {
Ok(config) => Ok(config),
Err(SshdConfigError::FileNotFound(_)) => {
// If file doesn't exist, create empty config
Ok(Map::new())
}
Err(e) => Err(e),
}
ensure_sshd_config_exists(get_cmd_info.metadata.filepath.clone())?;
get_sshd_settings(&get_cmd_info, false)
}
53 changes: 51 additions & 2 deletions resources/sshdconfig/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")));
Comment on lines +113 to +114
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.
}

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()
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.
));
}

let candidates = get_sshd_config_default_source_candidates();
let source_path = candidates
.iter()
.find(|candidate| candidate.is_file())
.cloned()
.ok_or_else(|| {
let paths = candidates
.iter()
.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.
})?;

if let Some(parent) = target_path.parent() {
std::fs::create_dir_all(parent)?;
}

std::fs::copy(&source_path, &target_path)?;
debug!("{}", t!("util.seededConfigFromDefault", source = source_path.display(), target = target_path.display()));

Ok(target_path)
}

/// Invoke sshd -T.
///
/// # Errors
Expand Down Expand Up @@ -244,5 +295,3 @@ pub fn read_sshd_config(input: Option<PathBuf>) -> Result<String, SshdConfigErro
Err(SshdConfigError::FileNotFound(filepath.display().to_string()))
}
}


23 changes: 22 additions & 1 deletion resources/sshdconfig/tests/sshdconfig.get.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 = @{
Expand All @@ -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
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.
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
Expand Down
27 changes: 22 additions & 5 deletions resources/sshdconfig/tests/sshdconfig.set.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 = @{
Expand All @@ -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
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.

Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue
Remove-Item -Path $nonExistentPath -Force -ErrorAction SilentlyContinue
}

It 'Should fail with invalid keyword and not modify file' {
Expand Down
33 changes: 33 additions & 0 deletions resources/sshdconfig/tests/sshdconfigRepeat.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
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.

Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue
Remove-Item -Path $nonExistentPath -Force -ErrorAction SilentlyContinue
}
}
}
35 changes: 35 additions & 0 deletions resources/sshdconfig/tests/sshdconfigRepeatList.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
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.

Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue
Remove-Item -Path $nonExistentPath -Force -ErrorAction SilentlyContinue
}
}
}
Loading