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 21 commits into
base: main
Choose a base branch
from
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
89 changes: 89 additions & 0 deletions dsc/tests/dsc_sshdconfig.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# 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 {
$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:
'@
# set a non-default value in a temporary sshd_config file
"LogLevel Debug3" | Set-Content -Path $TestDrive/test_sshd_config
}

It 'Export works' {
$out = dsc config export -i "$yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
$out.resources.count | Should -Be 1
$out.resources[0].properties | Should -Not -BeNullOrEmpty
$out.resources[0].properties.port[0] | Should -Be 22
}

It 'Get works'{
$out = dsc config get -i "$yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
$out.results.count | Should -Be 1
$out.results.metadata.includeDefaults | Should -Be $true
$out.results.result.actualState | Should -Not -BeNullOrEmpty
$out.results.result.actualState.port | Should -Be 22
$out.results.result.actualState.passwordAuthentication | Should -Be 'yes'
}

It 'Get with a specific setting works' {
$get_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: 'no'
'@
$out = dsc config get -i "$get_yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
$out.results.count | Should -Be 1
($out.results.result.actualState.psobject.properties | measure-object).count | Should -Be 1
$out.results.result.actualState.passwordauthentication | Should -Be 'yes'
}

It 'Get with defaults excluded works' {
$filepath = Join-Path $TestDrive 'test_sshd_config'
$get_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:
includeDefaults: false
filepath: $filepath
"@
$out = dsc config get -i "$get_yaml" | ConvertFrom-Json -Depth 10
$LASTEXITCODE | Should -Be 0
$out.results.count | Should -Be 1
$out.results.metadata.includeDefaults | Should -Be $false
$out.results.result.actualState.count | Should -Be 1
$out.results.result.actualState.port | Should -Not -Be 22
$out.results.result.actualState.loglevel | Should -Be 'debug3'
}
}
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
5 changes: 3 additions & 2 deletions sshdconfig/locales/en-us.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
_version = 1

[args]
getInput = "input to get 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,7 +21,6 @@ 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]
Expand Down
2 changes: 2 additions & 0 deletions sshdconfig/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub struct Args {
pub enum Command {
/// Get default shell, eventually to be used for `sshd_config` and 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 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: 12 additions & 7 deletions sshdconfig/src/export.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use serde_json::{Map, Value};

use crate::error::SshdConfigError;
use crate::inputs::SshdCommandArgs;
use crate::parser::parse_text_to_map;
use crate::util::invoke_sshd_config_validation;

/// Invoke the export command.
/// Invoke the export command and return a map.
///
/// # Errors
///
/// This function will return an error if the command cannot invoke sshd -T, parse the return, or convert it to json.
pub fn invoke_export() -> Result<(), SshdConfigError> {
let sshd_config_text = invoke_sshd_config_validation()?;
let sshd_config: serde_json::Map<String, serde_json::Value> = parse_text_to_map(&sshd_config_text)?;
let json = serde_json::to_string(&sshd_config)?;
println!("{json}");
Ok(())
///
/// # Returns
///
/// This function will return `Ok(Map<String, Value>)` if the export is successful.
pub fn invoke_export(sshd_args: Option<SshdCommandArgs>) -> Result<Map<String, Value>, SshdConfigError> {
let sshd_config_text = invoke_sshd_config_validation(sshd_args)?;
let sshd_config: Map<String, Value> = parse_text_to_map(&sshd_config_text)?;
Ok(sshd_config)
}
44 changes: 41 additions & 3 deletions sshdconfig/src/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,27 @@ use {
};

use rust_i18n::t;
use serde_json::{Map, Value};
use tracing::debug;

use crate::args::Setting;
use crate::error::SshdConfigError;
use crate::export::invoke_export;
use crate::util::{extract_metadata_from_input, extract_sshd_defaults};

/// Invoke the get command.
///
/// # Errors
///
/// This function will return an error if the desired settings cannot be retrieved.
pub fn invoke_get(setting: &Setting) -> Result<(), SshdConfigError> {
pub fn invoke_get(input: Option<&String>, setting: &Setting) -> Result<Map<String, Value>, SshdConfigError> {
debug!("{}: {:?}", t!("get.debugSetting").to_string(), setting);
match *setting {
Setting::SshdConfig => Err(SshdConfigError::NotImplemented(t!("get.notImplemented").to_string())),
Setting::WindowsGlobal => get_default_shell()
Setting::SshdConfig => get_sshd_settings(input),
Setting::WindowsGlobal => {
get_default_shell()?;
Ok(Map::new())
}
}
}

Expand Down Expand Up @@ -82,3 +88,35 @@ fn get_default_shell() -> Result<(), SshdConfigError> {
fn get_default_shell() -> Result<(), SshdConfigError> {
Err(SshdConfigError::InvalidInput(t!("get.windowsOnly").to_string()))
}

fn get_sshd_settings(input: Option<&String>) -> Result<Map<String, Value>, SshdConfigError> {
let cmd_info = extract_metadata_from_input(input)?;
let mut result = invoke_export(cmd_info.sshd_args)?;

if !cmd_info.metadata.include_defaults {
let defaults = extract_sshd_defaults()?;
// Filter result based on default settings.
// If a value in result is equal to the default, it will be excluded.
// Note that this excludes all defaults, even if they are explicitly set in sshd_config.
result.retain(|key, value| {
if let Some(default) = defaults.get(key) {
default != value
} else {
true
}
});
}

if !cmd_info.input.is_empty() {
// Filter result based on the keys provided in the input JSON.
// If a provided key is not found in the result, its value is null.
result.retain(|key, _| cmd_info.input.contains_key(key));
for key in cmd_info.input.keys() {
result.entry(key.clone()).or_insert(Value::Null);
}
}

// Add the _metadata field to the result
result.insert("_metadata".to_string(), serde_json::to_value(cmd_info.metadata)?);
Ok(result)
}
Loading
Loading