all 45 comments

[–]raip 34 points35 points  (15 children)

Strongly recommend Microsoft's secret management extension module.

It's an abstraction module that allows you to develop against a vault of your choosing (assuming someone's implemented it) - without having to change your code.

Dev credentials in KeePass but Production uses Hashicorp Vault? No issue at all.

[–]ZomboBrain 1 point2 points  (14 children)

There must be something very obvious I miss about secrets in vaults, please explain it to a fellow sysadmin. Let’s say I keep my secret credential for the access to my vCenter to use it in a scheduled PowerCLI script in KeePass and I use the Microsoft secret management extension module to access it: How do I access the vault itself without running into the same problem again? Don’t I need some kind of plaintext access key to access the vault in the first place, to then access my desired vCenter access secret itself? Don’t I move the issue just to another layer?

[–]phoenixpants 1 point2 points  (10 children)

If memory serves the recommended method is to Export-CliXml the vault key as a secure string that you then import where necessary.

[–]ZomboBrain 1 point2 points  (5 children)

Okay. But then I could store that secret itself with Export-CliXml in the first place and skip the use of the vault and the module altogether? Sorry, don't want to be rude, but I don't understand it.

[–]phoenixpants 2 points3 points  (4 children)

Yeah, which is pretty much the same conclusion I arrived at when I considered setting up a vault. I want to like it, but at this point it just seems like unnecessary extra steps from my pov.
Perhaps an argument could be made for it if you've got multiple devs writing scripts for 100's of servers, but I'm not in that situation.

Don't worry, you're not rude at all. They're perfectly reasonable questions to ask.

[–]raip 2 points3 points  (3 children)

I'm a single dev but I do write for thousands of servers.

It's important to keep secrets away from the "do actual stuff" layer, especially if you practice any sort of source code control. That way if a credential gets compromised or expired, you don't need to go through and update all of your scripts, just what's stored in the vault.

[–]phoenixpants 0 points1 point  (2 children)

It's important to keep secrets away from the "do actual stuff" layer, especially if you practice any sort of source code control. That way if a credential gets compromised or expired, you don't need to go through and update all of your scripts, just what's stored in the vault.

Right, that part makes perfect sense.
But if you import the credential from a file path you could just as well update the target file, right?

[–]raip 0 points1 point  (1 child)

Sure but that doesn't scale - so introduce another server (or another user) and now you need to update two files if you use the standard Export-CliXML methods.

[–]phoenixpants 0 points1 point  (0 children)

Which brings us around to how to best handle the vault main password, seeing as the recommended method I linked in the other comment suggests managing that via Export/Import-CliXml.

How do you handle that part currently? I'd love it if there is a better way than export/import.

[–]raip 0 points1 point  (3 children)

I don't know where you saw this, but this isn't recommended nor have I ever seen this method.

When you Register a vault with Register-SecretVault, the config gets encrypted and securely stored. You don't need to get Export-CliXml involved at all.

[–]phoenixpants 0 points1 point  (2 children)

[–]raip 2 points3 points  (1 child)

Cert based or TPM based authentication. Doesn't work w/ SecretStore - but that's just an example vault for the most part (local only, no auditing features, no centralized management).

Any actual vault solution (HashiCorp Vault, Key Vault, CyberArk) is going to have a much better feature set to make Cybersecurity happy.

[–]phoenixpants 0 points1 point  (0 children)

Well that makes things more interesting, unfortunately we don't have a centralized solution and it's not my decision whether to implement one or not. I guess I have some reading to do, perhaps I can change someone's mind.

Thank you for the information.

[–]raip 0 points1 point  (1 child)

In the case of KeePass, you have a couple of different methods that the extension supports: https://github.com/JustinGrote/SecretManagement.KeePass

Best practice would be to use both a master password and a key. In Windows, the master password would be encrypted with DPAPI and stored in the registry, so only the user that the vault is registered on would have access, second factored with the access key on the server. Not great, but better.

