Safely using PSCredentials in a Powershell DSC Configuration

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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:

1
2
3
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:

The wrong way to work around this is to suppress this message by specifing PsDscAllowPlainTextPassword=$true as part of the ConfigurationData for this node:

1
2
3
4
5
6
7
8
9
10
$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 thisthis 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:

  1. The Target machine needs to have a certificate in its certificate store with both a public and a private key
  2. We export just the public key portion to a certificate file that we give to the Sending machine.
  3. 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.
  4. On the sending side, whenever we run our Configuration to generate the .mof file, we tell it to use the certificate file to encrypt the PSCredentials with, and Powershell won’t complain anymore about storing passwords in plain text.

diagramSounds 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.

local-certificate-store

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:

1
2
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:

1
2
3
4
5
6
7
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:

1
2
3
4
5
6
7
8
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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:

1
2
3
4
5
6
7
8
9
10
$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 PSCredentials. Applying this configuration to the TargetServer should succeed, because it knows with which certificate to decrypt these credentials:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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-PSSessionStart-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:

1
2
3
$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.