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
193 changes: 193 additions & 0 deletions dsc/tests/dsc_sshdconfig.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,197 @@ resources:
$out.results.result.afterState.authorizedkeysfile | Should -Be @('./.ssh/authorized_keys', './.ssh/authorized_keys2')
}
}

Context 'Subsystem and SubsystemList Tests' {
BeforeAll {
# Create a temporary test directory for sshd_config files
$TestDir = Join-Path $TestDrive "sshd_test"
New-Item -Path $TestDir -ItemType Directory -Force | Out-Null
$script:TestConfigPath = Join-Path $TestDir "sshd_config"

# Define OS-specific paths with spaces
if ($IsWindows) {
$script:PathWithSpaces = "C:\Program Files\OpenSSH\sftp-server.exe"
$script:DefaultSftpPath = "sftp-server.exe"
$script:AlternatePath = "C:\OpenSSH\bin\sftp.exe"
}
else {
$script:PathWithSpaces = "/usr/local/lib/openssh server/sftp-server"
$script:DefaultSftpPath = "/usr/lib/openssh/sftp-server"
$script:AlternatePath = "/usr/libexec/sftp-server"
}
}

BeforeEach {
# Create test config with existing subsystems
$initialContent = @"
Port 22
subsystem sftp $script:DefaultSftpPath
Subsystem test2 /path/to/test2
PasswordAuthentication yes
"@
Set-Content -Path $script:TestConfigPath -Value $initialContent
}

AfterEach {
# Clean up test config file after each test
if (Test-Path $script:TestConfigPath) {
Remove-Item -Path $script:TestConfigPath -Force -ErrorAction SilentlyContinue
}
if (Test-Path "$script:TestConfigPath.bak") {
Remove-Item -Path "$script:TestConfigPath.bak" -Force -ErrorAction SilentlyContinue
}
}

It 'Should add a new subsystem that does not already exist' -Skip:($script:skipSubsystemTests) {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
metadata:
Microsoft.DSC:
securityContext: elevated
resources:
- name: newsub
type: Microsoft.OpenSSH.SSHD/Subsystem
metadata:
filepath: $($script:TestConfigPath -replace '\\', '/')
properties:
_exist: true
subsystem:
name: newsubsystem
value: /path/to/newsubsystem
"@
$out = dsc config set -i "$config_yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
$out.hadErrors | Should -BeFalse
$out.results.Count | Should -Be 1
$out.results[0].type | Should -BeExactly 'Microsoft.OpenSSH.SSHD/Subsystem'
$out.results[0].result.afterState._exist | Should -Be $true
$out.results[0].result.afterState.subsystem.name | Should -Be 'newsubsystem'

# Verify file contains the new subsystem
$subsystems = Get-Content $script:TestConfigPath | Where-Object { $_ -match '^\s*subsystem\s+' }
$subsystems.Count | Should -Be 3
$subsystems | Should -Contain "subsystem newsubsystem /path/to/newsubsystem"
}

It 'Should remove a subsystem when _exist is false' -Skip:($script:skipSubsystemTests) {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
metadata:
Microsoft.DSC:
securityContext: elevated
resources:
- name: removesub
type: Microsoft.OpenSSH.SSHD/Subsystem
metadata:
filepath: $($script:TestConfigPath -replace '\\', '/')
properties:
_exist: false
subsystem:
name: sftp
"@
$out = dsc config set -i "$config_yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
$out.hadErrors | Should -BeFalse
$out.results[0].result.afterState._exist | Should -Be $false

# Verify subsystem was removed
$subsystems = Get-Content $script:TestConfigPath | Where-Object { $_ -match '^\s*subsystem\s+' }
$subsystems.Count | Should -Be 1
$subsystems | Should -Not -Match 'sftp'
}

It 'Should add multiple new subsystems with SubsystemList' -Skip:($script:skipSubsystemTests) {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
metadata:
Microsoft.DSC:
securityContext: elevated
resources:
- name: multisubsystem
type: Microsoft.OpenSSH.SSHD/SubsystemList
metadata:
filepath: $($script:TestConfigPath -replace '\\', '/')
properties:
_purge: false
subsystem:
- name: newsub1
value: /path/to/newsub1
- name: newsub2
value: /path/to/newsub2
"@
$out = dsc config set -i "$config_yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
$out.hadErrors | Should -BeFalse
$out.results[0].type | Should -BeExactly 'Microsoft.OpenSSH.SSHD/SubsystemList'
$out.results[0].result.afterState.subsystem.Count | Should -Be 2

# Verify all subsystems are present (old + new)
$subsystems = Get-Content $script:TestConfigPath | Where-Object { $_ -match '^\s*subsystem\s+' }
$subsystems.Count | Should -Be 4
$subsystems | Should -Contain "subsystem newsub1 /path/to/newsub1"
$subsystems | Should -Contain "subsystem newsub2 /path/to/newsub2"
}

It 'Should preserve unlisted subsystems when _purge is false' {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
metadata:
Microsoft.DSC:
securityContext: elevated
resources:
- name: preservesubsystem
type: Microsoft.OpenSSH.SSHD/SubsystemList
metadata:
filepath: $($script:TestConfigPath -replace '\\', '/')
properties:
_purge: false
subsystem:
- name: onlythisone
value: /path/to/this
"@
$out = dsc config set -i "$config_yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
$out.hadErrors | Should -BeFalse

# Verify all existing subsystems are still present plus the new one
$subsystems = Get-Content $script:TestConfigPath | Where-Object { $_ -match '^\s*subsystem\s+' }
$subsystems.Count | Should -Be 3
$subsystems | Should -Contain "subsystem onlythisone /path/to/this"
$subsystems | Should -Contain "subsystem sftp $script:DefaultSftpPath"
$subsystems | Should -Contain "Subsystem test2 /path/to/test2"
}

It 'Should remove unlisted subsystems when _purge is true' {
$config_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
metadata:
Microsoft.DSC:
securityContext: elevated
resources:
- name: purgesubsystem
type: Microsoft.OpenSSH.SSHD/SubsystemList
metadata:
filepath: $($script:TestConfigPath -replace '\\', '/')
properties:
_purge: true
subsystem:
- name: sftp
value: $($script:AlternatePath -replace '\\', '/')
- name: newsub
value: /path/to/newsub
"@
$out = dsc config set -i "$config_yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
$out.hadErrors | Should -BeFalse

# Verify only specified subsystems remain
$subsystems = Get-Content $script:TestConfigPath | Where-Object { $_ -match '^\s*subsystem\s+' }
$subsystems.Count | Should -Be 2
$sftpLine = $subsystems | Where-Object { $_ -match 'sftp' }
$sftpLine | Should -Match ([regex]::Escape($script:AlternatePath))
$subsystems | Should -Contain "subsystem newsub /path/to/newsub"
$subsystems | Should -Not -Match 'test2'
}
}
}
4 changes: 3 additions & 1 deletion resources/sshdconfig/.project.data.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
],
"CopyFiles": {
"All": [
"sshd_config.dsc.resource.json"
"sshd_config.dsc.resource.json",
"sshd-subsystem.dsc.resource.json",
"sshd-subsystemList.dsc.resource.json"
],
"Windows": [
"sshd-windows.dsc.resource.json"
Expand Down
11 changes: 10 additions & 1 deletion resources/sshdconfig/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ defaultShellEscapeArgsMustBe0Or1 = "'%{input}' must be a 0 or 1"
defaultShellEscapeArgsMustBeDWord = "escapeArguments must be a DWord"
defaultShellMustBeString = "shell must be a string"
includeWarning = "Include directive found in sshd_config. This resource uses 'sshd -T' to process the overall configuration state, which merges all included files but does not return the Include directive itself"
invalidSetting = "Invalid setting: %{setting}"
traceInput = "Get input:"
windowsOnly = "Microsoft.OpenSSH.SSHD/Windows is only applicable to Windows"

Expand All @@ -57,6 +58,8 @@ missingKeyInCriteria = "missing key in criteria node: '%{input}'"
missingValueInCriteria = "missing value in criteria node: '%{input}'"
missingValueInChildNode = "missing value in child node: '%{input}'"
noArgumentsFound = "no arguments found in node: '%{input}'"
structuredKeywordCannotUseOperator = "structured keyword '%{keyword}' cannot use operator syntax"
structuredKeywordRequiresMinArgs = "structured keyword '%{keyword}' requires at least a name and a value"
valueDebug = "Parsed argument value:"
unknownNode = "unknown node: '%{kind}'"
unknownNodeType = "unknown node type: '%{node}'"
Expand All @@ -67,9 +70,15 @@ backupCreated = "Backup created at: %{path}"
cleanupFailed = "Failed to clean up temporary file %{path}: %{error}"
configDoesNotExist = "sshd_config file does not exist, no backup created"
defaultShellDebug = "default_shell: %{shell}"
entryNameRequired = "Entry 'name' field is required and cannot be empty"
entryValueRequired = "Entry '%{name}' requires a 'value' field"
expectedArrayForKeyword = "Expected array for keyword '%{keyword}'"
failedToParse = "failed to parse: '%{input}'"
failedToParseDefaultShell = "failed to parse input for DefaultShell with error: '%{error}'"
multipleKeywordsNotAllowed = "Multiple keywords not allowed in input (found %{count} keywords)"
nameValueEntryRequiresValue = "Name-value keyword entry requires 'value' field when _exist is true"
noKeywordFoundInInput = "No keyword found in input (expected one keyword like 'subsystem')"
purgeFalseRequiresExistingFile = "_purge=false requires an existing sshd_config file. Use _purge=true to create a new configuration file."
purgeFalseUnsupported = "_purge=false is not supported for keywords that can have multiple values"
settingDefaultShell = "Setting default shell"
settingSshdConfig = "Setting sshd_config"
shellPathDoesNotExist = "shell path does not exist: '%{shell}'"
Expand Down
6 changes: 4 additions & 2 deletions resources/sshdconfig/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,7 @@ pub struct DefaultShell {
#[derive(Clone, Debug, Eq, PartialEq, ValueEnum)]
pub enum Setting {
SshdConfig,
WindowsGlobal
}
SshdConfigRepeat,
SshdConfigRepeatList,
WindowsGlobal,
}
Loading
Loading