Skip to content

Commit 6da122a

Browse files
committed
fix Echo to respect secure types
1 parent 5c2f0e4 commit 6da122a

File tree

9 files changed

+88
-38
lines changed

9 files changed

+88
-38
lines changed

dsc/examples/secure_parameters.dsc.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ parameters:
44
type: secureString
55
myObject:
66
type: secureObject
7+
showSecrets:
8+
type: bool
9+
defaultValue: false
710
resources:
811
- name: Echo 1
912
type: Microsoft.DSC.Debug/Echo
1013
properties:
1114
output: "[parameters('myString')]"
15+
showSecrets: "[parameters('showSecrets')]"
1216
- name: Echo 2
1317
type: Microsoft.DSC.Debug/Echo
1418
properties:
1519
output: "[parameters('myObject').myProperty]"
20+
showSecrets: "[parameters('showSecrets')]"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
parameters:
2+
myString: mySecret
3+
myObject:
4+
myProperty: mySecretProperty
5+
showSecrets: true

dsc_lib/src/configure/mod.rs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
use crate::configure::config_doc::{ExecutionKind, Metadata, Resource};
55
use crate::configure::context::{Context, ProcessMode};
6-
use crate::configure::parameters::is_secure_value;
76
use crate::configure::{config_doc::RestartRequired, parameters::Input};
87
use crate::discovery::discovery_trait::DiscoveryFilter;
98
use crate::dscerror::DscError;
@@ -215,19 +214,7 @@ fn add_metadata(dsc_resource: &DscResource, mut properties: Option<Map<String, V
215214

216215
match properties {
217216
Some(properties) => {
218-
let mut unsecure_properties = Map::new();
219-
for (key, value) in properties {
220-
if is_secure_value(&value) {
221-
if value.get("secureString").is_some() {
222-
unsecure_properties.insert(key.clone(), "<secureString>".into());
223-
} else if value.get("secureObject").is_some() {
224-
unsecure_properties.insert(key.clone(), "<secureObject>".into());
225-
}
226-
} else {
227-
unsecure_properties.insert(key, value);
228-
}
229-
}
230-
Ok(serde_json::to_string(&unsecure_properties)?)
217+
Ok(serde_json::to_string(&properties)?)
231218
},
232219
_ => {
233220
Ok(String::new())

dsc_lib/src/configure/parameters.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub struct SecureString {
1919

2020
impl Display for SecureString {
2121
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22-
write!(f, "<secureString>")
22+
write!(f, "<secureValue>")
2323
}
2424
}
2525

@@ -31,7 +31,7 @@ pub struct SecureObject {
3131

3232
impl Display for SecureObject {
3333
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34-
write!(f, "<secureObject>")
34+
write!(f, "<secureValue>")
3535
}
3636
}
3737

dsc_lib/src/dscresources/command_resource.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use serde_json::{Map, Value};
99
use std::{collections::HashMap, env, process::Stdio};
1010
use crate::configure::{config_doc::ExecutionKind, config_result::{ResourceGetResult, ResourceTestResult}};
1111
use crate::dscerror::DscError;
12-
use super::{dscresource::get_diff, invoke_result::{ExportResult, GetResult, ResolveResult, SetResult, TestResult, ValidateResult, ResourceGetResponse, ResourceSetResponse, ResourceTestResponse, get_in_desired_state}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}};
12+
use super::{dscresource::{get_diff, redact}, invoke_result::{ExportResult, GetResult, ResolveResult, SetResult, TestResult, ValidateResult, ResourceGetResponse, ResourceSetResponse, ResourceTestResponse, get_in_desired_state}, resource_manifest::{ArgKind, InputKind, Kind, ResourceManifest, ReturnKind, SchemaKind}};
1313
use tracing::{error, warn, info, debug, trace};
1414
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, process::Command};
1515

@@ -286,7 +286,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
286286
return Ok(TestResult::Group(group_test_response));
287287
}
288288

