Skip to content

Add sshdconfig get #1004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 30 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d268733
add sshdcmdargs struct and method to retrieve defaults
tgauth Jul 11, 2025
ebe3308
add default option to get
tgauth Jul 11, 2025
f169871
cleanup get
tgauth Jul 11, 2025
b8356b2
fix merge conflicts
tgauth Jul 18, 2025
0d65d5d
add e2e sshdconfig tests for get/export and update schema
tgauth Jul 18, 2025
2e82fc7
cleanup get/export display
tgauth Jul 21, 2025
812e1cc
update get to read _metadata
tgauth Jul 24, 2025
8bf176d
support custom sshdconfig filepath for get tests
tgauth Jul 24, 2025
b881d7d
update toml
tgauth Jul 24, 2025
894c9b0
add comment to struct
tgauth Jul 24, 2025
011bd8c
fix clippy
tgauth Jul 24, 2025
db2d25a
fix skip logic
tgauth Jul 24, 2025
af54285
fix i8n
tgauth Jul 25, 2025
4695d56
Revert "fix i8n"
tgauth Jul 25, 2025
3acbbdd
fix i8n take 2
tgauth Jul 25, 2025
c72f6a5
fix i8n take 3
tgauth Jul 25, 2025
484003c
use copilot suggestions
tgauth Jul 25, 2025
f5b619f
address Steve's feedback
tgauth Jul 28, 2025
5e2ab58
Update en-us.toml
tgauth Jul 28, 2025
85f9a7b
Merge branch 'main' into add-sshdconfig-get
tgauth Aug 5, 2025
64e1dfc
add check for sshd in test discovery
tgauth Aug 5, 2025
8ad00ad
add newline
tgauth Aug 6, 2025
dba561a
combine export and get command behavior
tgauth Aug 7, 2025
ea05024
update get and export _includeDefaults behavior
tgauth Aug 14, 2025
21da9c0
add _inheritedDefaults functionality
tgauth Aug 14, 2025
11702a0
update parser for match
tgauth Aug 14, 2025
33ab856
modify export behavior
tgauth Aug 14, 2025
2c348ef
add localization
tgauth Aug 14, 2025
d0c61da
fix localization
tgauth Aug 14, 2025
97c767e
Merge branch 'main' into add-sshdconfig-get
tgauth Aug 15, 2025
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
136 changes: 136 additions & 0 deletions dsc/tests/dsc_sshdconfig.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
BeforeDiscovery {
if ($IsWindows) {
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [System.Security.Principal.WindowsPrincipal]::new($identity)
$isElevated = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
$sshdExists = ($null -ne (Get-Command sshd -CommandType Application -ErrorAction Ignore))
$skipTest = !$isElevated -or !$sshdExists
}
}

