Skip to content
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

add schema validation #28

Merged
merged 1 commit into from
Mar 22, 2023
Merged
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
11 changes: 7 additions & 4 deletions dsc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,13 +376,16 @@ fn get_input(input: &Option<String>, stdin: &Option<String>) -> String {

#[cfg(debug_assertions)]
fn check_debug() {
if env::var("DEBUG_CONFIG").is_ok() {
eprintln!("attach debugger to pid {} and press any key to continue", std::process::id());
if env::var("DEBUG_DSC").is_ok() {
eprintln!("attach debugger to pid {} and press a key to continue", std::process::id());
loop {
let event = event::read().unwrap();
match event {
event::Event::Key(_key) => {
break;
event::Event::Key(key) => {
// workaround bug in 0.26+ https://github.com/crossterm-rs/crossterm/issues/752#issuecomment-1414909095
if key.kind == event::KeyEventKind::Press {
break;
}
}
_ => {
eprintln!("Unexpected event: {:?}", event);
Expand Down
11 changes: 11 additions & 0 deletions dsc/tests/dsc_get.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,15 @@ Describe 'config get tests' {
$output.actual_state.valueName | Should -BeExactly 'ProductName'
$output.actual_state.valueData.String | Should -Match 'Windows .*'
}

It 'invalid input is validated against schema' -Skip:(!$IsWindows) {
$json = @'
{
"keyPath": "HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion",
"Name": "ProductName"
}
'@
$json | dsc resource get -r registry
$LASTEXITCODE | Should -Be 2
}
}
1 change: 1 addition & 0 deletions dsc_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ edition = "2021"

[dependencies]
derive_builder ="0.12"
jsonschema = "0.17"
regex = "1.7"
reqwest = { version = "0.11", features = ["blocking"] }
schemars = { version = "0.8.12" }
Expand Down
13 changes: 8 additions & 5 deletions dsc_lib/src/dscerror.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ use thiserror::Error;

#[derive(Error, Debug)]
pub enum DscError {
#[error("HTTP status: {0}")]
HttpStatus(StatusCode),
#[error("Command: [{0}] {1}")]
Command(i32, String),

#[error("HTTP: {0}")]
Http(#[from] reqwest::Error),

#[error("HTTP status: {0}")]
HttpStatus(StatusCode),

#[error("IO: {0}")]
Io(#[from] std::io::Error),

Expand All @@ -18,9 +21,6 @@ pub enum DscError {
#[error("Manifest: {0}\nJSON: {1}")]
Manifest(String, serde_json::Error),

#[error("Command: [{0}] {1}")]
Command(i32, String),

#[error("Missing manifest: {0}")]
MissingManifest(String),

Expand All @@ -33,6 +33,9 @@ pub enum DscError {
#[error("Operation: {0}")]
Operation(String),

#[error("Schema: {0}")]
Schema(String),

#[error("Unknown: {code:?} {message:?}")]
Unknown {
code: i32,
Expand Down
29 changes: 29 additions & 0 deletions dsc_lib/src/dscresources/command_resource.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use jsonschema::JSONSchema;
use serde_json::Value;
use std::{process::Command, io::{Write, Read}, process::Stdio};

Expand All @@ -7,6 +8,8 @@ use super::{resource_manifest::{ResourceManifest, ReturnKind, SchemaKind}, invok
pub const EXIT_PROCESS_TERMINATED: i32 = 0x102;

pub fn invoke_get(resource: &ResourceManifest, filter: &str) -> Result<GetResult, DscError> {
verify_json(resource, filter)?;

let (exit_code, stdout, stderr) = invoke_command(&resource.get.executable, resource.get.args.clone().unwrap_or_default(), Some(filter))?;
if exit_code != 0 {
return Err(DscError::Command(exit_code, stderr.to_string()));
Expand All @@ -19,6 +22,8 @@ pub fn invoke_get(resource: &ResourceManifest, filter: &str) -> Result<GetResult
}

pub fn invoke_set(resource: &ResourceManifest, desired: &str) -> Result<SetResult, DscError> {
verify_json(resource, desired)?;

// if resource doesn't implement a pre-test, we execute test first to see if a set is needed
if !resource.set.pre_test.unwrap_or_default() {
let test_result = invoke_test(resource, desired)?;
Expand Down Expand Up @@ -83,6 +88,8 @@ pub fn invoke_set(resource: &ResourceManifest, desired: &str) -> Result<SetResul
}

pub fn invoke_test(resource: &ResourceManifest, expected: &str) -> Result<TestResult, DscError> {
verify_json(resource, expected)?;

let (exit_code, stdout, stderr) = invoke_command(&resource.test.executable, resource.test.args.clone().unwrap_or_default(), Some(expected))?;
if exit_code != 0 {
return Err(DscError::Command(exit_code, stderr.to_string()));
Expand Down Expand Up @@ -181,6 +188,28 @@ fn invoke_command(executable: &str, args: Vec<String>, input: Option<&str>) -> R
Ok((exit_code, stdout, stderr))
}

fn verify_json(resource: &ResourceManifest, json: &str) -> Result<(), DscError> {
let schema = invoke_schema(resource)?;
let schema: Value = serde_json::from_str(&schema)?;
let compiled_schema = match JSONSchema::compile(&schema) {
Ok(schema) => schema,
Err(e) => {
return Err(DscError::Schema(e.to_string()));
},
};
let json: Value = serde_json::from_str(json)?;
match compiled_schema.validate(&json) {
Ok(_) => return Ok(()),
Err(err) => {
let mut error = String::new();
for e in err {
error.push_str(&format!("{} ", e));
}
return Err(DscError::Schema(error));
},
};
}

fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {
let mut diff_properties: Vec<String> = Vec::new();
if expected.is_null() {
Expand Down
3 changes: 3 additions & 0 deletions dsc_lib/src/dscresources/invoke_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GetResult {
pub actual_state: Value,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SetResult {
pub before_state: Value,
pub after_state: Value,
pub changed_properties: Option<Vec<String>>,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct TestResult {
pub expected_state: Value,
pub actual_state: Value,
Expand Down
1 change: 1 addition & 0 deletions dsc_lib/src/dscresources/resource_manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ResourceManifest {
#[serde(rename = "manifestVersion")]
pub manifest_version: String,
Expand Down
4 changes: 2 additions & 2 deletions registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ lto = true

[dependencies]
atty = { version = "0.2" }
clap = { version = "3.2", features = ["derive"] }
crossterm = { version = "0.24.0" }
clap = { version = "4.1", features = ["derive"] }
crossterm = { version = "0.26" }
ntreg = { path = "../ntreg" }
ntstatuserror = { path = "../ntstatuserror" }
schemars = { version = "0.8.12" }
Expand Down
2 changes: 1 addition & 1 deletion registry/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub enum RegistryValueData {
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename = "Registry")]
#[serde(rename = "Registry", deny_unknown_fields)]
pub struct RegistryConfig {
#[serde(rename = "$id", skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
Expand Down