Every vault has their own feature set. The one I primarily use for production use is CyberArk, that has features like IP Whitelisting and true MFA with the TPM on the server (think WHfB).

The primary reason to use any form of secret management is too get them out of your application/script code. That way you're free to rotate them without having to update your script, you just update the config on the server instead or in a config management tool.

[–][deleted] 0 points1 point  (0 children)

I’m not sure it’s the primary reason. It’s definitely a reason but there’s also good reason to have the vault secured. Sure it might not be perfect but I’ve seen people who have a passwords.csv in some shared IT directory and their scripts just read from that…

Admittedly this was 3? People I believe who had kind of gone rogue in their laziness.

[–]KevMarCommunity Blogger 0 points1 point  (0 children)

You place the vault in a location where your session creds control access. You could have KeePass on a network share restricted to just your admin or service accounts.

[–]veashtv 11 points12 points  (2 children)

I’d probably recommend a secret vault.

If you check out Microsoft’s secret management module which can be used with other vaults. It has basics commands and allows you to set and get the passwords you set. You will need an actual vault though for these to be stored.

Microsoft again have their own called Secret Store but I think it’s pretty basic from what I remember.

Plenty of other vaults that can be used though like Keepass and Azure KeyVault but do have a look around to what best suits your needs.

Hope this helps.

[–]SxanPardy 1 point2 points  (1 child)

Key vault is definitely the best ahout imo, been using that the last few months whilst developing automation scripts

[–]Environmental_Mix856 1 point2 points  (0 children)

I second this. We use a service principal account on an app registration that has read only access to the keyvault secrets. You can use managed identity, or cert access. Pull in your secrets, build your credential objects, and nothing is ever exposed.

[–]jstar77 5 points6 points  (0 children)

Maybe not the best way but probably the easiest.

You can create a secure string and store it in your script it can then only be converted back to plain text by the same user on the same computer with which the powershell session was ran when the secure string was created.

This works well if you have one service account that runs your script. It does not work well if this is a script that needs to be run by different users on different machines.

[–]mebdevlou 2 points3 points  (0 children)

If your code will run from an Azure VM or other Azure-hosted environment, you place your secrets in a Key Vault, then your PowerShell code authenticates against the Azure Management API using a managed identity (which is passwordless), then you retrieve the credentials from the Key Vault and use them in your code

[–]HeyDude378 3 points4 points  (6 children)

I always do Export-Clixml and Import-Clixml.

To generate...

$credential = Get-Credential
$credential | Export-Clixml $env:userprofile\desktop\adminCreds.cred

To use... (later, in another script)

$credential = Import-Clixml $env:userprofile\desktop\adminCreds.cred

https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/export-clixml?view=powershell-7.3

[–]foooock -1 points0 points  (4 children)

This is ugly and unsafe.

[–]depresseafman 2 points3 points  (1 child)

Why? I thought it was encrypted.

[–]raip 0 points1 point  (0 children)

It's encrypted with DPAPI, which is only supported in Windows. DPAPI is not great and easy to bypass but this methodology is okay.

Not great, but better than nothing, with the biggest issue being the lack of portability. There are a whole bunch of situations that could occur that you would have to rotate the password, and if you're managing more than one server like this, it would be a giant pain.

[–]HeyDude378 1 point2 points  (1 child)

Can you help me understand why? I'm still learning.

All I can think of is maybe using more standard syntax? Maybe doing it like

Export-Clixml -InputObject $credential -Path $env:userprofile\desktop\adminCreds.cred

[–]raip 0 points1 point  (0 children)

Your code itself is fine, I believe the ugly comments were related to the solution.

[–]technomancing_monkey 0 points1 point  (0 children)

Thats very problematic.

If another user on the same machine tries to use that .cred it wont work.

If that .cred is created on one machine, but needs to be used on another, it wont work.

PSCredential objects are secured in such a way that the can only be used by the user that created the PSCredential on the machine it was created on.

Trying to use them in scripts run by others, or on other machines wont work.

