Skip to content

Secrets Management RFC #208

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
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
207 changes: 207 additions & 0 deletions 2-Draft-Accepted/RFCxxxx-Secrets-Management.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
---
RFC: RFCxxxx
Author: Steve Lee
Status: Draft
SupercededBy: N/A
Version: 0.1
Area: Security
Comments Due: July 31st, 2019
Plan to implement: Yes, PS7
---

# Simplify and secure use of secrets in scripts

Advanced scripts that touch many systems require multiple secrets and
types of secrets particularly when orchestrating across different clouds.
Best practice is to not hard code any secrets in scripts, but currently
this requires custom code on different platforms to handle this securely.

This RFC proposes new cmdlets to make this simpler and secure.

## Motivation

> As a PowerShell script author,
> I can securely use multiple secrets,
> so that I can automate complex orchestration across multiple remote resources.

## High Level Design

This is a new independent module called `Microsoft.PowerShell.SecretsManagement`.
Secrets are stored securely in a local vault.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please list the cmdlets exposed from the module? I find it hard to infer from rest of the RFC about exposed cmdlets.
It would be great if you can list all exposed cmdlets at the beginning of the specification section.

The local vault is expected to only allow access to the user who owns that
vault.
Secrets required to access remote vaults are stored in the local vault and used by the secrets management
cmdlets to retrieve remote secrets.

`User Context` --> `Local Vault` --> `SecretsVault` --> `Remote Vault`

## User Experience
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to move this second to where the extension model is discussed. For someone who is not familiar with Az.KeyVault, he/she don't even know whether Register-SecretsVault is from that module or Microsoft.PowerShell.SecretsManagement.
Moving this to extension model discussion, it can serve as an example for that discussion.


Registering and using remote secrets:

```powershell
# Install the Az.KeyVault module to be used in this example.
# This module doesn't conform to the extensions model so would need to be
# updated to actually work.
Install-Module Az.KeyVault

# In this example, we explicitly register this extension
Register-SecretsVault -Name Azure -Cmdlet Get-AzKeyVaultSecret `

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be Register-SecretVault (pluralization in cmdlet names)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not true. Seems like you missed @SteveL-MSFT's reply about that. SecretsVault is singular.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While there might only be one "SecretsVault", and it might actually contain multiple "Secrets", the point of the "No Plurals" rule is about how plurals translate. At this point in the ecosystem, it's also about a reasonable expectation.

I do not believe that any PowerShell user will assume that Register-SecretVault will register a vault and then make it secret (it kinda flies in the face of the cmdlet name). I do believe that, especially as we have "Add-Secret", "Register-SecretsVault" would end up being confusing in practice. Your brain would remember one or the other, and you'd be more likely to flub it.

The other rationale, past pure pluralization, is the desire for common noun roots and the easy memorization that comes with it.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One other call out here, now that I see it, is that it shold support a -Parameter @{} ,which will be passed down to the undelying command. The scenario for this is that my cmdlet fits fine, but I want to supply default parameters to map to the underlying store.

