Secrets Management Module Vault Extensions

A new PowerShell Secrets Management module has been published on PowerShell Gallery. It is currently in a pre-release state and still in active development. Even though the module is not complete, we have released it to gather early community feedback. Most of the functionality is implemented, but currently the module only works on Windows with Linux support coming next, followed by macOS. This article describes the Secrets Management module in general, but is mostly about how vault extensions work. For a full overview of Secrets Management see this article.

The Secrets Management module is based on the Secrets Management RFC, which you can learn more about from this great Ignite presentation.

Secrets Management module

The purpose of the Secrets Management module is to provide secure storage and access of secrets in your PowerShell scripts. It consists of an always available local storage solution (or vault), along with a vault extension mechanism that allows registration of other secrets storage/retrieval solutions. A vault extension can implement a local or remote custom storage solution.

The Secrets Management module contains commands for registering vault extensions, and manipulating vault secrets.

For example, this script uses the Secrets Management module to retrieve a NuGet API key in order to publish MyNewModule to the PowerShell Gallery, and invoke script remotely using a stored credential:

Import-Module Microsoft.PowerShell.SecretsManagement # Publish a module to the PowerShell Gallery Publish-Module -Path C:\Modules\Publish\MyNewModule -NuGetApiKey (Get-Secret NuGetApiKey -AsPlainText) # Run management script on multiple machines Invoke-Command -Cn $machines -FilePath .\MyMgmtScript.ps1 -Credential (Get-Secret MgmtCred)

Built-in local vault

The Secrets Management module comes with a built-in local vault. It is always present and can be used to safely store and retrieve secrets without any extension vaults registered. On Windows, it is implemented with CredMan. Credential Manager encrypts and stores secrets based on the current user context, and only that same user can access those secrets. On Linux, the built-in local vault will likely use Gnome Keyring to securely store and retrieve secrets, though others can be added in the future, whether by the PowerShell Team or an external vault extension author.

This example shows the names and types of credentials created and stored in the built-in local vault:

Get-SecretInfo Name Vault TypeName ---- ----- -------- GitHubPATForRelease BuiltInLocalVault String GitRepoAccessToken BuiltInLocalVault String LocalAdminCred BuiltInLocalVault PSCredential SecretParameters BuiltInLocalVault Hashtable PSGalleryPAT BuiltInLocalVault String

Secret data types

The Secrets Management module supports five data types, and the built-in local vault supports all five types. However, extension vaults can implement any subset of the supported types:

byte[]

string

SecureString

PSCredential

Hashtable

The Hashtable data type is used by the module to store optional vault extension parameters. Because the additional parameters may contain secrets, the parameters are stored securely as a Hashtable in the built-in local vault.

Vault extensions

In addition to the built-in local vault, any number of vault extensions can be registered. A vault extension can store secrets on the local machine or remotely. For example, a remote vault extension could use Azure KeyVault to store secrets. A vault extension is registered to the current logged in user context, and only that user can access the registered vault.

Vault extensions are PowerShell modules. Extensions can be either script based PowerShell modules or binary modules. Script based extension modules must implement and export four module functions. Binary based extension modules must include a C# class that derives from an abstract extension class, and implements four required methods.

Extension vault registration cmdlets

Register-SecretsVault registers a PowerShell module as an extension vault for the current user context. Validation is performed to ensure the module either provides the required binary with implementing type or the required script commands. If a dictionary of additional parameters is specified then it will be stored securely in the built-in local vault.

Get-SecretsVault returns a list of extension vaults currently registered in the user context.

Unregister-SecretsVault un-registers an extension vault.

Register-SecretsVault -Name CodeLocalVault -ModuleName CodeLocalVault Get-SecretsVault Name ModuleName ImplementingType ---- ---------- ---------------- BuiltInLocalVault CodeLocalVault CodeLocalVault CodeLocalVault.ImplementingClass ScriptLocalVault ScriptLocalVault

Script vault extension module

A script based vault extension must be a PowerShell module that includes a subdirectory named ‘ImplementingModule’, and which is in the same directory as the PowerShell module manifest file. The ImplementingModule subdirectory must contain PowerShell script module files named ‘ImplementingModule’, e.g. ImplementingModule.psd1, ImplementingModule.psm1, and which implement the required script functions.

The ImplementingModule module is in a subdirectory so as not to expose the SetSecret, GetSecret, RemoveSecret, GetSecretInfo script functions to the user through PowerShell’s command discovery. This way the functions will only be available for internal use.

NOTE: We plan to change the ‘ImplementingModule’ name to ‘SecretsManagementExtension’, since it is more descriptive. Remember, this is a work in progress!

ImplementingModule.psd1

@{ ModuleVersion = '1.0' RootModule = '.\ImplementingModule.psm1' FunctionsToExport = @('Set-Secret','Get-Secret','Remove-Secret','Get-SecretInfo') }

ImplementingModule.psm1

