This is the second post of a serie around DSC and my personal journey:
Part 1: Pull server (setup and querying node status)
Part 2: Configure node’s LCM in a bulk operation reading GUID from AD and using individual certificates
Part 3: Automate creation and publishing to pull server of configurations reading Configuration and Environment Data files
In this post I will cover how you can configure the Local Configuration Manager (LCM) of the target nodes to pull their configuration from a pull server. The challenge was to keep track of the used GUID and not to use the same certificate on all nodes in order to Securing the MOF files.
Update 28.08.2015
Update 04.09.2015
You can configure the following properties of the Local Configuration Manager:
Property | Description |
---|---|
AllowModuleOverwrite | Controls whether new configurations downloaded from the configuration server are allowed to overwrite the old ones on the target node. Possible values are True and False. |
CertificateID | GUID a certificate used to secure credentials for access to the configuration. For more information see Want to secure credentials in Windows PowerShell Desired State Configuration?. |
ConfigurationID | Indicates a GUID which is used to get a particular configuration file from a server set up as a “pull” server. The GUID ensures that the correct configuration file is accessed. |
ConfigurationMode | Specifies how the Local Configuration Manager actually applies the configuration to the target nodes. It can take the following values:
|
ConfigurationModeFrequencyMins | Represents the frequency (in minutes) at which the background application of DSC attempts to implement the current configuration on the target node. The default value is 15. This value can be set in conjunction with RefreshMode. When RefreshMode is set to PULL, the target node contacts the configuration server at an interval set by RefreshFrequencyMins and downloads the current configuration. Regardless of the RefreshMode value, at the interval set by ConfigurationModeFrequencyMins, the consistency engine applies the latest configuration that was downloaded to the target node. RefreshFrequencyMins should be set to an integer multiple of ConfigurationModeFrequencyMins. |
Credential | Indicates credentials (as with Get-Credential) required to access remote resources, such as to contact the configuration server. |
DownloadManagerCustomData | Represents an array that contains custom data specific to the download manager. |
DownloadManagerName | Indicates the name of the configuration and module download manager. |
RebootNodeIfNeeded | Certain configuration changes on a target node might require it to be restarted for the changes to be applied. With the value True, this property will restart the node as soon as the configuration has been completely applies, without further warning. If False (the default value), the configuration will be completed, but the node must be restarted manually for the changes to take effect. |
RefreshFrequencyMins | Used when you have set up a “pull” server. Represents the frequency (in minutes) at which the Local Configuration Manager contacts a “pull” server to download the current configuration. This value can be set in conjunction with ConfigurationModeFrequencyMins. When RefreshMode is set to PULL, the target node contacts the “pull” server at an interval set by RefreshFrequencyMins and downloads the current configuration. At the interval set by ConfigurationModeFrequencyMins, the consistency engine then applies the latest configuration that was downloaded to the target node. If RefreshFrequencyMins is not set to an integer multiple of ConfigurationModeFrequencyMins, the system will round it up. The default value is 30. |
RefreshMode | Possible values are Push (the default) and Pull. In the “push” configuration, you must place a configuration file on each target node, using any client computer. In the “pull” mode, you must set up a “pull” server for Local Configuration Manager to contact and access the configuration files. |
Note: I took the table from this KB article.
As you can see there are plenty properties to configure. But the major important ones are the following:
- CertificateID
- ConfigurationID
As described in the table CertificateID is used to determine, which certificate the target node is using to decrypt the credentials. Whereas the ConfigurationID is used to determine, which configuration to pull and apply.
There are many examples out there, which are using the same certificate on all nodes in order to en/decrypt credentials. That’s fine and easy as long as this fits your company’s needs. But what when you cannot use of the same certificate or you don’t want to? Then it’s going to be somehow complicated as you would need to keep track of any used certificate on each node. Similar the same applies to the GUID for the ConfigurationID. It’s going to be hard to keep track for which node which GUID is used.
CertificateID
As in my environment an Enterprise CA exist and each server anyways got its own certificate, I thought it would be a good idea to use this existing certificate. The challenge is now to get from the node’s certificate the thumbprint and use this one to configure the LCM. Luckily there already exist a function, which covers this in some way. Just scroll down on the KB article about Securing the MOF files. At the end you will find an example on the whole process with the function called Get-EncryptionCertificate.
The function is searching on a node for a certificate, which is valid and the node has a private key for. If such a certificate exist, it will be exported locally on the node first and then copied to a folder on the server from where the function was run from.
I thought this is almost perfect, but I needed to tweak it a little bit in order to meet my needs. Therefore I change the function as follows:
Parameters
I added two parameters:
- Path: Here you define the folder to where the certificate will be exported. The folder will be automatically created on the node if it doesn’t exist. This will only happen, when you use the switch DoExport. But you have to make sure that the same path exists on the server from where you run the function!
- DoExport: Will try to export the certificate as a first step locally on the node and then copy it to the server you are calling the function from.
Certificate check
The script only checks if the certificate is valid and the node has the corresponding private key. I extended this to the following criteria:
- $_.PrivateKey.KeyExchangeAlgorithm: Node has the corresponding private key
- $_.Verify(): the certificate is valid
- $_.DnsNameList -match $env:Computername
$Server: The DNS name of the server is in the certificate - $_.Issuer -ne $_.Subject: the certificate is NOT a selfsigned one
As I’m sorting the certificates based on the attribute NOTAFTER ($certificates = dir Cert:\LocalMachine\my | sort NOTAFTER -Descending), I get the certificate, which is valid for the longest time. Just in case there are multiple certificates matching the criteria. The other ones would also be exported.
Firewall rule
I couldn’t copy the exported certificate to the pull server sometimes via SMB.I had to enable a rule: File and Printer Sharing (SMB-In) will be enabled for all profiles. I’m still working on this as I don’t like this as it is.
Update 28.08.2015:
I came across an issue, while exporting a certificate. This is now fixed.
Update 04.09.2015:
Minor fix for certificate filtering.
Here is the modified function:
function Get-EncryptionCertificate { [CmdletBinding()] param ( [string]$ComputerName, [string]$Path, [switch]$DoExport ) If ($DoExport) { [array]$returnValue= Invoke-Command -ComputerName $computerName -ScriptBlock { $certificates = dir Cert:\LocalMachine\my | sort NOTAFTER -Descending | ?{$_.PrivateKey.KeyExchangeAlgorithm -and $_.Verify() -and ($_.DnsNameList -match $env:ComputerName) -and ($_.Issuer -ne $_.Subject)} | select -First 1 $FW = (Get-NetFirewallRule -DisplayName "File and Printer Sharing (SMB-In)").Enabled $Folder = $using:Path $Server = $using:Computername if ($certificates) { # Create the folder to hold the exported public key if (! (Test-Path $Folder)) { md $Folder | Out-Null } #Enable FW #netsh adv firew set rule name="File and Printer Sharing (SMB-In)" dir=in profile=any new enable=yes If ($FW -eq "False") { #Write-Host "Enable FW rule!" Enable-NetFirewallRule -DisplayName "File and Printer Sharing (SMB-In)" Sleep 2 } # Export the public key to a well known location $certPath = Export-Certificate -Cert $certificates -FilePath (Join-Path -path $Folder -childPath "$Server.cer") -Force # Return the thumbprint, and exported certificate path return @($certificates.Thumbprint,$certPath); } } # Copy the exported certificate locally $destinationPath = join-path -Path "$Path" -childPath "$Computername.cer" Copy-Item -Path (join-path -path \\$Computername -childPath $returnValue[1].FullName.Replace(":","$")) $destinationPath -Force | Out-Null } Else { [array]$returnValue= Invoke-Command -ComputerName $ComputerName -ScriptBlock { $certificates = dir Cert:\LocalMachine\my | sort NOTAFTER -Descending | ?{$_.PrivateKey.KeyExchangeAlgorithm -and $_.Verify() -and ($_.DnsNameList -match $env:ComputerName) -and ($_.Issuer -ne $_.Subject)} | select -First 1 if ($certificates) { # Return the thumbprint return $certificates.Thumbprint; } } } # Return the thumbprint, and exported certificate path return @($returnValue[0],$returnValue[1].FullName) }
ConfigurationID
As we are using a pull server we need we need a MOF file, which needs to have a GUID as name (e.g.:7d6d6ee7-d504-497f-ad8f-72dba244e186.mof). Besides this you also need a checksum for this file. But this will be part of the next post.
You can easily create a new GUID in many ways. One example to do so in Powershell would be like this:
$GUID= [guid]::NewGuid().Guid
But you will have the problem to keep track of the nodes names and their corresponding GUIDs. This can be really challenging especially when you have to update configurations.
I thought by myself it would be a good idea to use an existing GUID for this, which is bound to the node:
Why not just use the AD attribute objectguid of the node’s computer account?
For my scenario this was the perfect solution. If you have to deal with non-domain joined nodes, you will need to find another way.
Assuming $Name is the target node’s NetBIOS name it looks like this:
$GUID = ([guid]([adsisearcher]”(samaccountname=$Name`$)”).FindOne().Properties[“objectguid”][0]).Guid
If $Name is the target node’s DNS name it looks like this:
$GUID = ([guid]([adsisearcher]”(dNSHostName=$Name)”).FindOne().Properties[“objectguid”][0]).Guid
The difference is that I use the property samaccountname in the first and dNSHostname in the second search to find the object in AD.
Set-DSCLCMforPullServer
Putting all together I wrote a script, which imports a CSV file(could also be the output from the Exchange 2013 Server Role Requirements Calculator) and configures the LCM as you want. You only have to make sure to have a column called ServerName in this file. It takes care of the above mentioned important properties. You can download it here. The script has the following parameters:
Parameter |
Description |
---|---|
ServerCSVFile | Full path to the CSV file containing the nodes |
MOFsPath | Path to a folder to store the generated MOF files |
PullServer | The name of the DSC pull server |
Port | The port of the PSDSCComplianceServer component of a DSC pull server. The default is 8080. |
ConfigurationMode | The mode of the DSC engine on the node. You can set it one of the following values:”ApplyAndAutoCorrect”, “ApplyOnly”, “ApplyAndMonitor”. The default is “ApplyAndAutoCorrect”. |
RefreshFrequencyMins | To specify the LCM property RefreshFrequencyMins. Default is 30. |
ConfigurationModeFrequencyMins | To specify the LCM property ConfigurationModeFrequencyMins. Default is 30. |
RebootNodeIfNeeded | To specify the LCM property RebootNodeIfNeeded. Default is $True. |
AllowModuleOverwrite | To specify the LCM property AllowModuleOverwrite. Default is $True. |
ForceReboot | To force a reboot of the node after you have configured the LCM. Default is $False. |
SkipPing | To bypass connectivity test. Maybe useful when ICMP is not allowed on FW, which is the default. Default is $False. |
CertThumbprint | If you want to specify a thumbprint of the certificate use this parameter. All nodes will use the same thumbprint. |
Here are some examples and screenshoots:
Set-DSCLCMforPullServer.ps1 -ServerCSVFile C:\DSC\CSVs\LabServers.csv -MOFsPath C:\DSC\Config -PullServer fabpullsrv.fabrikam.local
As you can see there was a problem getting the certificate from fabex03 and on fabex02 and fabex04 the connectivity check failed. Therefore those 2 servers will be skipped. If you are sure that those servers would be up just use the parameter SkipPing.
The corresponding CSV file looks like this:
ServerName,FQDN,Location,DAGID,Role
fabex01.fabrikam.local,fabex01.fabrikam.local,amer,dag1,FirstDAGMember
fabex02,fabex02.fabrikam.local,amer,dag1,AdditionalDAGMember
fabex03,fabex03.fabrikam.local,emea,dag1,AdditionalDAGMember
fabex04,fabex04.fabrikam.local,emea,dag1,AdditionalDAGMember
You can also use the verbose switch:
This was the second part. In the third one I will shared my approach how I incorporated the method described above into the process of creating MOF files for nodes.
Feedback is always welcome!
Pingback: Automate Exchange installation and configuration with DSC Part 1:Pull server | The clueless guy
Pingback: Automate Exchange installation and configuration with DSC Part 3:Automate creation and publishing to pull server | The clueless guy
Pingback: To run iPowershell Desired State Configuration(DSC):How to enforce a consistency check? | The clueless guy
Pingback: 为Pull服务器设置DSCLCM - Exchange中文站
Pingback: Tips and tricks for DSC:Modify LCM | The clueless guy