So I've been kicking the tires and trying to do more with the Secrets Management modules from Microsoft, now that they are out of pre-release status. You can install the Microsoft.PowerShell.SecretStore and Microsoft.PowerShell.SecretManagement modules, you'll need both, from the PowerShell Gallery. You can find extension modules that build on the Microsoft modules for working with other key vaults or secret store. Run find-module -tag secretmanagement
to find additional modules. But what I want to talk about today relates to the Microsoft modules. Although, it might apply to you with any of the extension modules. The challenge is using the secrets management modules with a PowerShell profile script.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Defined Variables
In my PowerShell profile script, I call a script that defines a number of variables. These are items I use throughout the day, often in conjunction with PSDefaultParameterValues. Things like API keys and saved tokens. I used to store these values in the script file but decided to move these items to a secret store using the Microsoft secret management modules. I modified the script to set the variables by getting the values from the store.
$foo = Get-Secret -name foo -vault Secrets -AsPlainText
When launching a new PowerShell session, this worked. Up to a point. I would start PowerShell, and then be prompted to for the vault password. This isn't that big a burden, but there are implications.
Schedule Jobs
On my desktop, there are other things that can start PowerShell. Specifically, items like ScheduledJobs. By default, when you setup a scheduled job, the job launches a new PowerShell session which will run your profile scripts. But now there is a problem. When a scheduled job runs , the profile script is going to prompt for a password. But this is not happening interactively, so I can't provide it.
I could modify the scheduled job to not load the profile. But that's a lot of extra work. And maybe some of my scheduled jobs need the profile. I need to programmatically unlock the secret store so that I can define my variables. This is also the challenge you face when trying to integrate secret management into any type of automated process.
Separate the Password
First, let me point out that my approach is not the only solution, or even the best solution. Whenever you are dealing with secrets or sensitive information, there are always risks, challenges, and trade-offs. In my situation, the solution was to separate the vault password from the vault. By that I mean, I needed another way to get the password that didn't require any interaction.
I took the vault password and stored it in a protected CMS message using Protect-CMSMessage. The document can only be read on a computer with the necessary document encryption private keys. I saved the file to a text file using the computer name as part of the file name.
$vPass = "C:\scripts\$($env:computername)-vault.txt"
$myVault = "Secrets"
Unlocking Secrets
In my profile script, I can uprotect the password, convert it back to a secure string and use it to unlock the vault.
$q = Unprotect-CmsMessage -Path $vPass | ConvertTo-SecureString -AsPlainText -Force
Unlock-SecretStore -Password $q -PasswordTimeout 28800
I work at home and don't have too many security concerns, so I also set the password timeout to 8 hours, so that if I need to access the vault later in the day I don't need to re-enter the password. Once the fault is unlocked I can use Get-Secret to retrieve the values and set my variables. Because my secret names are identical to the variables I use, I can loop through a list. Here's the complete code snippet.
$vPass = "C:\scripts\$($env:computername)-vault.txt"
$myVault = "Secrets"
if (Test-Path $vPass) {
$q = Unprotect-CmsMessage -Path $vPass | ConvertTo-SecureString -AsPlainText -Force
Unlock-SecretStore -Password $q -PasswordTimeout 28800
$vars = "foo", "bar", "secret", "secret2", "apikey", "gitkey","bitlykey"
foreach ($var in $vars) {
Set-Variable -Name $var -Value (Get-Secret -Name $var -Vault $myVault -AsPlainText) -Scope global
}
#secure string variables
$bitly = Get-Secret -Name bitly -Vault $myVault
$artd = Get-Secret -Name company -Vault $myVault
}
This code runs as part of my profile script. I also have a few secrets that I want to remain as secure strings.
Summary
With this solution, I can store secrets in the secret management vault and use them in my profile. And, the profile can be used with Scheduled Jobs as well. It is up to you to find the best method for securely storing and retrieving the vault password.
You can learn more about the secret management module by visiting the module's repository.
Personally, I prefer to use KeyBase vault’s feature for my non-corp stuff. Its less complex and likely more secure way to store and retrieve secrets. From my review of Secrets Management modules, I can’t think of reason to use it over just communicating directly. Here is a module, I wrote for working with KeyBase’s KV engine: https://cadayton.keybase.pub/PSGallery/Modules/PSKeyBase/PSKeyBase.html.
There are definitely options. I think the Microsoft base modules still have a little work to be done.
Probably fine for your use case, but I think setting a plain text secret’s value to a string interns it on heap memory. That could leave your systems vulnerable to heap inspection.
That may be true, but if I have something running on my system able to dump memory, I’m already in trouble and have bigger problems.
Instead of initializing $vars with the names of the secrets that you have in your vault, and then having to keep updating your script when you add/delete secrets, consider getting all secrets from the vault with Get-SecretInfo and looping thru the items and setting your identically named variables.
This will also remove the names of the secrets from your script, and make your script a tad bit more secure.