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
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) {
$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"
}
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