Skip to content

sshdconfig: add tracing and update constants #958

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 7 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
47 changes: 43 additions & 4 deletions sshdconfig/Cargo.lock

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

3 changes: 2 additions & 1 deletion sshdconfig/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ lto = true
atty = { version = "0.2" }
chrono = { version = "0.4" }
clap = { version = "4.5", features = ["derive"] }
crossterm = { version = "0.27" }
rust-i18n = { version = "3.1" }
schemars = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
thiserror = { version = "2.0" }
tracing = "0.1.37"
tracing-subscriber = "0.3.17"
tracing-subscriber = { version = "0.3.17", features = ["ansi", "env-filter", "json"] }
tree-sitter = "0.25"
tree-sitter-rust = "0.24"
tree-sitter-ssh-server-config = { path = "../tree-sitter-ssh-server-config" }
Expand Down
14 changes: 8 additions & 6 deletions sshdconfig/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,27 @@ 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"

[set]
failedToParseInput = "failed to parse input as DefaultShell with error: '%{error}'"
shellPathDoesNotExist = "shell path does not exist: '%{shell}'"
shellPathMustNotBeRelative = "shell path must not be relative"

[parser]
failedToParse = "failed to parse: '%{input}'"
failedToParseAsArray = "value is not an array"
failedToParseChildNode = "failed to parse child node: '%{input}'"
failedToParseNode = "failed to parse '%{input}'"
failedToParseRoot = "failed to parse root: '%{input}'"
invalidConfig = "invalid config: '%{input}'"
invalidMultiArgNode = "multi-arg node '%{input}' is not valid"
invalidValue = "operator is an invalid value for node"
keyNotFound = "key '%{key}' not found"
keyNotRepeatable = "key '%{key}' is not repeatable"
missingKeyInChildNode = "missing key in child node: '%{input}'"
missingValueInChildNode = "missing value in child node: '%{input}'"
missingKeyInChildNode = "missing key in child node: '%{input}'"
unknownNode = "unknown node: '%{kind}'"
unknownNodeType = "unknown node type: '%{node}'"

[set]
failedToParseInput = "failed to parse input as DefaultShell with error: '%{error}'"
shellPathDoesNotExist = "shell path does not exist: '%{shell}'"
shellPathMustNotBeRelative = "shell path must not be relative"

[util]
sshdElevation = "elevated security context required"
tracingInitError = "Failed to initialize tracing"
5 changes: 4 additions & 1 deletion sshdconfig/src/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ use {
crate::metadata::windows::{DEFAULT_SHELL, DEFAULT_SHELL_CMD_OPTION, DEFAULT_SHELL_ESCAPE_ARGS, REGISTRY_PATH},
};

use rust_i18n::t;
use tracing::debug;

use crate::args::Setting;
use crate::error::SshdConfigError;
use rust_i18n::t;

