Skip to content
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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,23 @@ changes since the last release, see the [diff on GitHub][unreleased].
before applying them. Passing `what_if` with an operation that doesn't support it returns an
invalid parameters error.

### Changed

- Extensions that implement the `secret` capability must now define the `args` property for the
`secret` command with exactly one secret name input argument (`nameArg`) and at most one vault
input argument (`vaultArg`). Previously, an extension could omit the secret name input argument,
which left DSC with no way to tell the extension which secret to retrieve. DSC no longer loads an
extension manifest that defines a nonfunctional `secret` command and logs an informational
message explaining the problem. The extension manifest JSON schema enforces the same
requirements.

<details><summary>Related work items</summary>

- Issues: [#1729][#1729]
- PRs: _None_
Comment thread
Gijsreyn marked this conversation as resolved.

</details>

## [v3.2.2][release-v3.2.2] - 2026-06-16

This section includes a summary of changes for the `3.2.2` release. For the full list of changes
Expand Down Expand Up @@ -1982,4 +1999,5 @@ Version `3.0.0` is the first generally available release of DSC.
[#1557]: https://github.com/PowerShell/DSC/issues/1557
[#1558]: https://github.com/PowerShell/DSC/issues/1558
[#1562]: https://github.com/PowerShell/DSC/issues/1562
[#1729]: https://github.com/PowerShell/DSC/issues/1729
Comment thread
Gijsreyn marked this conversation as resolved.

5 changes: 4 additions & 1 deletion docs/reference/schemas/extension/manifest/root.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,10 @@ runtime. When this property is defined, the extension has the `secret` capabilit
invoke the extension for the [secret()][05] configuration function.

The value of this property must be an object. The object's `executable` property, defining the name
of the command to call, is mandatory. The `args` property is optional. For more information, see
of the command to call, and `args` property, defining the arguments to pass to the command, are
both mandatory. The `args` property must define the secret name input argument exactly once and may
define the vault input argument at most once. DSC doesn't load an extension manifest that defines
the `secret` property without a secret name input argument. For more information, see
[DSC extension manifest secret property schema reference][06].

```yaml
Expand Down
44 changes: 31 additions & 13 deletions docs/reference/schemas/extension/manifest/secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ that extension.
The `secret` definition must include these properties:

- [executable](#executable)
- [args](#args)

## Properties

### executable

The `executable` property defines the name of the command to run. The value must be the name of a
command secretable in the system's `PATH` environment variable or the full path to the command. A
command discoverable in the system's `PATH` environment variable or the full path to the command. A
file extension is only required when the command isn't recognizable by the operating system as an
executable.

Expand All @@ -56,15 +57,23 @@ The `args` property defines the list of arguments to pass to the command. Each i
can be a string representing a static argument, a [name argument](#name-argument) object, or a
[vault argument](#vault-argument) object.

The array should contain exactly one name argument. It may contain a single vault argument and any
The array must contain exactly one name argument. It may contain at most one vault argument and any
number of static string arguments.

If the array doesn't define a name argument, DSC can't pass the secret name to the extension. If
the array doesn't define a vault argument, DSC can't pass the vault name to the extension.
Without a name argument, DSC can't pass the secret name to the extension, so the extension can't
retrieve a specific secret. DSC doesn't load an extension manifest that defines the `secret`
property without the `args` property, without a name argument, with more than one name argument,
with more than one vault argument, or with an item that isn't a string, a name argument, or a
vault argument. When DSC skips a manifest for one of these reasons, it logs an informational
message that explains the problem. Use the `--trace-level info` option, like
`dsc --trace-level info extension list`, to see the message.

If the array doesn't define a vault argument, DSC can't pass the vault name to the extension, so
the extension can't retrieve a secret from a specific vault.

```yaml
Type: array
Required: false
Required: true
ItemsType: [string, object(Name or Vault argument)]
```

Expand All @@ -79,34 +88,43 @@ Type: string

#### Name argument

Defines an argument that receives the path to the file to import.
Defines the argument that receives the name of the secret to retrieve.

DSC passes the value of `nameArg` followed by the name of the secret to retrieve.
DSC passes the value of `nameArg` followed by the name of the secret to retrieve. The `args` array
must define this argument exactly once.

A file argument is defined as a JSON object with the following properties:
A name argument is defined as a JSON object with the following properties:

- `nameArg` (required) - The argument to pass before the secret name, like `--secret-name`.

The object must not define any other properties. An object that defines both `nameArg` and
`vaultArg` isn't a valid argument.

```yaml
Type: object
RequiredProperties: [nameArg]
Type: object
RequiredProperties: [nameArg]
AdditionalProperties: false
```

#### Vault argument

Defines an argument that receives the name of a specific vault to retrieve a secret from.

DSC passes the value of `vaultArg` followed by the name of the vault when the `secret()` function
specifies a vault. When the function doesn't specify a vault, DSC ignores the vault argument.
specifies a vault. When the function doesn't specify a vault, DSC ignores the vault argument. The
`args` array may define this argument at most once.

A vault argument is defined as a JSON object with the following properties:

- `vaultArg` (required) - The argument to pass before the name of the vault to retrieve a secret
from, like `--vault-name`.

The object must not define any other properties.

```yaml
Type: object
RequiredProperties: [vaultArg]
Type: object
RequiredProperties: [vaultArg]
AdditionalProperties: false
```

<!-- Link reference definitions -->
Expand Down
68 changes: 67 additions & 1 deletion dsc/tests/dsc_extension_secret.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ Describe 'Tests for the secret() function and extensions' {
}

It 'Deprecated extension shows message' {
$oldRestrictedPath = $env:DSC_RESTRICTED_PATH
try {
$dscHome = Split-Path (Get-Command dsc).Source -Parent
$env:DSC_RESTRICTED_PATH = (Join-Path -Path $dscHome -ChildPath 'deprecated') + [System.IO.Path]::PathSeparator + $dscHome
Expand All @@ -197,7 +198,72 @@ Describe 'Tests for the secret() function and extensions' {
$LASTEXITCODE | Should -Be 4
(Get-Content -Raw -Path "$TestDrive/error.log") | Should -Match "Extension 'Test/ExtensionDeprecated' is deprecated: This extension is deprecated" -Because (Get-Content -Raw -Path "$TestDrive/error.log")
} finally {
$env:DSC_RESTRICTED_PATH = $null
$env:DSC_RESTRICTED_PATH = $oldRestrictedPath
}
}

It 'Secret extension manifest <reason> is not loaded' -TestCases @(
@{ reason = 'without args'; secret = '{ "executable": "dsctest" }'; expectedError = 'missing field *args*' }
@{ reason = 'without a name argument'; secret = '{ "executable": "dsctest", "args": ["no-op"] }'; expectedError = "The 'secret' command doesn't define the secret name input argument" }
@{ reason = 'with multiple name arguments'; secret = '{ "executable": "dsctest", "args": ["no-op", { "nameArg": "--name" }, { "nameArg": "--secret" }] }'; expectedError = "The 'secret' command defines the secret name input argument 2 times" }
@{ reason = 'with multiple vault arguments'; secret = '{ "executable": "dsctest", "args": ["no-op", { "nameArg": "--name" }, { "vaultArg": "--vault" }, { "vaultArg": "--store" }] }'; expectedError = "The 'secret' command defines the vault input argument 2 times" }
@{ reason = 'with an argument that defines both nameArg and vaultArg'; secret = '{ "executable": "dsctest", "args": ["no-op", { "nameArg": "--name", "vaultArg": "--vault" }] }'; expectedError = "Argument 2 of the 'secret' command isn't valid" }
) {
param($secret, $expectedError)

$manifest = @"
{
"`$schema": "https://aka.ms/dsc/schemas/v3/bundled/extension/manifest.json",
"type": "Test/SecretInvalid",
"version": "0.1.0",
"description": "Invalid secret extension for testing.",
"secret": $secret
}
"@

$oldRestrictedPath = $env:DSC_RESTRICTED_PATH
try {
$env:DSC_RESTRICTED_PATH = $TestDrive
Set-Content -Path "$TestDrive/secretInvalid.dsc.extension.json" -Value $manifest
$out = dsc -l info extension list 2> $TestDrive/error.log | ConvertFrom-Json
$errorLog = Get-Content -Raw -Path $TestDrive/error.log
$LASTEXITCODE | Should -Be 0 -Because $errorLog
@($out).type | Should -Not -Contain 'Test/SecretInvalid'
$errorLog | Should -BeLike "*INFO Failed to load manifest: *$expectedError*" -Because $errorLog
} finally {
$env:DSC_RESTRICTED_PATH = $oldRestrictedPath
}
}

It 'Secret extension manifest with a name argument and a vault argument is loaded' {
$manifest = @'
{
"$schema": "https://aka.ms/dsc/schemas/v3/bundled/extension/manifest.json",
"type": "Test/SecretValid",
"version": "0.1.0",
"description": "Valid secret extension for testing.",
"secret": {
"executable": "dsctest",
"args": [
"no-op",
{ "vaultArg": "--vault" },
{ "nameArg": "--name" }
]
}
}
'@

$oldRestrictedPath = $env:DSC_RESTRICTED_PATH
try {
$env:DSC_RESTRICTED_PATH = $TestDrive
Set-Content -Path "$TestDrive/secretValid.dsc.extension.json" -Value $manifest
$out = dsc extension list 2> $TestDrive/error.log | ConvertFrom-Json
$LASTEXITCODE | Should -Be 0 -Because (Get-Content -Raw -Path $TestDrive/error.log)
@($out).Count | Should -Be 1
$out.type | Should -BeExactly 'Test/SecretValid'
$out.capabilities | Should -BeExactly @('secret')
} finally {
$env:DSC_RESTRICTED_PATH = $oldRestrictedPath
}
}
}
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 @@ -303,6 +303,13 @@ manifestImported = "Manifest imported from extension %{extension}"
extensionManifestSchemaTitle = "Extension manifest schema URI"
extensionManifestSchemaDescription = "Defines the JSON Schema the extension manifest adheres to."

[extensions.secret]
invalidArgs = "The arguments for the 'secret' command are invalid: %{err}"
missingNameArg ="The 'secret' command doesn't define the secret name input argument, so DSC can't pass the name of the secret to retrieve to the extension. Define exactly one argument in 'secret.args' as an object with the 'nameArg' property"
multipleNameArgs = "The 'secret' command defines the secret name input argument %{count} times. Define exactly one argument in 'secret.args' as an object with the 'nameArg' property"
multipleVaultArgs = "The 'secret' command defines the vault input argument %{count} times. Define at most one argument in 'secret.args' as an object with the 'vaultArg' property"
invalidArg = "Argument %{position} of the 'secret' command isn't valid. Each argument must be a string, an object with only the 'nameArg' property, or an object with only the 'vaultArg' property. Invalid argument: %{arg}"

[functions]
invalidArgType = "Invalid argument type"
invalidArguments = "Invalid argument(s)"
Expand Down
90 changes: 90 additions & 0 deletions lib/dsc-lib/locales/schemas.extension.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
_version: 2
schemas:
extension:
manifest:
secret:
Comment thread
Gijsreyn marked this conversation as resolved.
title:
en-us: Secret command
description:
en-us: >-
Defines how to call the extension to retrieve a secret from a vault.
markdownDescription:
en-us: |-
Defines how to call the extension to retrieve a secret from a vault at runtime. When
this property is defined, the extension has the `secret` capability and DSC can invoke
the extension for the [`secret()`][01] configuration function.

When DSC invokes the `secret` command, it expects the extension to write the secret
value to stdout as a single line. If the extension writes more than one line to stdout,
DSC raises an error. If the extension writes nothing to stdout, DSC treats the secret as
not found for that extension.

For more information, see the [secret property reference][02].

[01]: https://learn.microsoft.com/powershell/dsc/reference/schemas/config/functions/secret
[02]: https://learn.microsoft.com/powershell/dsc/reference/schemas/extension/manifest/secret
executable:
title:
en-us: Executable
description:
en-us: >-
The name of the command to run to retrieve a secret.
markdownDescription:
en-us: |-
Defines the name of the command to run to retrieve a secret. The value must be the
name of a command discoverable in the system's `PATH` environment variable or the
full path to the command. A file extension is only required when the command isn't
recognizable by the operating system as an executable.
args:
title:
en-us: Arguments
description:
en-us: >-
The arguments to pass to the command to retrieve a secret. The arguments must define
the secret name input argument exactly once and may define the vault input argument
at most once.
markdownDescription:
en-us: |-
Defines the list of arguments to pass to the command to retrieve a secret. Each item
can be a string representing a static argument, an object with the `nameArg` property,
or an object with the `vaultArg` property.

The arguments must define exactly one object with the `nameArg` property. DSC passes
the value of `nameArg` followed by the name of the secret to retrieve. Without this
argument, DSC can't tell the extension which secret to retrieve, so DSC doesn't load
the extension manifest.

The arguments may define at most one object with the `vaultArg` property. DSC passes
the value of `vaultArg` followed by the name of the vault when the `secret()` function
specifies a vault. When the function doesn't specify a vault, DSC omits this argument.
constraints:
nameArg:
title:
en-us: Secret name input argument
description:
en-us: >-
The arguments must define exactly one object with the `nameArg` property.
markdownDescription:
en-us: |-
The arguments must define exactly one object with the `nameArg` property. DSC passes
the value of `nameArg` followed by the name of the secret to retrieve. Without this
argument, DSC can't tell the extension which secret to retrieve.
errorMessage:
en-us: >-
The `secret` command must define the secret name input argument exactly once. Define
exactly one argument in `secret.args` as an object with the `nameArg` property.
vaultArg:
title:
en-us: Vault input argument
description:
en-us: >-
The arguments may define at most one object with the `vaultArg` property.
markdownDescription:
en-us: |-
The arguments may define at most one object with the `vaultArg` property. DSC passes
the value of `vaultArg` followed by the name of the vault when the `secret()` function
specifies a vault.
errorMessage:
en-us: >-
The `secret` command defines the vault input argument more than once. Define at most
one argument in `secret.args` as an object with the `vaultArg` property.
3 changes: 3 additions & 0 deletions lib/dsc-lib/src/dscerror.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,9 @@ pub enum DscError {
#[error("{t}: {0}", t = t!("dscerror.securityContext"))]
SecurityContext(String),

#[error(transparent)]
SecretArgs(#[from] crate::extensions::secret::SecretArgsError),

#[error(transparent)]
SemVer(#[from] crate::types::SemanticVersionError),

Expand Down
Loading
Loading