Register-SecretVault -Name Azure Cmdlet Get-AzKeyVaultSecret -Module AZ.KeyVault -Version 1.0.0 -Parameter @{VaultName='MyVault;Location="West US"}

As I type out that bunch of code, I believe it would be nice to accept a command via pipeline, or to look up the command / assume defaults given the name. e.g.

  Get-Command Get-AzureKeyVaultSecret | Regster-SecretVault  # -Name would be AzureKeyVaultSecret, unless specified
  Register-SecretVault -Name Azure -Cmdlet Get-AzKeyVaultSecret # finds the command and supplies module / version, maybe complains or warns if there is no module

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing is that the -Cmdlet parameter is somewhat nonstandard. In a quick check of PS 5.1, the only cmds with -Cmdlet are:

  • Export-ModuleMember
  • Import-Module
  • New-Module

All of which actually need cmdlet.

Conversely, it's -Commad on:

  • Get/Remove Job
  • Invoke-Expression
  • Trace-Command
  • Get-PSBreakpoint /Set-PSBreakpoint

The last pair is especially instructive here, as I recall a discussion when naming that parameter on this topic. We wanted to avoid naming command breakpoints -Cmdlet because they could use commands that were not Cmdelts.

Unless you're saying this secret vault has to be a Cmdlet, the parameter should be -Command

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SecretsVault indicates a single vault containing multiple secrets. SecretVault indicates a single vault that itself is secret. I am with @KirkMunro on this one, the proposed noun is singular as the noun itself here is Vault while Secrets is describing what the vault itself is for.

-Module Az.KeyVault
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move this line to the previous line instead of using a line continuation?


# When using remote vaults, expectation is that secrets are
# stored using their tooling, so this module is only for retrieving secrets
# from remote vaults
$azDevOpsToken = Get-Secret -Name AzDevOps
Invoke-RestMethod https://dev.azure.com/… -Credential $azDevOpsToken -Authentication Basic
```

Registering and using local secret:

```powershell
# For local vault, we can register custom secrets
# In this example, we store a PSCredential object
Add-Secret -Secret $cred -Name MyCreds
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to add tags? It seems quite useful to add additional descriptive data (and it looks to be supported by the back end store). Also, I would like to have a description of all the parameters.
I assume this supports ShouldProcess, but want to be explicit.
Also, what is the nature of the object that is returned by some of these cmdlets?

New-PSSession -ComputerName myServer -Credential (Get-Secret -Name MyCreds)
```

## Specification

### Secrets Vault

There are two types of vaults: local and remote.
A local vault, by default, is already created and named `Default`.
`Default` on Windows is [CredMan](https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/credentials-management).
`Default` on macOS is [KeyChain](https://developer.apple.com/documentation/security/keychain_services).

>[!NOTE]
>KeyChain support is unlikely to be available in the first release of this feature.

`Default` on Linux is [Gnome Keyring](https://wiki.gnome.org/Projects/GnomeKeyring/).

>[!NOTE]
>Linux has many options for local credential management. The choice of Gnome Keyring
>is that it's a simple local vault we can easily test against and validate that our
>design works with a well-known Linux local vault. Since the extension model is
>open, we expect additional vault support to come from owners of those vaults or
>the community.

### Credential Vault Extensions

Vault extensions whether they are local or remote are provided as modules.

Extensions are modules that either:

- Call `Register-SecretsVault` upon loading the module, or
- Expose a command to the user which calls `Register-SecretsVault` using
input from the user

Modules that provide a vault extension should have the tag `SecretsVaultExtension`.

Extensions can register themselves by calling `Register-SecretsVault`
passing the name of the relevant cmdlet to `-Cmdlet` and the module name to `-Module`.
A `-Name` parameter is used for the user to define a friendly name.
There is an optional `-Parameters` parameter that takes a hashtable that will
be splatted to the extension cmdlet to support additional metadata needed
by the extension (such as authentication).

When using this model, the extension cmdlet would have to match the parameters and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is pretty sketchy - would you please provide a demo.txt, or similar?

output of `Get-Secret` to be compatible.

Alternatively, a `-ScriptBlock` parameter can be used instead of `-Cmdlet` and `-Module`
to allow using existing cmdlets without the need to write a wrapper module:

```powershell
Register-SecretsVault -Name AzKeyVault {
param($Name)
Get-AzKeyVaultSecret -VaultName (Get-Secret AzureKeyVaultName) -Name $Name |
Select -Expand SecretValue

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The downside to this kind of implementation (as opposed to a C# implementation with standardized implementations) is that there's no consistency as to how things like error handling work.

Can we include a list of standard error messages that providers should use for a few common scenarios such as:

  • Access Denied
  • Not Found

If providers all implement the exact same error handling for these scenarios, then it will make switching between providers much easier, as error handling code won't have to be rewritten.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As ideal as standardized error codes are, I do not believe this is something where one could guarantee underlying consistency. A web-based SecretVault will have a different return code under the covers compared to a C-api based SecretVault.

We could encourage people to use standard -ErrorIDs ( or at least a standard -ErrorCategory) to attempt to address the problem. Luckily, -ErrorCategory already contains the two we need: ObjectNotFound and PermissionDenied (or AuthenticationError).

As far as the need/want for a C# api, one can always use PowerShell's C# api to treat these cmdlets as a C# API. e.g.

PowerShell.Create().AddScript('Get-Secret MySecret').Invoke<string>();

Thus I believe not only isn't it in scope for this RFC, it shouldn't be for any future RFCs. I believe to do so opens a nasty pandora's box which would encourage other aspects of PowerShell to provide a C# api as well. This in turn de-emphasizes the need or want to ever write scripts.

Thus I believe we should specify standard error categories, but should not be in the habit of making special C# wrappers for PowerShell.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, the important part of my comment above was the standardization of errors. C# makes it easier to enforce standards, but having a section in the RFC stating that providers should implement the existing error categories/ID's to ensure consistency between different providers is the next best thing.

For example, if one had multiple remote providers and wanted to find a secret but wasn't certain which provider contained it (e.g. during a migration from one provider to another) then they could write a script that iterates through each installed remote provider and puts a "continue" in the catch block if it encounters an exception in the "ObjectNotFound" category. It would suck to have to handle each provider's error in a different way.

Copy link
Member Author

@SteveL-MSFT SteveL-MSFT Jul 25, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ErrorCategory is an enum, so not sure what needs to be explicitly stated here.

}
```

`Register-SecretsVault` has a `-Default` switch to register the
default local vault (if applicable, see above) if it has been unregistered.

If neither are supplied, then the current user context is used and relies on
the extension cmdlet to handle authentication.

If registering a vault with a Name that already exists, an error will be returned.

`Unregister-SecretsVault` should be used to remove any existing
registration or when updating a registration to a new version.
A `-Name` parameter is mandatory to remove a specific extension.

`Get-SecretsVault` will enumerate registered extensions returning
the module name and cmdlet used (if appropriate) and the friendly name.

A `SecretsVaultInfo` object will contain properties for all supported
parameters by registration.

```output
Name Module Cmdlet ScriptBlock
---- ------ ------ -----------
AzKeyVault AzKeyVault Get-AzKeyVaultSecret
myVault param($Name)
```

### Storing Secrets

The `Add-Secret` cmdlet is used to store a secret.
The `-Name` must be unique within a vault.
The `-Vault` parameter defaults to the local vault.
A `-NoClobber` parameter will cause this cmdlet to fail if the secret already exists.
A `-Secret` parameter accepts one of the supported types outlined below.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is where I would love to have some sort of -Tag or similar


### Retrieving Secrets

The `Get-Secret` cmdlet is used to retrieve secrets as the same type as they
were originally added.
The `-Name` parameter retrieves the secret associated with that name.
The `-Vault` parameter defaults to the local vault.
The returned object will be the original stored secret type.

`Get-Secret` without `-Name` will enumerate all stored secrets returning the
name and the vault.
`-Vault` can be used to filter to a specific vault for the enumeration.

### Removing Secrets

The `Remove-Secret` cmdlet is used to remove a stored secret.

### Authorization

Access to the credential vault is always using the current process security context.
In the case of remote vaults, the remote credential is stored within the local
default vault and retrieved as needed when accessing secrets from that remote
vault.

### Secrets Supported

Secrets supported will be:

- PSCredential
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

network credential?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that a secret of any kind? It has a plaintext password property.

- SecureString
- String
- HashTable
- Byte[]

## Alternate Proposals and Considerations

In this release, the following are non-goals that can be addressed in the future:

- Provision to rotate certs/access tokens
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems, in any event, as additional code based on the ones described herein.

- Sharing local vaults across different computer systems
- Sharing local vault across different user contexts
- A PSProvider for a Secrets: PSDrive
- C# interface for extensions
- C# API to retrieve secrets for C# based modules
- Delegation support
- Local vault requiring to be unlocked automatically
- Ubiquitous `-Secret` parameter taking a hashtable to automatically populate
a cmdlet's parameter taking a secret from the vault:

```powershell
Invoke-WebRequest -Secret @{Credential="GitHubCred"}
# this retrieves the secret called GitHubCred and passes it to the `-Credential`
# parameter of Invoke-WebRequest
```