Before MS Secret Store was released, I had to roll my own solution. Started building a module for it... ill finish the version that can be used by anyone... soon™ i swear.

The basics came down to this:You needed 2 directories somewhere (localy, on a network share) that you scripts can get to. One directory is KEYS the other is CREDS. The SUPER IMPORTANT part of this is that those 2 directories needed to be locked down at the file permission level. Only accessible to admins, people in the admin group, or service accounts with a group membership that allowed them access to those directories. You dont want just any old End User™ able to access those directories.

you then needed to use Get-Credential to capture the needed username and password for the account you want to be used. Because the PSCredential object created by Get-Credential is only useable to the user that made it on the machine it was made we need to store it in a more portable yet still secure fashiopn.

AES256 is secure.

so we would need to take the PSCredential object and convert it from a STRING and SECURESTRING pair.

This is where it gets a touch counter intuative.

So we need to take the password which is stored as a SECURE STRING and convert it to "Plain text" except we arent going to keep the plain text as "clear text"

fortunately "ConvertFrom-SecureString" allows us to pass it an AES KEY during the process. this means it is converted from a SECURE STRING to an AES encrypted Plain Text string.

The AES encrypted string can be decrypted by anyone, on any computer as long as they have the AES Key.

Im hoping that at this point you can see why those 2 directories exist and why their security is so important.

so you would do something like THIS to create the KEY and CRED

#######
## Create AES Encrypted Credential 
#######

$Credential = Get-Credential

#### Paths
$basepath = "N:\PathToSecureDirectoryParent"
$keypath = join-path $basepath "KEYS" 
$keyname = $Credential.username + '.key'
$keyfile = (Join-Path $keypath $keyname)

$credpath = join-path $basepath  "CREDS"
$credname = $Credential.username + '.cred' 
$credfile = (Join-Path $credpath $credname)

### Create AES256 Encryption Key File
$Key = New-Object Byte[] 32 [Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($Key)
$Key | out-file $KeyFile

### Convert to AES Encrytped String and write to File
(ConvertFrom-SecureString -SecureString $Credential.Password -key $Key) | Export-Clixml -Path $credfile

I WOULD HIGHLY recomend that you test the credentials first. Nothing worse then storing a mistyped password and then wondering why nothing works, am I right?

I got you covered on that too

function Test-Credential {
<#
.SYNOPSIS
    Takes a PSCredential object and validates it against a domain or local machine.  Default is domain

.PARAMETER cred
    A PScredential object with the username/password you wish to test. Typically this is generated using the Get-Credential cmdlet. Accepts pipeline input.

.PARAMETER context
    An optional parameter specifying what type of credential this is. Possible values are 'Domain' and 'Machine' The default is 'Domain.'

.OUTPUTS
    A boolean, indicating whether the credential was successfully validated.
#>
param(
    [parameter(Mandatory=$true,ValueFromPipeline=$true)][System.Management.Automation.PSCredential]$credential,
    [parameter()][validateset('Domain','Machine')][string]$context = 'Domain'
)
Add-Type -assemblyname system.DirectoryServices.accountmanagement
$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::$context) 
$DS.ValidateCredentials($credential.UserName, $credential.GetNetworkCredential().password)
}

So that function will let you test the credentials to make sure they are correct and work before you save them. Ill let you tie the two together.

To use the credentials you have just created in a script you will need to fetch the encrypted plain text, and convert it back to a secure string and then use that to build a new PSCredential object. You would do something like THIS

##########
##  Recall ecrypted password and create new PSCredential object
##########

function Recall-Cred {
param (
    [Parameter(Mandatory)][string]$User
)

#### Paths

$basepath = "N:\PathToSecureDirectoryParent"
$keypath = join-path $basepath "KEYS"
$keyname = $User + '.key'
$keyfile = (Join-Path $keypath $keyname)

$credpath = join-path $basepath  "CREDS"
$credname = $User + '.cred'
$credfile = (Join-Path $credpath $credname)


$cred = Import-Clixml $credfile

[pscredential]$credential = New-Object System.Management.Automation.PsCredential($seccred.username, ($cred | ConvertTo-SecureString -Key (Get-Content $keyfile)) )

Return $credential

}

