When I was trying to figure out how my team or other dev teams could start using DSC I had a couple goals I wanted to try to achieve:
- Highly privileged credentials will be required and must be secured
- Source control will be used to track changes and to automate builds
- Provide both the admins and dev teams a way to bring their configurations together
These goals gave rise to a number of possible options, from simple to complex. If you have used DSC beyond a sample configuration then you know that eventually you need to use
PSCredential in your configuration and that you can specify in your configuration how you want those PSCredential values encrypted at rest in the MOF file.
But how do you get those
PSCredential objects into your configuration file if there is no human to provide input to
Get-Credential? Sure you can use
ConvertTo-SecureString -String 'secret' -AsPlainText -Force and if you can guarantee where you store the configuration script is secure then that may be fine.
You could also on your build system login/runas the account that will perform the build and use
ConvertFrom-SecureString to produce an encrypted secret that only that user on that system will be able to use. How do you share that login credential with others so that they can produce their own encrypted secret?
You can even used a shared key with
ConvertFrom-SecureString but then how do you secure access to that key?
You could even create a central application that encrypts a secret for a user using a shared secret that is only accessible on the central application and DSC build system either via a secured file/registry key. But what happens when you need to rotate that shared secret?
If you are using DSC, you are most likely using certificates to encrypt the
PSCredential values in the MOF configuration file as a best practice, because passwords in the clear are a really bad idea and other methods mentioned above can be tricky.
Every certificate has two pieces of information, a public key and a private key. Just as the names state the private key is meant to be kept private and the public one can be used by anyone.
You don't typically encrypt data with a private key because anyone could decrypt it with the public key. This method is typically used for signing data that comes from the private entitiy but that's another blog entry and I digress.
However, if we encrypt a secret with the public key, only the entities with the private key will be able to decrypt the secret.
So, if anyone can encrypt data using the public key which can be distributed, only the entity with the private key, e.g. DSC build server(s) will be able to decrypt the secret before stuffing it into a
If you want to get started and don't have the module installed, you can get it from the PowerShell Gallery which is by far my favorite feature of PowerShell now.
Once you have it installed you'll need a certificate. PowerShell v5 has an enhanced
New-SelfSignedCertificate cmdlet that you can use to generate the appropriate certificate as show here:
New-SelfSignedCertificate -Subject 'CN=PowerShell DSC Encryption'
However, if you are stuck in PowerShell v4 like I was when I first made the module, you can use the
New-PSEncryptedDataCertificate function as shown here:
New-PSEncryptedDataCertificate -Name 'PowerShell DSC Encryption'
Great, so you have a certificate, you should generate it on the system where you want the private key to exist, then export the public key with
Export-Certificate and you should probably also use
Export-PfxCertificate to export the entire certificate for source control. This is ok, there is a shared password you will need to access the certificate pfx file to be able to reinstall or install it on other systems.
In the end, you should be able to see a certificate installed into some location in your key store:
PS C:\> Get-ChildItem Cert:\LocalMachine\My PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\My Thumbprint Subject ---------- ------- B6052E85F1EB02011AC34B05042EC085080B29F6 CN=PowerShell DSC Encryption
The core of the module is all about encrypting data at rest. It takes
SecureString values and encrypts them with the specified certificate and stores what information is needed to decrypt the data.
You can specify the certificate in three different ways.
- Path to the certificate, e.g.
- The thumbprint of the certificate
You can now encrypt data:
'Secret' | ConvertTo-PSEncryptedData -Thumbprint B6052E85F1EB02011AC34B05042EC085080B29F6
This produces an encrypted
eyJQcm92aWRlciI6eyJLZXlTaXplIjoyNTYsIkZlZWRiYWNrU2l6ZSI6OCwiUGFkZGluZyI6IlBLQ1M3IiwiS2V5IjoiRklvU1U3aWMxOWRkNU0xVmx6TTNt V1BmN2tlY0ZXQmUvZ2xIY3JmcDdwMVBKd3VUcEdtUmZaZ0E1SWZUTjRvWGQwN1lpZWtoVG9NcjVvUTJjNW12M1ZMUmhQSWpTNysweUE0UkJKSEM2Vjl3Y2VI T1dIOUVIZ1pkdXNYcTNWUWY1TXFKa0M1MEJwNml1SHJ0LzUxWlVoT3RRN0tvU0I2MmZveUtNVGNCeHlZYVR0dUpDOFMwU1I2dHlXUzJob09yZXpyZFZFTE5T ODZSb2xNcWxOU2NrRjJvbENXQ0Z5V2hiRVAveEs4RHZldkpsL29Ga1hTNE44aXJuMEIxVDdRekx5TzNZTmZBME9PZTNMT1pIMjVRNEVWbkJrNnB4TXlaZ1px UlVraFJtYUlkOHB3TmM0S3J4RC8zRUZFek5vQWhEZjBNV2p3SndWeFRkK2dqZVFoclNBPT0iLCJUeXBlIjoiU3lzdGVtLlNlY3VyaXR5LkNyeXB0b2dyYXBo eS5BZXNDcnlwdG9TZXJ2aWNlUHJvdmlkZXIiLCJJViI6IitWSStyYjVaSk5YRE85VnRicnd6b2c9PSIsIkJsb2NrU2l6ZSI6MTI4LCJNb2RlIjoiQ0JDIn0s IkVuY29kaW5nIjoiRGVmYXVsdCIsIkRhdGEiOiJja05hNVN3bGhQbk1lalNHWUhhdUxBPT0iLCJDZXJ0aWZpY2F0ZSI6IkI2MDUyRTg1RjFFQjAyMDExQUMz NEIwNTA0MkVDMDg1MDgwQjI5RjYifQ==
You can also encrypt a
SecureString the same way:
Read-Host -AsSecureString -Prompt 'Secret' | ConvertTo-PSEncryptedData -Thumbprint B6052E85F1EB02011AC34B05042EC085080B29F6
Decryption works the same way with
ConvertFrom-PSEncryptedData and as long as you have access to the private key of the certificate thumbprint that was used to encrypt the string it is almost like magic.
'Secret' | ConvertTo-PSEncryptedData -Thumbprint B6052E85F1EB02011AC34B05042EC085080B29F6 | ConvertFrom-PSEncryptedData
If you don't have access to the private key you will see the following error:
ConvertFrom-PSEncryptedData : The certificate 'CN=PowerShell DSC Encryption' with thumbprint 'B6052E85F1EB02011AC34B05042EC085080B29F6' does not contain a private key or access is denied.
Data At Rest
Having those large
base64 strings all over the DSC configuration makes it a hassle to deal with, but if you are used to things like that feel free to stop here, you know all you need to get started.
For me, I like to encapsulate complexity as much as possible and putting the encrypted data in the configuration only creates noise. To store the data instead in external files use the functions
Each are called just like
ConvertFrom-PSEncryptedData except you now have to pass a file path:
'Secret' | Export-PSEncryptedData -Thumbprint B6052E85F1EB02011AC34B05042EC085080B29F6 -OutputPath .\secret.encrypted Import-PSEncryptedData -Path .\secret.encrypted
The above could be used to encrypt the passwords of
PSCredential but then you'd still have to mash that up to the username and do some calls to
New-Object to get it all together.
Why do that more than once? So I wrapped it into a simple call just like export/import but instead of taking a
SecureString it works with instances of
Get-Credential | Export-PSEncryptedCredential -Thumbprint B6052E85F1EB02011AC34B05042EC085080B29F6 -OutputPath .\credential.encrypted Import-PSEncryptedCredential -Path .\credential.encrypted
I hope this helps you think about security while using DSC, because if your process isn't secure today it is only a matter of time before all those things that keep your CISO and his team up at night happen as you are just making it easier for the bad guys.
If you want to see how these functions work to help increase their security or want to contribute to the module check out the GitHub project.