Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
5 changes: 4 additions & 1 deletion resources/sshdconfig/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ inputMustBeBoolean = "value of '%{input}' must be true or false"

[error]
command = "Command"
configInitRequired = "Configuration File Initialization Required"
envVar = "Environment Variable"
fileNotFound = "File not found: %{path}"
invalidInput = "Invalid Input"
Expand Down Expand Up @@ -86,7 +87,6 @@ defaultShellDebug = "default_shell: %{shell}"
expectedArrayForKeyword = "Expected array for keyword '%{keyword}'"
failedToParse = "failed to parse: '%{input}'"
failedToParseDefaultShell = "failed to parse input for DefaultShell with error: '%{error}'"
purgeFalseRequiresExistingFile = "_purge=false requires an existing sshd_config file. Use _purge=true to create a new configuration file."
settingDefaultShell = "Setting default shell"
settingSshdConfig = "Setting sshd_config"
shellPathDoesNotExist = "shell path does not exist: '%{shell}'"
Expand All @@ -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 = "'%{path}' does not exist and no default source could be found. Checked: %{paths}. Start the sshd service to initialize it, then retry."
sshdConfigNotFoundNonWindows = "'%{path}' does not exist. Start the sshd service to initialize it, then retry."
sshdConfigReadFailed = "failed to read sshd_config at path: '%{path}'"
sshdElevation = "elevated security context required"
tempFileCreated = "temporary file created at: %{path}"
Expand Down
2 changes: 2 additions & 0 deletions resources/sshdconfig/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use thiserror::Error;
pub enum SshdConfigError {
#[error("{t}: {0}", t = t!("error.command"))]
CommandError(String),
#[error("{t}: {0}", t = t!("error.configInitRequired"))]
ConfigInitRequired(String),
#[error("{t}: {0}", t = t!("error.envVar"))]
EnvVarError(#[from] std::env::VarError),
#[error("{t}", t = t!("error.fileNotFound", path = .0))]
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)
}
65 changes: 63 additions & 2 deletions resources/sshdconfig/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,69 @@ 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(win_dir) = std::env::var("windir") {
candidates.push(
PathBuf::from(win_dir)
.join("System32")
.join("OpenSSH")
.join("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::ConfigInitRequired(
t!("util.sshdConfigNotFoundNonWindows", path = target_path.display()).to_string(),
));
}

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::ConfigInitRequired(
t!(
"util.sshdConfigDefaultNotFound",
path = target_path.display(),
paths = paths
)
.to_string(),
)
})?;

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 +307,3 @@ pub fn read_sshd_config(input: Option<PathBuf>) -> Result<String, SshdConfigErro
Err(SshdConfigError::FileNotFound(filepath.display().to_string()))
}
}


5 changes: 4 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,8 +161,11 @@ 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
}
}
33 changes: 28 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,32 @@ 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) {
Comment thread
tgauth marked this conversation as resolved.
Outdated
$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"
}
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 "does not exist"
}

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
39 changes: 39 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 @@ -230,5 +233,41 @@ PasswordAuthentication yes
$subsystems = Get-Content $TestConfigPath | Where-Object { $_ -match '^\s*subsystem\s+' }
$subsystems | Should -Contain "subsystem testExistDefault /path/to/subsystem"
}

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
}
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 "does not exist"
}

Remove-Item -Path $stderrFile -Force -ErrorAction SilentlyContinue
Remove-Item -Path $nonExistentPath -Force -ErrorAction SilentlyContinue
}
}
}
41 changes: 41 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,43 @@ 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
}
elseif ($IsWindows) {
$LASTEXITCODE | Should -Not -Be 0
$stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue
$stderr | Should -Match "no default source could be found"
}
Comment thread
tgauth marked this conversation as resolved.
else {
$LASTEXITCODE | Should -Not -Be 0
$stderr = Get-Content -Path $stderrFile -Raw -ErrorAction SilentlyContinue
$stderr | Should -Match "does not exist"
}

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