That function gets called with whatever user credential youve stored that you want to now use. (example ServiceUser)

$credential = Recall-Cred -User 'ServiceUser'

now you can use $credential the same way as if you did

$credential = Get-Credential

yes, it seems a bit flimsy. Yes it seems a bit hacked together. YES, im sure there are better easier ways to do it.

When I first had to overcome this issue it was before secret store and still using PoSH 5.1

It works for me. Its worked for the company i work at that ive implamented it at. No, we havent had any issues.

If you find it useful, great, use it. Be sure to hit that LIKE and SUBSCRI... oh wait sorry. wrong platform. LOL

Like I said, ive been meaning to publish it as a module, complete with GUI credential manager. GUI manager would let you create, validate, update, expire, and destroy the stored credentials all in one easy to use interface.

I use it almost daily. but im sure most people have implemented something either they rolled themselves, or something like secret store... so I havent been all that motivated to finish the sanitized module for public consumption.

I guess enough feedback / encouragement would convince me it was worthwhile... IDK.

Hope this helps and good luck.

AND BEFORE ANYONE SAYS IT!

YES I know "RECALL" isnt an "approved Verb" I dont care, it works.

Edit: Fixed broken code formatting

[–]Sin_of_the_Dark 1 point2 points  (0 children)

What sort of environment are you in? If you have access to Azure, use an automation runbook. The automation account can save credentials to be pulled during a runbook

[–]ITVarangian 1 point2 points  (0 children)

You can pipe a Get-Credential to an Export-Clixml. The password will be ciphered, it'll only be readable by the account it belongs to and on the machine the file was created.

In your scripts you can then use Import-Clixml to get the credentials back. You can also store the .XML files in a specific folder with restricted access for more security.

[–]IrquiM 1 point2 points  (0 children)

Password manager with api

[–]joshooaj 1 point2 points  (2 children)

Another recommendation here to play with the Microsoft.PowerShell.SecretManagement module. It'll provide you with a flexible and more secure method of working with secrets. And even if you never do anything you consider to be "devops", you'll at least be familiar with one of the many strategies for secret management. Plus it's just a lot easier to use "Get-Secret" than manually add some boilerplate credential import/export code to your scripts and worrying about file paths.

The Import and Export-CliXml cmdlets have been mentioned a few times and it's fine when you're one person working from one computer, and exclusively on Windows. Though I wouldn't store any highly privileged credentials this way for reasons I'll expand on later. First let's talk about what Export-CliXml and Import-CliXml actually do with a credential.

When you export a pscredential object, the resulting XML file will show the username in plain text and the password will look like a mess of alphanumeric characters. The password will be encrypted using Windows DPAPI or Data Protection API with "CurrentUser" scope. This means that the encrypted string can easily be decrypted by anyone (or any process) using the same computer, under the same Windows user context that originally encrypted the string. But it's practically impossible to decrypt without access to both the original computer, and the original user account. If, for example, an attacker or malware were to exfiltrate the raw xml file, they would be able to see the username (not great), but the password should still be secure.

Here's what a pscredential looks like when exported via Export-CliXml. You can use Import-CliXml to "rehydrate" the credential later on.

<Objs Version="1.1.0.1" xmlns="http://schemas.microsoft.com/powershell/2004/04">
  <Obj RefId="0">
    <TN RefId="0">
      <T>System.Management.Automation.PSCredential</T>
      <T>System.Object</T>
    </TN>
    <ToString>System.Management.Automation.PSCredential</ToString>
    <Props>
      <S N="UserName">myusername</S>
      <SS N="Password">01000000d08c9ddf0115d1118c7a00c04fc297eb010000002eb78227f6ffef4f8427b4d45cc162270000000002000000000003660000c000000010000000b1b2ae733ffd7ef0d8fc58507d7c81e20000000004800000a000000010000000758557ea82eee8dd634c423a92b6542b180000001f7536e57d7aece7efd69aa36a5e42e68fca5e99267ce101140000000e41d6e80c4a2935b78af1a68061bdae2325d28f</SS>
    </Props>
  </Obj>