Describe 'SSHDConfig resource tests' -Skip:(!$IsWindows -or $skipTest) {
BeforeAll {
# set a non-default value in a temporary sshd_config file
"LogLevel Debug3`nPasswordAuthentication no" | Set-Content -Path $TestDrive/test_sshd_config
$filepath = Join-Path $TestDrive 'test_sshd_config'
$yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
metadata:
Microsoft.DSC:
securityContext: elevated
resources:
- name: sshdconfig
type: Microsoft.OpenSSH.SSHD/sshd_config
properties:
_metadata:
Copy link
Member

Choose a reason for hiding this comment

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

Input metadata should be a peer to properties. It could be passed this way since that's how DSC does it internally, but I think users looking at our tests as examples should see it passed at a higher level to the resource.

Copy link
Collaborator Author

@tgauth tgauth Aug 19, 2025

Choose a reason for hiding this comment

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

Like this? The input metadata is processed by DSC but doesn't seem to be passed to the resource (at least for get or export) unless I also need to make a change to the manifest?

- name: sshdconfig
  type: Microsoft.OpenSSH.SSHD/sshd_config
  metadata:
    filepath: 
  properties:

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think I see where the engine needs to be updated: https://github.com/PowerShell/DSC/blob/main/dsc_lib/src/configure/mod.rs#L173
I can go ahead and make that change in a separate PR?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, create a new PR if that isn't working as expected, then we'll merge that one first and get back to this one.

filepath: $filepath
"@
}

It '<command> works' -TestCases @(
@{ command = 'get' }
@{ command = 'export' }
) {
param($command)
$out = dsc config $command -i "$yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
if ($command -eq 'export') {
$out.resources.count | Should -Be 1
$out.resources[0].properties | Should -Not -BeNullOrEmpty
$out.resources[0].properties.port | Should -BeNullOrEmpty
$out.resources[0].properties.passwordAuthentication | Should -Be 'no'
$out.resources[0].properties._inheritedDefaults | Should -BeNullOrEmpty
} else {
$out.results.count | Should -Be 1
$out.results.result.actualState | Should -Not -BeNullOrEmpty
$out.results.result.actualState.port[0] | Should -Be 22
$out.results.result.actualState.passwordAuthentication | Should -Be 'no'
$out.results.result.actualState._inheritedDefaults | Should -Contain 'port'
}
}

It 'Export with filter works' {
$export_yaml = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
metadata:
Microsoft.DSC:
securityContext: elevated
resources:
- name: sshdconfig
type: Microsoft.OpenSSH.SSHD/sshd_config
properties:
passwordauthentication: 'yes'
_metadata:
filepath: $filepath
"@
$out = dsc config export -i "$export_yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
$out.resources.count | Should -Be 1
($out.resources[0].properties.psobject.properties | Measure-Object).count | Should -Be 1
$out.resources[0].properties.passwordAuthentication | Should -Be 'no'
}

It '<command> with _includeDefaults specified works' -TestCases @(
@{ command = 'get'; includeDefaults = $false }
@{ command = 'export'; includeDefaults = $true }
) {
param($command, $includeDefaults)
$filepath = Join-Path $TestDrive 'test_sshd_config'
$input = @"
`$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json
metadata:
Microsoft.DSC:
securityContext: elevated
resources:
- name: sshdconfig
type: Microsoft.OpenSSH.SSHD/sshd_config
properties:
_includeDefaults: $includeDefaults
_metadata:
filepath: $filepath
"@
$out = dsc config $command -i "$input" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
if ($command -eq 'export') {
$out.resources.count | Should -Be 1
$out.resources[0].properties.loglevel | Should -Be 'debug3'
$out.resources[0].properties.port | Should -Be 22
$out.resources[0].properties._inheritedDefaults | Should -BeNullOrEmpty
} else {
$out.results.count | Should -Be 1
($out.results.result.actualState.psobject.properties | Measure-Object).count | Should -Be 2
$out.results.result.actualState.loglevel | Should -Be 'debug3'
$out.results.result.actualState._inheritedDefaults | Should -BeNullOrEmpty
}
}

Context 'Surface a default value that has been set in file' {
BeforeAll {
"Port 22" | Set-Content -Path $TestDrive/test_sshd_config
}

It '<command> works' -TestCases @(
@{ command = 'get' }
@{ command = 'export' }
) {
param($command)
$out = dsc config $command -i "$yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
if ($command -eq 'export') {
$out.resources.count | Should -Be 1
$out.resources[0].properties | Should -Not -BeNullOrEmpty
$out.resources[0].properties.port[0] | Should -Be 22
$out.resources[0].properties.passwordauthentication | Should -BeNullOrEmpty
$out.resources[0].properties._inheritedDefaults | Should -BeNullOrEmpty
} else {
$out.results.count | Should -Be 1
$out.results.result.actualState | Should -Not -BeNullOrEmpty
$out.results.result.actualState.port | Should -Be 22
$out.results.result.actualState.passwordAuthentication | Should -Be 'yes'
$out.results.result.actualState._inheritedDefaults | Should -Not -Contain 'port'
}
}
}
}
58 changes: 57 additions & 1 deletion sshdconfig/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sshdconfig/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ rust-i18n = { version = "3.1" }
schemars = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
tempfile = "3.8"
thiserror = { version = "2.0" }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["ansi", "env-filter", "json"] }
Expand Down
12 changes: 9 additions & 3 deletions sshdconfig/locales/en-us.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
_version = 1

[args]
getInput = "input to get for sshd_config or default shell settings"
exportInput = "input to export from sshd_config"
setInput = "input to set in sshd_config"

[error]
command = "Command"
invalidInput = "Invalid Input"
io = "IO"
json = "JSON"
language = "Language"
notImplemented = "Not Implemented"
parser = "Parser"
parseInt = "Parse Integer"
persist = "Persist"
registry = "Registry"

[get]
Expand All @@ -19,11 +22,10 @@ defaultShellCmdOptionMustBeString = "cmdOption must be a string"
defaultShellEscapeArgsMustBe0Or1 = "'%{input}' must be a 0 or 1"
defaultShellEscapeArgsMustBeDWord = "escapeArguments must be a DWord"
defaultShellMustBeString = "shell must be a string"
notImplemented = "get not yet implemented for Microsoft.OpenSSH.SSHD/sshd_config"
windowsOnly = "Microsoft.OpenSSH.SSHD/Windows is only applicable to Windows"

[main]
export = "Export"
export = "Export command: %{input}"
schema = "Schema command:"
set = "Set command: '%{input}'"

Expand Down Expand Up @@ -51,5 +53,9 @@ shellPathDoesNotExist = "shell path does not exist: '%{shell}'"
shellPathMustNotBeRelative = "shell path must not be relative"

[util]
includeDefaultsMustBeBoolean = "_includeDefaults must be true or false"
inputMustBeEmpty = "get command does not support filtering based on input settings"
sshdConfigNotFound = "sshd_config not found at path: '%{path}'"
sshdConfigReadFailed = "failed to read sshd_config at path: '%{path}'"
sshdElevation = "elevated security context required"
tracingInitError = "Failed to initialize tracing"
11 changes: 8 additions & 3 deletions sshdconfig/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ pub struct Args {

#[derive(Subcommand)]
pub enum Command {
/// Get default shell, eventually to be used for `sshd_config` and repeatable keywords
/// Get default shell and `sshd_config`, eventually to be used for repeatable keywords
Get {
#[clap(short = 'i', long, help = t!("args.getInput").to_string())]
input: Option<String>,
#[clap(short = 's', long, hide = true)]
setting: Setting,
},
Expand All @@ -24,8 +26,11 @@ pub enum Command {
#[clap(short = 'i', long, help = t!("args.setInput").to_string())]
input: String
},
/// Export `sshd_config`
Export,
/// Export `sshd_config`, eventually to be used for repeatable keywords
Export {
#[clap(short = 'i', long, help = t!("args.exportInput").to_string())]
input: Option<String>
},
Schema {
// Used to inform which schema to generate
#[clap(short = 's', long, hide = true)]
Expand Down
7 changes: 5 additions & 2 deletions sshdconfig/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

use rust_i18n::t;
use tempfile::PersistError;
use thiserror::Error;

#[derive(Debug, Error)]
Expand All @@ -10,16 +11,18 @@ pub enum SshdConfigError {
CommandError(String),
#[error("{t}: {0}", t = t!("error.invalidInput"))]
InvalidInput(String),
#[error("{t}: {0}", t = t!("error.io"))]
IOError(#[from] std::io::Error),
#[error("{t}: {0}", t = t!("error.json"))]
Json(#[from] serde_json::Error),
#[error("{t}: {0}", t = t!("error.language"))]
LanguageError(#[from] tree_sitter::LanguageError),
#[error("{t}: {0}", t = t!("error.notImplemented"))]
NotImplemented(String),
#[error("{t}: {0}", t = t!("error.parser"))]
ParserError(String),
#[error("{t}: {0}", t = t!("error.parseInt"))]
ParseIntError(#[from] std::num::ParseIntError),
#[error("{t}: {0}", t = t!("error.persist"))]
PersistError(#[from] PersistError),
#[cfg(windows)]
#[error("{t}: {0}", t = t!("error.registry"))]
RegistryError(#[from] registry_lib::error::RegistryError),
Expand Down
19 changes: 0 additions & 19 deletions sshdconfig/src/export.rs

This file was deleted.

Loading
Loading