An abstraction to handle using secrets
Summary of the new feature / enhancement
Current design requires passing secrets into a configuration as parameters as either SecureString or SecureObject. However, it may be easier/better to have a way for a configuration to have sufficient information to pull secrets from a known secret store. For example, at runtime having the secrets pulled from Azure KeyVault.
Proposed technical implementation details (optional)
This will likely be used for enterprise which have a shared or common secret store while community configurations that require secrets may still make sense to be passed in as parameters.
What may make sense is to introduce a getSecret('storeName', 'secretName') function. The storeName is associated with a specific DSC extension with that name and the secretName is passed to that extension. The secret itself is retrieved from the extension as clear text, but treated within DSC as a SecureString or SecureObject (both should work and up to what the extension returns).
Now that we have an extension mechanism, I think the way this will work is that an extension will implement the secret operation. At this time, I don't think we support storing secrets, only retrieving them, so the function will simply be secret(<nameOfSecret>). dsc will call all extensions that implement the secret operation with the name of the secret as an argument:
- if one returns a value, that value is stored as a
SecureString(I don't think we need to worry aboutSecureObjectto simplify the contract) - if none return a value, that is an error
- if more than one returns a value, that is an error
Extensions that don't contain that secret should still return a 0 success code. A non-zero exit code should only indicate a problem retrieving the secret other than not found.
This model does not define how the user authenticates with the secret store, expectation is that the extension returns an appropriate error message informing the user how to unlock or login as needed.
I think the way this will work is:
- function
secret(<nameOfSecret>, [vaultName], [secureString|secureObject]) - only the first param is required which is the name of the secret, if no vault is specified, then all vaults are queried for the secret
- if more than one return a secret, it is an error
- if none return the secret, it is an error
- if the secret is an empty string, this is allowed
- the
[vaultName]must match the vault extension type name such as:Microsoft.Azure/KeyVault - vaults may return either a
secureStringorsecureObjectidentified by the last optional parameter 1. if not specified, then DSC will try to deserialize the secret into a JSON object, if successful, it's asecureObject(and thus you can use dot notation to access members), if deserialization fails, it's asecureString2. if specified assecureStringthen any JSON text will just be a string 3. if specified assecureObjectand the JSON fails to deserialize, this is an error
Regarding point 3, do we need to distinguish between different vaults of the same type name?
Regarding point 3, do we need to distinguish between different vaults of the same type name?
Good question. I suppose users may have multiple different vaults within AzKeyVault, for example. In that case, maybe it should be:
[valutName] is the name of the vault and all vault extensions get queried for it. In this case, the configuration doesn't specify at all which vault extension to use. And initially, I won't add the 3rd parameter and we can reserve future parameters to specify the extension if a need arises.
So vault extensions would have to accept a parameter for the secret name and an optional parameter for the vault name.