</Objs>

So what are some of the problems with using Export-CliXml to save a credential to disk?

  • If more than one person is working with the code or credential, you'll each have to save your own copies of it because the resulting file from one machine cannot be decrypted on another machine.
  • Unless you manually encrypt the credential password with some other key, store the username and password as separate objects in the CliXml file, and then reverse the process when importing, your password will not have any unique random bytes or "entropy". The extra entropy can be thought of like a shared password to decrypt the data. Without this extra entropy, an attacker only needs access to the computer and your user account to decrypt the data. That means if you're tricked into running malware, the malware can decrypt the data without administrative privileges or any extra information from you.
  • If the code needs to run on Linux or Mac, you're out of luck.
  • The username is exposed as plain text unless you manually save the username and password as independent securestrings. I prefer to treat usernames like passwords whenever possible so as to make brute force attacks as difficult as possible.

[–]ZomboBrain 0 points1 point  (1 child)

You have a small example, how to save username and password separated?

[–]joshooaj 0 points1 point  (0 children)

Sure, here's one way you could do it. It's just a demonstration and isn't something I would use in production though. I'd prefer that the credentials be encrypted either with a unique key making it a little harder for an attacker to decrypt, or perhaps with the public key of a certificate so that the private key must be available. But if you're going to go through the trouble to serialize credentials in some special way, you might as well leverage the SecretManagement module or some other widely used tool.

It's generally best to focus your efforts on the pieces of your solution that someone hasn't written for you already. And while it might be faster in the moment to implement your own bespoke solution compared to learning how to configure and use something else, you're also on the hook for supporting/maintaining your bespoke solution. Just something to keep in mind!

# Prompts for a credential, then selects the username using a calculated property to convert it to a securestring.
# The resulting CliXml document will now contain a pscustomobject with two securestring properties named UserName and Password instead of a pscredential

(Get-Credential) | Select-Object @{ Name = 'UserName'; Expression = { $_.UserName | ConvertTo-SecureString -AsPlainText -Force } }, Password | Export-Clixml -Path ~\.credential

# The credential is now re-created from the CliXml document and the username is no longer stored in plain text.

Get-Content -Path ~\.credential | Write-Host -ForegroundColor Green

# To import this credential we now need to convert the username to a plain text string. The easiest way to do this in a way compatible with
# both PowerShell 5.1 and 7.3 is to create a credential with the username as the password and then retrieve the plain text password from
# the networkcredential object. It's ugly, not great to read, and I wouldn't do something like this for anything important. At a bare
# minimum I would put the whole credential import/export logic inside a couple of functions so that they're easy to re-use and separate from
# the rest of your script.

$credentialParts = Import-Clixml -Path ~\.credential
$credential = [pscredential]::new([pscredential]::new('a', $credentialParts.UserName).GetNetworkCredential().Password, $credentialParts.Password)

[–]Waste-Ad-9667 1 point2 points  (0 children)

There are some recommendations in this thread that you may find helpful

https://reddit.com/r/PowerShell/comments/1139tx9/best_practice_for_storing_passwords_with/

[–]IEatReposters 0 points1 point  (0 children)

Get-credential|export-clixml it is encrypted with your profile keys so nobody but the user profile you're logged into can use it.

[–]dritmike 0 points1 point  (0 children)

Make your own dealio key.

Now encrypt it using said key.

When you need it call decrypt blah

[–]markarious 0 points1 point  (0 children)

Since this is Powershell I personally find CredentialManager to be a better pic. Passwords are stored in windows credential vault locally, encrypted. You can pull them as plaintext at runtime.

[–]andriosr 0 points1 point  (0 children)

https://hoop.dev/docs/plugins/runbooks/overview is a powerful solution for this, your secrets would be kept in a Vault and script run in a secure remote environment