289-
let expected_value: Value = serde_json::from_str(expected)?;
289+
let mut expected_value: Value = serde_json::from_str(expected)?;
290290
match test.returns {
291291
Some(ReturnKind::State) => {
292292
let actual_value: Value = match serde_json::from_str(&stdout){
@@ -297,6 +297,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
297297
};
298298
let in_desired_state = get_desired_state(&actual_value)?;
299299
let diff_properties = get_diff(&expected_value, &actual_value);
300+
expected_value = redact(&expected_value);
300301
Ok(TestResult::Resource(ResourceTestResponse {
301302
desired_state: expected_value,
302303
actual_state: actual_value,
@@ -315,6 +316,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
315316
return Err(DscError::Command(resource.resource_type.clone(), exit_code, t!("dscresources.commandResource.testNoDiff").to_string()));
316317
};
317318
let diff_properties: Vec<String> = serde_json::from_str(diff_properties)?;
319+
expected_value = redact(&expected_value);
318320
let in_desired_state = get_desired_state(&actual_value)?;
319321
Ok(TestResult::Resource(ResourceTestResponse {
320322
desired_state: expected_value,
@@ -339,6 +341,7 @@ pub fn invoke_test(resource: &ResourceManifest, cwd: &str, expected: &str) -> Re
339341
}
340342
};
341343
let diff_properties = get_diff( &expected_value, &actual_state);
344+
expected_value = redact(&expected_value);
342345
Ok(TestResult::Resource(ResourceTestResponse {
343346
desired_state: expected_value,
344347
actual_state,

dsc_lib/src/dscresources/dscresource.rs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
use crate::{configure::{config_doc::{Configuration, ExecutionKind, Resource}, Configurator, context::ProcessMode}, dscresources::resource_manifest::Kind};
4+
use crate::{configure::{Configurator, config_doc::{Configuration, ExecutionKind, Resource}, context::ProcessMode, parameters::is_secure_value}, dscresources::resource_manifest::Kind};
55
use crate::dscresources::invoke_result::{ResourceGetResponse, ResourceSetResponse};
66
use dscerror::DscError;
77
use jsonschema::Validator;
@@ -311,7 +311,7 @@ impl Invoke for DscResource {
311311
let TestResult::Resource(ref resource_result) = result.results[0].result else {
312312
return Err(DscError::Operation(t!("dscresources.dscresource.invokeReturnedWrongResult", operation = "test", resource = self.type_name).to_string()));
313313
};
314-
let desired_state = resource_result.desired_state
314+
let mut desired_state = resource_result.desired_state
315315
.as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "desiredState", property_type = "object").to_string()))?
316316
.get("resources").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "resources").to_string()))?
317317
.as_array().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "resources", property_type = "array").to_string()))?[0]
@@ -324,6 +324,7 @@ impl Invoke for DscResource {
324324
.as_object().ok_or(DscError::Operation(t!("dscresources.dscresource.propertyIncorrectType", property = "result", property_type = "object").to_string()))?
325325
.get("properties").ok_or(DscError::Operation(t!("dscresources.dscresource.propertyNotFound", property = "properties").to_string()))?.clone();
326326
let diff_properties = get_diff(&desired_state, &actual_state);
327+
desired_state = redact(&desired_state);
327328
let test_result = TestResult::Resource(ResourceTestResponse {
328329
desired_state,
329330
actual_state,
@@ -346,7 +347,7 @@ impl Invoke for DscResource {
346347
let resource_manifest = import_manifest(manifest.clone())?;
347348
if resource_manifest.test.is_none() {
348349
let get_result = self.get(expected)?;
349-
let desired_state = serde_json::from_str(expected)?;
350+
let mut desired_state = serde_json::from_str(expected)?;
350351
let actual_state = match get_result {
351352
GetResult::Group(results) => {
352353
let mut result_array: Vec<Value> = Vec::new();
@@ -360,6 +361,7 @@ impl Invoke for DscResource {
360361
}
361362
};
362363
let diff_properties = get_diff( &desired_state, &actual_state);
364+
desired_state = redact(&desired_state);
363365
let test_result = TestResult::Resource(ResourceTestResponse {
364366
desired_state,
365367
actual_state,
@@ -491,6 +493,37 @@ pub fn get_well_known_properties() -> HashMap<String, Value> {
491493
])
492494
}
493495

496+
/// Checks if the JSON value is sensitive and should be redacted
497+
///
498+
/// # Arguments
499+
///
500+
/// * `value` - The JSON value to check
501+
///
502+
/// # Returns
503+
///
504+
/// Original value if not sensitive, otherwise a redacted value
505+
pub fn redact(value: &Value) -> Value {
506+
trace!("Redacting value: {value}");
507+
if is_secure_value(value) {
508+
return Value::String("<secureValue>".to_string());
509+
}
510+
511+
if let Some(map) = value.as_object() {
512+
let mut new_map = Map::new();
513+
for (key, val) in map {
514+
new_map.insert(key.clone(), redact(val));
515+
}
516+
return Value::Object(new_map);
517+
}
518+
519+
if let Some(array) = value.as_array() {
520+
let new_array: Vec<Value> = array.iter().map(|val| redact(val)).collect();
521+
return Value::Array(new_array);
522+
}
523+
524+
value.clone()
525+
}
526+
494527
#[must_use]
495528
/// Performs a comparison of two JSON Values if the expected is a strict subset of the actual
496529
///
@@ -524,6 +557,11 @@ pub fn get_diff(expected: &Value, actual: &Value) -> Vec<String> {
524557
}
525558

526559
for (key, value) in &*map {
560+
if is_secure_value(&value) {
561+
// skip secure values as they are not comparable
562+
continue;
563+
}
564+
527565
if value.is_object() {
528566
let sub_diff = get_diff(value, &actual[key]);
529567
if !sub_diff.is_empty() {

dsc_lib/src/parser/expressions.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,21 @@ impl Expression {
146146
if !object.contains_key(member) {
147147
return Err(DscError::Parser(t!("parser.expression.memberNameNotFound", member = member).to_string()));
148148
}
149-
value = convert_to_secure(&object[member]);
149+
if is_secure {
150+
value = convert_to_secure(&object[member]);
151+
} else {
152+
value = object[member].clone();
153+
}
150154
} else {
151155
return Err(DscError::Parser(t!("parser.expression.accessOnNonObject").to_string()));
152156
}
153157
},
154158
Accessor::Index(index_value) => {
155-
index = convert_to_secure(index_value);
159+
if is_secure {
160+
index = convert_to_secure(index_value);
161+
} else {
162+
index = index_value.clone();
163+
}
156164
},
157165
Accessor::IndexExpression(expression) => {
158166
index = expression.invoke(function_dispatcher, context)?;
@@ -169,7 +177,11 @@ impl Expression {
169177
if index >= array.len() {
170178
return Err(DscError::Parser(t!("parser.expression.indexOutOfBounds").to_string()));
171179
}
172-
value = convert_to_secure(&array[index]);
180+
if is_secure {
181+
value = convert_to_secure(&array[index]);
182+
} else {
183+
value = array[index].clone();
184+
}
173185
} else {
174186
return Err(DscError::Parser(t!("parser.expression.indexOnNonArray").to_string()));
175187
}
@@ -185,10 +197,6 @@ impl Expression {
185197
}
186198

187199
fn convert_to_secure(value: &Value) -> Value {
188-
if !is_secure_value(value) {
189-
return value.clone();
190-
}
191-
192200
if let Some(string) = value.as_str() {
193201
let secure_string = crate::configure::parameters::SecureString {
194202
secure_string: string.to_string(),

dscecho/src/echo.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,30 +14,35 @@ pub enum Output {
1414
Bool(bool),
1515
#[serde(rename = "number")]
1616
Number(i64),
17-
#[serde(rename = "object")]
18-
Object(Value),
1917
#[serde(rename = "secureObject")]
2018
SecureObject(SecureObject),
2119
#[serde(rename = "secureString")]
2220
SecureString(SecureString),
2321
#[serde(rename = "string")]
2422
String(String),
23+
// Object has to be last so it doesn't get matched first
24+
#[serde(rename = "object")]
25+
Object(Value),
2526
}
2627

2728
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
2829
#[serde(deny_unknown_fields)]
2930
pub struct SecureString {
31+
#[serde(rename = "secureString")]
3032
pub secure_string: String,
3133
}
3234

3335
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
3436
#[serde(deny_unknown_fields)]
3537
pub struct SecureObject {
38+
#[serde(rename = "secureObject")]
3639
pub secure_object: Value,
3740
}
3841

3942
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
4043
#[serde(deny_unknown_fields)]
4144
pub struct Echo {
4245
pub output: Output,
46+
#[serde(rename = "showSecrets", skip_serializing_if = "Option::is_none")]
47+
pub show_secrets: Option<bool>,
4348
}

dscecho/src/main.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,13 @@ fn main() {
2323
std::process::exit(1);
2424
}
2525
};
26-
match echo.output {
27-
Output::SecureObject(_) => {
28-
echo.output = Output::String("<secureObject>".to_string());
29-
},
30-
Output::SecureString(_) => {
31-
echo.output = Output::String("<secureString>".to_string());
32-
},
33-
_ => {}
26+
if echo.show_secrets != Some(true) {
27+
match &echo.output {
28+
Output::SecureString(_) | Output::SecureObject(_) => {
29+
echo.output = Output::String("<secureValue>".to_string());
30+
},
31+
_ => {}
32+
}
3433
}
3534
let json = serde_json::to_string(&echo).unwrap();
3635
println!("{json}");

0 commit comments

Comments
 (0)