I’ll show how to configure and use Powershell DSC so that it accepts PSCredential parameters in a configuration and encrypts these in the generated .mof file, so that only the target machine on which the configuration is applied can decrypt them.
This post applies to Powershell 4.0 and higher; if you want to learn more about Desired State Configuration and PSRemoting, check out these excellent e-books online: The DSC Book and Secrets of Powershell Remoting, or check out the complete list of ebooks at powershell.org.
The problem
By default, Powershell DSC prevents the use of PSCredential parameters in a configuration, because it would mean that the password would be stored as plain text in the .mof
file, which isn’t exactly secure. Suppose we have the following configuration that uses the Service resource:
Configuration MyConfiguration { param( [Parameter(Mandatory)] [PSCredential]$MyCredential ) Import-DscResource -ModuleName "PSDesiredStateConfiguration" Node "TargetServer" { Service WindowsUpdateService { Name = "wuauserv" Credential = $MyCredential } } } # Run the configuration, generating the MyConfiguration\TargetServer.mof file $credentials = Get-Credential -Message "Enter credentials" MyConfiguration -MyCredential $credentials
Because of the PSCredential parameter, creating the .mof
file fails with the following error:
Converting and storing encrypted passwords as plain text is not recommended. For more information on securing credentials in MOF file, please refer to MSDN blog: http://go.microsoft.com/fwlink/?LinkId=393729
The wrong way to work around this is to suppress this message by specifing PsDscAllowPlainTextPassword=$true
as part of the ConfigurationData
for this node:
$cd = @{ AllNodes = @( @{ NodeName = "TargetServer" PsDscAllowPlainTextPassword = $true } ) } MyConfiguration -MyCredential $credentials -ConfigurationData $cd
If you run it like this the .mof
file is generated, but it contains the password in plain text (open it with a text editor to see this for yourself), which is indeed “not recommended”:
Now, there are a couple of articles out there that try to explain what needs to happen in order to make this error go away, such as this, this and this one. After having read these and experimented quite a bit I finally got the ‘click’, but I can imagine that not everyone wants to put in that much effort and just use PsDscAllowPlainTextPassword=$true
and be done with it.
This is a shame really, since the setup isn’t that difficult, and I think that if things were explained just a little differently many more people would be using it.
So here goes, my attempt at writing up this information in the way I wished it was explained to me.
Overview
What needs to happen is that the PSCredential objects should not end up as plain text in the .mof
file, but rather be encrypted. This is done using assymetric cryptography, where the Sending machine encrypts the PSCredentials using a public key, so that only the Target machine with the corresponding private key is then able to decrypt them.
In concrete terms:
- The Target machine needs to have a certificate in its certificate store with both a public and a private key
- We export just the public key portion to a certificate file that we give to the Sending machine.
- Next, we’ll configure the Target machine’s Local Configuration Manager (which is responsible for applying the
.mof
files) to use this particular certificate from the certificate store to decrypt the encrypted values with. - On the sending side, whenever we run our Configuration to generate the
.mof
file, we tell it to use the certificate file to encrypt thePSCredential
s with, and Powershell won’t complain anymore about storing passwords in plain text.
Sounds easy enough? We’ll tackle each of these points in turn.
1. Install a certificate on the Target machine
Providing you don’t already have a certificate with a private key installed in the Personal certificate store for the Local Computer, there are various ways to obtain a certificate, such as:
A) Via Active Directory certificate enrollment
If your Target machine is part of an Active Directory domain with a Root or Enterprise Certification Authority, requesting a new certificate can be done through the Microsoft Management Console Certificates snap-in:
– Run mmc.exe
,
– Via File, Add/Remove Snap in, add the Certificates snap in for the local computer account,
– Navigate to Personal/Certificates,
– Right click in the details pane and choose All Tasks, Request New Certificate…
– Next, Next, check the “Computer” template and click Enroll.
If your system administrator has configured Automatic Certificate Enrollment, the certificate request should be granted immediately and the certificate should be added to the Computer’s Personal certificate store. Otherwise, your system administrator needs to explicitly grant this request.
B) Generating a self-signed certificate
If you don’t have a CA available, an alternative is to generate a self-signed certificate. The easiest way to do this is via IIS, via the Server Certificates feature – see this walkthrough.
C) Using makecert.exe
If you want to generate separate root and server certificates, you can use makecert
for this, as explained in this blog post.
Edit: Powershell 5 has some additional requirements on the certificate used, and otherwise will throw the following error when generating the .mof file:
Encryption certificates must contain the Data Encipherment or Key Encipherment key usage, and include the Document Encryption Enhanced Key Usage (1.3.6.1.4.1.311.80.1).
See this blog post and this StackOverflow post for more information.
2. Export the public key to a .cer file
Next, we’ll export the public key portion of this certificate to a Base-64 encoded X.509 .cer file. Assuming you still have the MMC certificate snap-in open on the Target machine (see above):
– Right-click the Certificate you’ve just generated and from the context menu, select All Tasks, Export
– Choose not to export private key,
– As Export file format, select “Base-64 encoded X.509 (.cer)”,
– Choose a filename to export to, say TargetServer.cer
A couple of notes:
– Since this file only contains the public key, it does not need to be kept safe, it can freely be distributed to other machines.
– Instead of using the MMC, you can also export the certificate from Powershell by using the Export-Certificate cmdlet.
– Prior to Windows 2012 this cmdlet is not yet available, but you can always export the certificate yourself.
Next, copy this file to the Sending machine, preferably to a well-known location so that all DSC Configurations can access it. For example, I’ve chosen to place this file at C:Program FilesDSC Public Keys
3. Configure the LCM to use this certificate
The Local Configuration Manager (LCM) on the Target machine needs to be told which certificate to use to decrypt the encrypted parts of the .mof files with. This being a part of DSC, the configuration of the LCM itself also happens via a DSC Configuration.
To request the current LCM configuration of the Target machine, simply run Get-DscLocalConfigurationManager
:
Get-DscLocalConfigurationManager AllowModuleOverwrite : False CertificateID : ConfigurationID : ConfigurationMode : ApplyAndMonitor ...
We’re about to change the CertificateID
property to the thumbprint of the certificate we generated. To determine the thumbprints of all eligible certificates, use:
Get-ChildItem Cert:\LocalMachine\my | Where-Object { $_.PrivateKey } Directory: Microsoft.PowerShell.Security\Certificate::LocalMachine\my Thumbprint Subject ---------- ------- 0EAD7D3A6255BED980028A84BDFC173ED330DB8E CN=TargetServer.tst.infosupport.com
Now that we have the thumbprint, we can use the LocalConfigurationManager resource to set this CertificateID
:
Configuration MetaConfiguration { # Note that this cannot be "localhost", it must be the actual computer name. Node $env:COMPUTERNAME { LocalConfigurationManager { CertificateID = "0EAD7D3A6255BED980028A84BDFC173ED330DB8E" } } } # Create a MetaConfiguration\TargetServer.meta.mof file and apply it to this computer: MetaConfiguration Set-DscLocalConfigurationManager .\MetaConfiguration -Verbose
With this, everything is set up correctly on the target machine.
4. Generating the .mof file using a certificate file
On the Sending machine, the final step is to generate the .mof
file using the certificate file that we have. This is done by specifying a CertificateFile
as part of the configuration data:
$cd = @{ AllNodes = @( @{ NodeName = "TargetServer" CertificateFile = " C:\Program Files\DSC Public Keys\TargetServer.cer" } ) } MyConfiguration -MyCredential $credentials -ConfigurationData $cd
This will generate a MyConfiguration\TargetServer.mof
file with an encrypted version of the PSCredential
s. Applying this configuration to the TargetServer should succeed, because it knows with which certificate to decrypt these credentials:
Start-DscConfiguration -Path MyConfiguration -Wait -Verbose VERBOSE: Perform operation 'Invoke CimMethod' with following parameters, ''methodName' = SendConfigurationApply,'className' = MSFT_DSCLocalConfigurationManager,'namespaceName' = root/Microsoft/Windows/DesiredStateConfiguration'. VERBOSE: An LCM method call arrived from computer TargetServer with user sid S-1-5-21-2782324402-2686024074-3196946232-1111. VERBOSE: [TargetServer]: LCM: [ Start Set ] VERBOSE: [TargetServer]: LCM: [ Start Resource ] [[Service]WindowsUpdateService] VERBOSE: [TargetServer]: LCM: [ Start Test ] [[Service]WindowsUpdateService] VERBOSE: [TargetServer]: [[Service]WindowsUpdateService] User name for service 'wuauserv' is 'LocalSystem'. It does not match 'TargetServer\Leon. VERBOSE: [TargetServer]: LCM: [ End Test ] [[Service]WindowsUpdateService] in 0.3120 seconds. VERBOSE: [TargetServer]: LCM: [ Start Set ] [[Service]WindowsUpdateService] VERBOSE: [TargetServer]: [[Service]WindowsUpdateService] Service 'wuauserv' already started, no action required. VERBOSE: [TargetServer]: LCM: [ End Set ] [[Service]WindowsUpdateService] in 0.3280 seconds. VERBOSE: [TargetServer]: LCM: [ End Resource ] [[Service]WindowsUpdateService] VERBOSE: [TargetServer]: LCM: [ End Set ] VERBOSE: [TargetServer]: LCM: [ End Set ] in 0.9840 seconds. VERBOSE: Operation 'Invoke CimMethod' complete. VERBOSE: Time taken for configuration job to complete is 1.029 seconds
By the way, if you’ve run this example, you now have changed the user account under which the Windows update service runs to the specified credentials. To revert these changes, run services.msc
, double click the “Windows update” service, and on the “Log On” tab, change the “Log on as” setting back to “Local system account”.
Extra: Using DSC over HTTPS
If WinRM on the TargetServer is configured only to allow HTTPS connections, you’ll find that Start-DscConfiguration
will fail with the error “The client cannot connect to the destination specified in the request. Verify that the service on the destination is running and is accepting requests.”
Unlike Enter-PSSession
, Start-DscConfiguration
doesn’t have a -UseSSL
flag, but we can still force it over SSL by creating a CimSession ourselves and pass that on to Start-DscConfiguration
:
$option = New-CimSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck -UseSsl $session = New-CimSession -ComputerName "TargetServer" -SessionOption $option Start-DscConfiguration -Path MyConfiguration -CimSession $session -Wait -Verbose
Conclusion
All in all, this hasn’t exactly been a short blog post, but I hope it clarifies how to use certificates to securely work with PSCredentials in DSC configurations.
Cheers! 😉
2 comments
Excellent that there are still some out there that don’t try to impress us with how complex they can describe a configuration, but rather on how simplistic they can explain a complex situation.
Kudos Leon
James
Nice article! I am wondering…Is there any way to compute in the target node the Thumbprint? Otherwise you have to get it manually. I´ve seen an article that mentioned something like this:
‘$((Get-ChildItem cert:\localmachine\my | Sort NotBefore | Select -First 1).ThumbPrint)’
to set the CertificateID in the localconfigurationmanager section.
but i don´t get it to work
Miguel