/// 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> {
debug!("Get setting: {:?}", setting);
match *setting {
Setting::SshdConfig => Err(SshdConfigError::NotImplemented(t!("get.notImplemented").to_string())),
Setting::WindowsGlobal => get_default_shell()
Expand Down
32 changes: 26 additions & 6 deletions sshdconfig/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
use clap::{Parser};
use rust_i18n::i18n;
use schemars::schema_for;
use std::process::exit;
use tracing::{debug, error};

use args::{Args, Command, DefaultShell, Setting};
use export::invoke_export;
use get::invoke_get;
use parser::SshdConfigParser;
use set::invoke_set;
use util::enable_tracing;

mod args;
mod error;
Expand All @@ -22,14 +25,25 @@ mod util;

i18n!("locales", fallback = "en-us");

const EXIT_SUCCESS: i32 = 0;
const EXIT_FAILURE: i32 = 1;

fn main() {
enable_tracing();

let args = Args::parse();

let result = match &args.command {
Command::Export => invoke_export(),
Command::Get { setting } => invoke_get(setting),
Command::Set { input } => invoke_set(input),
Command::Export => {
debug!("Export command");
Copy link
Member

Choose a reason for hiding this comment

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

should i18n all strings

invoke_export()
},
Command::Get { setting } => {
debug!("Get command: setting={:?}", setting);
invoke_get(setting)
},
Command::Schema { setting } => {
debug!("Schema command: setting={:?}", setting);
let schema = match setting {
Setting::SshdConfig => {
schema_for!(SshdConfigParser)
Expand All @@ -40,11 +54,17 @@ fn main() {
};
println!("{}", serde_json::to_string(&schema).unwrap());
Ok(())
}
},
Command::Set { input } => {
debug!("Set command: input={}", input);
invoke_set(input)
},
};

if let Err(e) = result {
eprintln!("{e}");
std::process::exit(1);
error!("{e}");
exit(EXIT_FAILURE);
}

exit(EXIT_SUCCESS);
}
56 changes: 29 additions & 27 deletions sshdconfig/src/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use clap::ValueEnum;

// TODO: ensure lists are complete

// keywords that can be repeated over multiple lines and should be represented as arrays
pub const REPEATABLE_KEYWORDS: [&str; 6] = [
"hostkey",
"include",
"listenaddress",
"port",
"setenv",
"subsystem"
];

#[derive(Clone, Debug, Eq, PartialEq, ValueEnum)]
pub enum RepeatableKeyword {
HostKey,
Include,
ListenAddress,
Port,
SetEnv,
Subsystem,
}

// keywords that can have multiple argments per line and should be represented as arrays
// but cannot be repeated over multiple lines, as subsequent entries are ignored
pub const MULTI_ARG_KEYWORDS: [&str; 7] = [
// keywords that can have multiple argments per line but cannot be repeated over multiple lines,
// as subsequent entries are ignored, should be represented as arrays
pub const MULTI_ARG_KEYWORDS: [&str; 16] = [
"authenticationmethods",
"authorizedkeysfile",
"casignaturealgorithms",
"channeltimeout",
"ciphers",
"hostbasedacceptedalgorithms",
"hostkeyalgorithms",
"ipqos",
"kexalgorithms",
"macs",
"permitlisten",
"permitopen",
"permituserenvironment",
"persourcepenalties",
"persourcepenaltyexemptlist",
"pubkeyacceptedalgorithms"
];

// keywords that can be repeated over multiple lines and should be represented as arrays.
// note that some keywords can be both multi-arg and repeatable, in which case they only need to be listed here
pub const REPEATABLE_KEYWORDS: [&str; 12] = [
"acceptenv",
"allowgroups",
"allowusers",
"denygroups",
"denyusers",
"hostkey",
"include",
"listenaddress",
"match",
"port",
"setenv",
"subsystem"
];

#[cfg(windows)]
pub mod windows {
pub const REGISTRY_PATH: &str = "HKLM\\SOFTWARE\\OpenSSH";
Expand Down
26 changes: 23 additions & 3 deletions sshdconfig/src/parser.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use rust_i18n::t;
use schemars::JsonSchema;
use serde_json::{Map, Value};
use tracing::debug;
use tree_sitter::Parser;

use crate::error::SshdConfigError;
use crate::metadata::{MULTI_ARG_KEYWORDS, REPEATABLE_KEYWORDS};
use rust_i18n::t;

#[derive(Debug, JsonSchema)]
pub struct SshdConfigParser {
Expand Down Expand Up @@ -77,6 +78,7 @@ impl SshdConfigParser {
t!("parser.failedToParseChildNode", input = input).to_string()
));
};
debug!("Parsing keyword: {text}");
if REPEATABLE_KEYWORDS.contains(&text) {
is_repeatable = true;
is_vec = true;
Expand All @@ -92,6 +94,7 @@ impl SshdConfigParser {
}
if node.kind() == "arguments" {
value = parse_arguments_node(node, input, input_bytes, is_vec)?;
debug!("Parsed argument value: {:?}", value);
}
}
if let Some(key) = key {
Expand Down Expand Up @@ -140,8 +143,14 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: &
let mut cursor = arg_node.walk();
let mut vec: Vec<Value> = Vec::new();
let mut value = Value::Null;
for node in arg_node.named_children(&mut cursor) {

// if there is more than one argument, but a vector is not expected for the keyword, throw an error
let children: Vec<_> = arg_node.named_children(&mut cursor).collect();
if children.len() > 1 && !is_vec {
return Err(SshdConfigError::ParserError(t!("parser.invalidMultiArgNode", input = input).to_string()));
}

for node in children {
if node.is_error() {
return Err(SshdConfigError::ParserError(t!("parser.failedToParseChildNode", input = input).to_string()));
}
Expand All @@ -152,7 +161,7 @@ fn parse_arguments_node(arg_node: tree_sitter::Node, input: &str, input_bytes: &
t!("parser.failedToParseNode", input = input).to_string()
));
};
Value::String(arg.to_string())
Value::String(arg.trim().to_string())
}
"number" => {
let Ok(arg) = node.utf8_text(input_bytes) else {
Expand Down Expand Up @@ -224,6 +233,17 @@ mod tests {
assert_eq!(result.get("hostkeyalgorithms").unwrap(), &Value::Array(expected));
}

#[test]
fn multiarg_string_with_spaces_keyword() {
let input = "allowgroups administrators \"openssh users\"\r\n";
let result: Map<String, Value> = parse_text_to_map(input).unwrap();
let expected = vec![
Value::String("administrators".to_string()),
Value::String("openssh users".to_string()),
];
assert_eq!(result.get("allowgroups").unwrap(), &Value::Array(expected));
}

#[test]
fn bool_keyword() {
let input = "printmotd yes\r\n";
Expand Down
Loading
Loading