diff --git a/dsc/tests/dsc_extension_secret.tests.ps1 b/dsc/tests/dsc_extension_secret.tests.ps1 index d3aba6633..6a6bca168 100644 --- a/dsc/tests/dsc_extension_secret.tests.ps1 +++ b/dsc/tests/dsc_extension_secret.tests.ps1 @@ -266,4 +266,20 @@ Describe 'Tests for the secret() function and extensions' { $env:DSC_RESTRICTED_PATH = $oldRestrictedPath } } + + It 'secureObject can be returned' { + $configYaml = @' + $schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Secure Object + type: Microsoft.DSC.Debug/Echo + properties: + output: "[secret('SecretObject', 'Vault2')]" + showSecrets: true +'@ + $out = dsc -l trace config get -i $configYaml 2> $TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.results.Count | Should -Be 1 + $out.results[0].result.actualState.Output.Hello | Should -BeExactly 'World' + } } diff --git a/extensions/test/secret/secret.ps1 b/extensions/test/secret/secret.ps1 index 79650c1c1..178847b1a 100644 --- a/extensions/test/secret/secret.ps1 +++ b/extensions/test/secret/secret.ps1 @@ -21,6 +21,9 @@ $secretsOne = @{ } Vault2 = @{ AnotherSecret = 'Foo' + SecretObject = @{ + Hello = 'World' + } | ConvertTo-Json -Compress } } diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 36e6d783c..a376a7db7 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -721,7 +721,6 @@ 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" diff --git a/lib/dsc-lib/src/configure/mod.rs b/lib/dsc-lib/src/configure/mod.rs index 467c18c0c..3ed7fd523 100644 --- a/lib/dsc-lib/src/configure/mod.rs +++ b/lib/dsc-lib/src/configure/mod.rs @@ -3,7 +3,7 @@ use crate::configure::config_doc::{ExecutionInformation, ResourceDirective}; use crate::configure::context::{Context, ProcessMode}; -use crate::configure::parameters::{SecureObject, SecureString, import_parameters}; +use crate::configure::parameters::{SecureObject, SecureString, import_parameters, is_secure_value}; use crate::configure::{config_doc::{ExecutionKind, IntOrExpression, Metadata, Parameter, Resource, ResourceDiscoveryMode, RestartRequired, ValueOrCopy}}; use crate::discovery::discovery_trait::DiscoveryFilter; use crate::dscerror::DscError; @@ -1217,12 +1217,12 @@ impl Configurator { default_value.clone() }; - if parameter.parameter_type == DataType::SecureString && value.is_string() { + if parameter.parameter_type == DataType::SecureString && value.is_string() && !is_secure_value(&value){ let secure_string = SecureString { secure_string: value.as_str().unwrap().to_string(), }; value = serde_json::to_value(secure_string)?; - } else if parameter.parameter_type == DataType::SecureObject && value.is_object() { + } else if parameter.parameter_type == DataType::SecureObject && value.is_object() && !is_secure_value(&value) { let secure_object = SecureObject { secure_object: value.clone(), }; diff --git a/lib/dsc-lib/src/extensions/secret.rs b/lib/dsc-lib/src/extensions/secret.rs index 43c5a2fb0..727ecce91 100644 --- a/lib/dsc-lib/src/extensions/secret.rs +++ b/lib/dsc-lib/src/extensions/secret.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use crate::{ - configure::parameters::SecureString, + configure::parameters::{SecureString, SecureObject}, dscerror::DscError, dscresources::{ command_resource::invoke_command, @@ -404,7 +404,7 @@ impl DscExtension { /// # Errors /// /// This function will return an error if the secret retrieval fails or if the extension does not support the secret capability. - pub fn secret(&self, name: &str, vault: Option<&str>) -> Result, DscError> { + pub(crate) fn secret(&self, name: &str, vault: Option<&str>) -> Result, DscError> { if self.capabilities.contains(&Capability::Secret) { debug!("{}", t!("extensions.dscextension.retrievingSecretFromExtension", name = name, extension = self.type_name)); let extension = match serde_json::from_value::(self.manifest.clone()) { @@ -440,10 +440,17 @@ impl DscExtension { // remove any trailing newline characters stdout.trim_end_matches('\n').to_string() }; + // if the secret is a valid JSON object, we wrap it as a SecureObject + if let Ok(json_value) = serde_json::from_str::(&secret) && json_value.is_object() { + let secure_object = SecureObject { + secure_object: json_value.clone(), + }; + return Ok(Some(serde_json::to_value(&secure_object)?)); + } let secure_string = SecureString { secure_string: secret.clone(), }; - Ok(Some(serde_json::to_string(&secure_string)?)) + Ok(Some(serde_json::to_value(&secure_string)?)) } } else { Err(DscError::UnsupportedCapability( diff --git a/lib/dsc-lib/src/functions/secret.rs b/lib/dsc-lib/src/functions/secret.rs index 9b353d8ef..a41c7c135 100644 --- a/lib/dsc-lib/src/functions/secret.rs +++ b/lib/dsc-lib/src/functions/secret.rs @@ -2,7 +2,7 @@ // Licensed under the MIT License. use crate::DscError; -use crate::configure::{context::Context, parameters::SecureString}; +use crate::configure::context::Context; use crate::extensions::dscextension::Capability; use crate::functions::{FunctionArgKind, FunctionCategory, FunctionMetadata}; use rust_i18n::t; @@ -48,7 +48,7 @@ impl Function for Secret { .filter(|ext| ext.capabilities.contains(&Capability::Secret)) .collect::>(); let mut secret_returned = false; - let mut result: String = String::new(); + let mut result: Value = Value::Null; if extensions.is_empty() { return Err(DscError::Function("secret".to_string(), t!("functions.secret.noExtensions").to_string())); } @@ -69,8 +69,7 @@ impl Function for Secret { } } if secret_returned { - let secure_string = serde_json::from_str::(&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)?) + Ok(result) } else { Err(DscError::Function("secret".to_string(), t!("functions.secret.secretNotFound", name = secret_name).to_string())) }