function Set-Secret { param ( [string] $Name, [object] $Secret, [hashtable] $AdditionalParameters ) } function Get-Secret { param ( [string] $Name, [hashtable] $AdditionalParameters ) } function Remove-Secret { param ( [string] $Name, [hashtable] $AdditionalParameters ) } function Get-SecretInfo { param ( [string] $, [hashtable] $AdditionalParameters ) }

For an example of a script based vault extension, check out the ScriptLocalVault module.

C# vault extension module

A C# vault extension is a PowerShell module that includes a binary assembly which implements the SecretsManagementExtension abstract class. The abstract class requires the implementation of four methods that are essentially the same as the required four functions of a script based vault extension. The PowerShell module is sparse, requiring only a PowerShell manifest file that identifies the implementing type in the ‘RequiredAssemblies’ entry.

CodeLocalVault.psd1

@{ ModuleVersion = '1.0' RequiredAssemblies = @('CodeLocalVault') }

CodeLocalVault.cs

// CodeLocalVault implements these SecretsManagementExtension abstract class methods public abstract bool SetSecret( string name, object secret, IReadOnlyDictionary<string, object> parameters, out Exception error); public abstract object GetSecret( string name, IReadOnlyDictionary<string, object> parameters, outException error); public abstract bool RemoveSecret( string name, IReadOnlyDictionary<string, object> parameters, outException error); public abstract KeyValuePair<string, string>[] GetSecretInfo( string filter, IReadOnlyDictionary<string, object> parameters, out Exception error);

For an example of a C# code based vault extension module, check out the CodeLocalVault module.

Extension vault module required functions

Both script based and binary module vault extensions must implement the same four functions.

The script function versions will write any errors to the error stream. The C# function versions will return an error as an out variable Exception object.

SetSecret

This function stores a secret object to the vault. It takes three input parameters: name , secret , parameters . The name parameter is the name of the secret to store. The secret parameter is the secret object to be stored. The parameters parameter is an optional dictionary (Hashtable) of name/value pairs for additional parameters. The function returns a boolean true on success and false otherwise.

GetSecret

This function returns a single secret object from the vault. It takes two input parameters: name , parameters . The name parameter is the name of the secret to store. The parameters parameter is an optional dictionary (Hashtable) of name/value pairs for additional parameters. The function returns a single secret object or null if secret name does not exist.

RemoveSecret

This function removes a single secret from the vault. It takes two input parameters: name , parameters . The name parameter is the name of the secret to store. The parameters parameter is an optional dictionary (Hashtable) of name/value pairs for additional parameters. The function returns a boolean true on success and false otherwise.

GetSecretInfo

Returns an array of string pairs representing each secret. The first string of the pair is the secret name. The second string is the secret object type name. This function never returns the actual secret object, but only information about the secret. This function takes two input parameters: name , parameters . The name parameter is the name of the secret to store. The parameters parameter is an optional dictionary (Hashtable) of name/value pairs for additional parameters.

The GetSecretInfo function returns different objects depending on whether it is implemented in script or C#.

A script function will return a pscustomobject for each secret.

Write-Output ([pscustomobject] @{ Name = "MyCredential" # Secret name Value = "PSCredential" # Secret object type name })

A C# method will return a KeyValuePair object for each secret.

secretInfo.Add( new KeyValuePair<string, string>( key: "MyCredential", // Secret name value: SupportedTypes.PSCredential.ToString())); // Secret object type name

For a consistent user experience, be sure and use the approved name for a secret object type. There is a public enum in the SecretsManagementExtension abstract class called SupportedTypes and that can be used for secret type names. The supported secret type names are:

ByteArray : Secret as an array of bytes

: Secret as an array of bytes String : Secret as a string object

: Secret as a string object SecureString : Secret as a SecureString object

: Secret as a SecureString object PSCredential : Secret as a PowerShell credential (contains both user name and password secret)

: Secret as a PowerShell credential (contains both user name and password secret) Hashtable : Secrets collected in a Hashtable

About vault extension examples

Two simple vault extension examples are provided at these links:

ScriptLocalVault

CodeLocalVault

These two examples are intended only as demonstrations of the basics of creating script and code based vault extensions. The vaults are not secure and should not be used to store sensitive information. Make sure you perform a full security review on any extension vaults you implement.

These example extension vaults use PowerShell’s Export-CliXml and Import-CliXml cmdlets to serialize secret objects to file. The files are stored in the temporary directory of the current user context.

Security

The security of Secrets Management is dependent on the underlying storage mechanism of each vault. It is critical that each extension vault be carefully reviewed for security. Using known and trusted secret storage solutions, such as Windows Credential Manager, Gnome Keyring, or Azure KeyVault, is the best path to implementing a secure vault extension.

Summary

The Secrets Management module is useful right out of the box for accessing secrets in PowerShell scripts. But the vault extension feature allows an even more powerful, cloud based way, to safely use secrets in script running on potentially any machine at any location.

Try Secrets Management out on Windows, and please give us feedback about your experience. Whether managing secrets or authoring vault extensions, you can give feedback here.

Paul Higinbotham Senior Software Engineer, PowerShell