Skip to content
Draft
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
9 changes: 0 additions & 9 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ default-members = [
"lib/dsc-lib-registry",
"resources/runcommandonset",
"lib/dsc-lib-security_context",
"resources/dism_dsc",
"resources/sshdconfig",
"resources/WindowsUpdate",
"resources/windows_service",
Expand All @@ -57,8 +58,7 @@ default-members = [
"grammars/tree-sitter-dscexpression",
"grammars/tree-sitter-ssh-server-config",
"y2j",
"xtask",
"resources/dism_dsc"
"xtask"
]

[workspace.metadata.groups]
Expand Down
13 changes: 11 additions & 2 deletions dsc/tests/dsc_extension_secret.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Describe 'Tests for the secret() function and extensions' {
- name: Echo
type: Microsoft.DSC.Debug/Echo
properties:
showSecrets: true
output: "[secret('MySecret')]"
'@
$out = dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json
Expand All @@ -34,6 +35,7 @@ Describe 'Tests for the secret() function and extensions' {
- name: Echo
type: Microsoft.DSC.Debug/Echo
properties:
showSecrets: true
output: "[secret('DifferentSecret', 'VaultA')]"
'@
$out = dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json
Expand All @@ -49,6 +51,7 @@ Describe 'Tests for the secret() function and extensions' {
- name: Echo
type: Microsoft.DSC.Debug/Echo
properties:
showSecrets: true
output: "[secret('NonExistentSecret')]"
'@
dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json
Expand All @@ -64,6 +67,7 @@ Describe 'Tests for the secret() function and extensions' {
- name: Echo
type: Microsoft.DSC.Debug/Echo
properties:
showSecrets: true
output: "[secret('MySecret', 'NonExistentVault')]"
'@
dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json
Expand All @@ -79,6 +83,7 @@ Describe 'Tests for the secret() function and extensions' {
- name: Echo
type: Microsoft.DSC.Debug/Echo
properties:
showSecrets: true
output: "[secret('DuplicateSecret')]"
'@
dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json
Expand All @@ -94,6 +99,7 @@ Describe 'Tests for the secret() function and extensions' {
- name: Echo
type: Microsoft.DSC.Debug/Echo
properties:
showSecrets: true
output: "[secret('DuplicateSecret', 'Vault1')]"
'@
$out = dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json
Expand All @@ -109,6 +115,7 @@ Describe 'Tests for the secret() function and extensions' {
- name: Echo
type: Microsoft.DSC.Debug/Echo
properties:
showSecrets: true
output: "[secret('DuplicateSame')]"
'@
$out = dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json
Expand All @@ -124,6 +131,7 @@ Describe 'Tests for the secret() function and extensions' {
- name: Echo
type: Microsoft.DSC.Debug/Echo
properties:
showSecrets: true
output: "[secret('MultiLine')]"
'@
dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json
Expand All @@ -147,9 +155,9 @@ Describe 'Tests for the secret() function and extensions' {
showSecrets: true
'@
$out = dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
$LASTEXITCODE | Should -Be 0 -Because (Get-Content -Raw -Path $TestDrive/error.log)
$out.results.Count | Should -Be 1
$out.results[0].result.actualState.Output.secureString | Should -BeExactly 'Hello'
$out.results[0].result.actualState.Output | Should -BeExactly 'Hello' -Because (Get-Content -Raw -Path $TestDrive/error.log)
}

It 'Allows to pass in secret() through variables' {
Expand All @@ -162,6 +170,7 @@ Describe 'Tests for the secret() function and extensions' {
type: Microsoft.DSC.Debug/Echo
properties:
output: "[variables('myString')]"
showSecrets: true
'@
$out = dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0
Expand Down
20 changes: 10 additions & 10 deletions dsc/tests/dsc_parameters.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -316,18 +316,18 @@ Describe 'Parameters tests' {
$LASTEXITCODE | Should -Be 0
if ($operation -eq 'export') {
$out.resources.Count | Should -Be 4 -Because ($out | ConvertTo-Json -Dep 10 | Out-String)
$out.resources[0].properties.output.secureString | Should -BeExactly 'mySecret'
$out.resources[1].properties.output.secureString | Should -BeExactly 'mySecretProperty'
$out.resources[2].properties.output[0].secureString | Should -BeExactly 'item1'
$out.resources[2].properties.output[1].secureString | Should -BeExactly 'item2'
$out.resources[3].properties.output.secureObject.secureString | Should -BeExactly 'item2'
$out.resources[0].properties.output | Should -BeExactly 'mySecret'
$out.resources[1].properties.output | Should -BeExactly 'mySecretProperty'
$out.resources[2].properties.output[0] | Should -BeExactly 'item1'
$out.resources[2].properties.output[1] | Should -BeExactly 'item2'
$out.resources[3].properties.output | Should -BeExactly 'item2'
} else {
$out.results.Count | Should -Be 4
$out.results[0].result.$property.output.secureString | Should -BeExactly 'mySecret' -Because ($out | ConvertTo-Json -Dep 10 | Out-String)
$out.results[1].result.$property.output.secureString | Should -BeExactly 'mySecretProperty'
$out.results[2].result.$property.output[0].secureString | Should -BeExactly 'item1'
$out.results[2].result.$property.output[1].secureString | Should -BeExactly 'item2'
$out.results[3].result.$property.output.secureObject.secureString | Should -BeExactly 'item2'
$out.results[0].result.$property.output | Should -BeExactly 'mySecret' -Because ($out | ConvertTo-Json -Dep 10 | Out-String)
$out.results[1].result.$property.output | Should -BeExactly 'mySecretProperty'
$out.results[2].result.$property.output[0] | Should -BeExactly 'item1'
$out.results[2].result.$property.output[1] | Should -BeExactly 'item2'
$out.results[3].result.$property.output | Should -BeExactly 'item2'
}
}

Expand Down
2 changes: 2 additions & 0 deletions extensions/test/secret/secret.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ param(
[string]$Vault
)

$ErrorActionPreference = 'Ignore'

$secretsOne = @{
Vault1 = @{
MySecret = 'Hello'
Expand Down
3 changes: 2 additions & 1 deletion lib/dsc-lib-jsonschema/.versions.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"latestMajor": "V3",
"latestMinor": "V3_2",
"latestPatch": "V3_2_1",
"latestPatch": "V3_2_2",
"all": [
"V3",
"V3_2",
"V3_2_2",
"V3_2_1",
"V3_2_0",
"V3_1",
Expand Down
7 changes: 7 additions & 0 deletions lib/dsc-lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ setParameter = "Set parameter '%{name}' to '%{value}'"
parameterNotDefined = "Parameter '%{name}' is not defined in configuration"
noVariables = "No variables defined in configuration"
setVariable = "Set variable '%{name}' to '%{value}'"
parameterNotSecureString = "Parameter '%{name}' is not a secure string"
parameterNotSecureObject = "Parameter '%{name}' is not a secure object"
parameterNotString = "Parameter '%{name}' is not a string"
parameterNotInteger = "Parameter '%{name}' is not an integer"
parameterNotBoolean = "Parameter '%{name}' is not a boolean"
Expand Down Expand Up @@ -95,6 +97,8 @@ metadataRefreshEnvOnlyAffectsSet = "Resource returned '_refreshEnv' which indica
metadataRefreshEnvNoOperationContext = "Resource returned '_refreshEnv' which indicates environment variable refresh is needed but no operation context is available"
metadataRefreshEnvNoContext = "Resource returned '_refreshEnv' which indicates environment variable refresh is needed but no context is available"
copyDeprecated = "Copy for loop '%{name}' is deprecated and will be removed in a future release. See https://github.com/PowerShell/DSC/issues/1429 for more details."
secureStringMustBeString = "Secure string parameter '%{name}' must be a string"
secureObjectMustBeObject = "Secure object parameter '%{name}' must be an object"

[configure.parameters]
importingParametersFromComplexInput = "Importing parameters from complex input"
Expand Down Expand Up @@ -531,6 +535,8 @@ keyNotInt = "Parameter '%{key}' is not an integer"
keyNotBool = "Parameter '%{key}' is not a boolean"
keyNotObject = "Parameter '%{key}' is not an object"
keyNotArray = "Parameter '%{key}' is not an array"
keyNotSecureString = "Parameter '%{key}' is not a secure string"
keyNotSecureObject = "Parameter '%{key}' is not a secure object"
keyNotFound = "Parameter '%{key}' not found in context"

[functions.parseCidr]
Expand Down Expand Up @@ -591,6 +597,7 @@ multipleSecrets = "Multiple secrets with the same name '%{name}' and different v
extensionReturnedError = "Extension '%{extension}': %{error}"
noExtensions = "No extensions supporting secrets was found"
secretNotFound = "Secret '%{name}' not found"
invalidSecretFormat = "Invalid secret format returned for secret '%{name}'"

[functions.shallowMerge]
description = "Combines an array of objects where only the top-level objects are merged"
Expand Down
39 changes: 36 additions & 3 deletions lib/dsc-lib/src/configure/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use crate::configure::config_doc::{ExecutionInformation, ResourceDirective};
use crate::configure::context::{Context, ProcessMode};
use crate::configure::parameters::import_parameters;
use crate::configure::parameters::{SecureObject, SecureString, import_parameters};
use crate::configure::{config_doc::{ExecutionKind, IntOrExpression, Metadata, Parameter, Resource, ResourceDiscoveryMode, RestartRequired, ValueOrCopy}};
use crate::discovery::discovery_trait::DiscoveryFilter;
use crate::dscerror::DscError;
Expand Down Expand Up @@ -1048,6 +1048,29 @@ impl Configurator {
// TODO: additional array constraints
// TODO: object constraints

let value = match &constraint.parameter_type {
DataType::SecureString => {
if let Some(string_value) = value.as_str() {
let secure_string = SecureString {
secure_string: string_value.to_string(),
};
serde_json::to_value(secure_string)?
} else {
return Err(DscError::Validation(t!("configure.mod.secureStringMustBeString", name = name).to_string()));
}
},
DataType::SecureObject => {
if let Some(object_value) = value.as_object() {
let secure_object = SecureObject {
secure_object: serde_json::to_value(object_value)?,
};
serde_json::to_value(secure_object)?
} else {
return Err(DscError::Validation(t!("configure.mod.secureObjectMustBeObject", name = name).to_string()));
}
},
_ => value.clone(),
};
validate_parameter_type(&name, &value, &constraint.parameter_type)?;
if constraint.parameter_type == DataType::SecureString || constraint.parameter_type == DataType::SecureObject {
info!("{}", t!("configure.mod.setSecureParameter", name = name));
Expand Down Expand Up @@ -1375,7 +1398,12 @@ pub fn invoke_property_expressions(parser: &mut Statement, context: &Context, pr
///
pub fn validate_parameter_type(name: &str, value: &Value, parameter_type: &DataType) -> Result<(), DscError> {
match parameter_type {
DataType::String | DataType::SecureString => {
DataType::SecureString => {
if serde_json::from_value::<SecureString>(value.clone()).is_err() {
return Err(DscError::Validation(t!("configure.mod.parameterNotSecureString", name = name).to_string()));
}
}
DataType::String => {
if !value.is_string() {
return Err(DscError::Validation(t!("configure.mod.parameterNotString", name = name).to_string()));
}
Expand All @@ -1395,7 +1423,12 @@ pub fn validate_parameter_type(name: &str, value: &Value, parameter_type: &DataT
return Err(DscError::Validation(t!("configure.mod.parameterNotArray", name = name).to_string()));
}
},
DataType::Object | DataType::SecureObject => {
DataType::SecureObject => {
if serde_json::from_value::<SecureObject>(value.clone()).is_err() {
return Err(DscError::Validation(t!("configure.mod.parameterNotSecureObject", name = name).to_string()));
}
},
DataType::Object => {
if !value.is_object() {
return Err(DscError::Validation(t!("configure.mod.parameterNotObject", name = name).to_string()));
}
Expand Down
8 changes: 1 addition & 7 deletions lib/dsc-lib/src/configure/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,7 @@ impl Display for SecureObject {
/// `true` if the value is a secure value, `false` otherwise.
#[must_use]
pub fn is_secure_value(value: &Value) -> bool {
if let Some(obj) = value.as_object()
&& obj.len() == 1
&& (obj.contains_key("secureString") || obj.contains_key("secureObject")) {
return true;
}

false
serde_json::from_value::<SecureString>(value.clone()).is_ok() || serde_json::from_value::<SecureObject>(value.clone()).is_ok()
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, JsonSchema)]
Expand Down
6 changes: 5 additions & 1 deletion lib/dsc-lib/src/extensions/secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

use crate::{
configure::parameters::SecureString,
dscerror::DscError,
dscresources::{
command_resource::invoke_command,
Expand Down Expand Up @@ -100,7 +101,10 @@ impl DscExtension {
// remove any trailing newline characters
stdout.trim_end_matches('\n').to_string()
};
Ok(Some(secret))
let secure_string = SecureString {
secure_string: secret.clone(),
};
Ok(Some(serde_json::to_string(&secure_string)?))
}
} else {
Err(DscError::UnsupportedCapability(
Expand Down
15 changes: 6 additions & 9 deletions lib/dsc-lib/src/functions/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,16 @@ impl Function for Parameters {
// if secureString or secureObject types, we keep it as JSON object
match data_type {
DataType::SecureString => {
let Some(value) = value.as_str() else {
return Err(DscError::Parser(t!("functions.parameters.keyNotString", key = key).to_string()));
};
let secure_string = SecureString {
secure_string: value.to_string(),
if serde_json::from_value::<SecureString>(value.clone()).is_err() {
return Err(DscError::Parser(t!("functions.parameters.keyNotSecureString", key = key).to_string()));
};
return Ok(serde_json::to_value(secure_string)?);
return Ok(value.clone());
},
DataType::SecureObject => {
let secure_object = SecureObject {
secure_object: value.clone(),
if serde_json::from_value::<SecureObject>(value.clone()).is_err() {
return Err(DscError::Parser(t!("functions.parameters.keyNotSecureObject", key = key).to_string()));
};
return Ok(serde_json::to_value(secure_object)?);
return Ok(value.clone());
},
DataType::String => {
let Some(_value) = value.as_str() else {
Expand Down
6 changes: 3 additions & 3 deletions lib/dsc-lib/src/functions/secret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

use crate::DscError;
use crate::configure::context::Context;
use crate::configure::{context::Context, parameters::SecureString};
use crate::extensions::dscextension::Capability;
use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata};
use rust_i18n::t;
Expand Down Expand Up @@ -57,7 +57,6 @@ impl Function for Secret {
if secret_returned && result != secret_value {
return Err(DscError::Function("secret".to_string(), t!("functions.secret.multipleSecrets", name = secret_name.clone()).to_string()));
}

result = secret_value;
secret_returned = true;
}
Expand All @@ -68,7 +67,8 @@ impl Function for Secret {
}
}
if secret_returned {
Ok(Value::String(result))
let secure_string = serde_json::from_str::<SecureString>(&result).map_err(|_err| DscError::Function("secret".to_string(), t!("functions.secret.invalidSecretFormat", name = secret_name.clone()).to_string()))?;
Ok(serde_json::to_value(secure_string)?)
} else {
Err(DscError::Function("secret".to_string(), t!("functions.secret.secretNotFound", name = secret_name).to_string()))
}
Expand Down
Loading
Loading