Building the basics Part 1 | PKI: RootCA

edit:7/26/2015

  • Added xNetworking and removed requirement for DHCP.
  • Please NOTE that this method does not currently work with WMF5 as the Host when targeting a WMF4 WM.

Invoke-DSCBuild -Target PKI | Test-Lab

Before I can get into the really fun stuff with DSC I need the prerequisites for a secure DSC environment. This involves a few things. The minimum is a Certificate Authority, the other is a HTTPS pull server. For my Lab I’m also going to also have a Domain Controller, because most enterprises will have a domain, and because it simplifies permissions.

  1. RootCA
  2. Domain Controller
  3. Enterprise CA
  4. Pull Server

Initial server build and Self Signed Certificate

I’m going to use my patched image as a baseline. Now because xAdcsDeployment and File resources require Credentials, I will have to create a self signed certificate to secure the passwords. I don’t want to use plaintext in a .MOF file.

Now I created a helper function that does the following.

Start-Deploy.ps1

  1. Copy the patch VHDX
  2. Mount to a temporary folder
  3. copy in files
    1. unattnd.xml (same one I used for the Patched Image creation)
    2. FirstRun.ps1 script
    3. AtStartup.ps1 script (SCC-Init.ps1)
    4. any other supporting scripts (New-SelfSignedCertificateEx)
  4. Save VHDX
  5. Start VM
  6. Wait for VM to stop

FirstRun.sp1:

  1. Create the AtStartup job to run AtStartup.ps1
  2. reboot
#requires -Version 2 -Modules ScheduledTasks
Start-Transcript -Path $PSScriptRoot\FirstRun.log
#Create AtStartup Task.
$Paramaters = @{
Action = New-ScheduledTaskAction -Execute '%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\PSTemp\AtStartup.ps1'
Trigger = New-ScheduledTaskTrigger -AtStartup
Settings = New-ScheduledTaskSettingsSet
}
$TaskObject = New-ScheduledTask @Paramaters
Register-ScheduledTask AtStartup -InputObject $TaskObject -User 'nt authority\system' -Verbose
Start-Sleep -Seconds 20
Restart-Computer -Verbose -Force
Stop-Transcript
view raw FirstRun.ps1 hosted with ❤ by GitHub

SCC-Init.sp1

  1. Start a transcript to SCC-Init.log
  2. check if pfx was copied in, (and I did not)
    1. if PFX present install that.
  3. if not, use New-SelfSignedCertificateEx (from Script Center)to create a certificate
  4. Export the certificate to a CER file
  5. shutdown the VM

Extracting the Certificate

Once we have a self signed certificate for encrypting passwords we need to get if off the VM and back to the host.

Again I created a helper function for this.

Get-VMCert.ps1

  1. Verify the VM is off
    1. if on, run stop-vm
  2. Mount the VHDX to a temporary folder
  3. copy the file from the relative path inside the VHDX to the supplied path on the host
  4. Dis-Mount the VHDX discarding changes changes.

Creating the MOF

Now due to how the ISE reacts to the having a “Configuration” in a file I separated out the configuration to a .ps1(Intelisence tends to timeout in scripts with a configuration in the ISE in WMF 4)

RootCA_Config.ps1

  1. Get the Certificate thumbprint from the CER file.
  2. Import Credentials from clixml for:
    1. Local Administrator (used to change the built-in Administrator and for setting up a Certificate Authority)
    2. Remote user needed to access the source.wim for adding features
  3. Create a hash table with AllNodes to pass the certificate thumbprint and CER file path with
  4. Configuration Block
    1. Set the IP Address
    2. Set Computer name
    3. Copy the source.wim from a remote share (my win8 workstation on the lab network)
      1. Now this point was something of a pain. In a production environment you don’t want to copy this file over. However due to how DISM (what WindowsFeature and install-WindowsFeatuere are wrappers for) Cant access a network share (even when guest in enabled) on WMF4. (WMF5 adds the ability to run each resource in specified user accounts and may fix this) The LCM runs as the local System account, In a Domain environment you can grant permissions via “Domain Computers” to a share and point the WindowsFeature resorce at the UNC to Source.WIM
    4. Install the Certificate Authority WindowsFeature
    5. Initialize the Certficate Authority useing xAdcsCertificationAuthority
    6. Set the location for the Certificate Revocation list and re-export
      1. This is done via a script resource, as xAdcsCertificationAuthority does not currently have a way to do this
  5. Create the LocalHost.Meta.MOF and LocalHost.MOF files

I had planed to set the IP address, but xNetworking was causing the DSC to hang in my tests.
So I had to turn on DHCP on my Voys router used in the Lab. I hope to track this down and submit a fix.

This issue was resolved and noted below

if (-not ($vmname))
{
$vmname = 'Root-CA'
}
$certpath = "$PSScriptRoot\$vmname.crt"
if (-not(Test-Path $certpath))
{
throw 'Certificate not found'
}
$cert = "$PSScriptRoot\$vmname.crt"
$certPrint = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
$certPrint.Import("$cert")
try
{
$LocalAdmin = Import-Clixml -Path $PSScriptRoot\$vmname.LocalAdminCred.xml
$RemoteUser = Import-Clixml -Path $PSScriptRoot\$vmname.RemoteUserCred.xml
}
catch
{
throw 'error importing passwords from xml'
}
$ConfigData = @{
AllNodes = @(
@{
NodeName = 'localhost'
CertificateFile = "$PSScriptRoot\$vmname.crt"
Thumbprint = "$($certPrint.Thumbprint)"
#PSDscAllowPlainTextPassword = $true;
};
)
}
configuration RootCA
{
Import-DscResource -ModuleName xComputerManagement, xAdcsDeployment, xNetworking,
PSDesiredStateConfiguration
node $AllNodes.NodeName
{
xIPAddress Ethernet
{
InterfaceAlias = 'Ethernet 4'
IPAddress = '10.20.1.51'
AddressFamily = 'IPv4'
SubnetMask = 24
}
User LocalAdmin
{
UserName = 'Administrator'
Disabled = $false
Ensure = 'Present'
Password = $LocalAdmin
PasswordChangeNotAllowed = $false
PasswordNeverExpires = $true
}
xComputer ComputerName
{
Name = 'Root-CA'
WorkGroupName = 'Workgroup'
}
File Source
{
DestinationPath = 'c:\Source.wim'
Credential = $RemoteUser
Ensure = 'Present'
SourcePath = '\\10.20.1.41\DSC\WIM\Source.wim'
Type = 'File'
DependsOn = '[xIPAddress]Ethernet'
}
WindowsFeature ADCS_Cert_Authority
{
Name = 'ADCS-Cert-Authority'
DependsOn = '[File]Source'
Ensure = 'Present'
Source = 'WIM:c:\Source.wim:1'
}
xAdcsCertificationAuthority Root_CA
{
CAType = 'StandaloneRootCA'
Credential = $LocalAdmin
CACommonName = 'Root-CA'
DependsOn = '[WindowsFeature]ADCS_Cert_Authority', '[xComputer]ComputerName'
Ensure = 'Present'
}
Script 'SetRevocationList'
{
GetScript = {
(Get-CACrlDistributionPoint).Uri
}
SetScript = {
$crllist = Get-CACrlDistributionPoint; foreach ($crl in $crllist)
{
Remove-CACrlDistributionPoint $crl.uri -Force
}
Add-CACRLDistributionPoint -Uri C:\Windows\System32\CertSrv\CertEnroll\%3%8.crl -PublishToServer -Force
Add-CACRLDistributionPoint -Uri http://pki.contoso.com/pki/%3%8.crl -AddToCertificateCDP -Force
$aialist = Get-CAAuthorityInformationAccess; foreach ($aia in $aialist)
{
Remove-CAAuthorityInformationAccess $aia.uri -Force
}
certutil.exe -setreg CA\CRLOverlapPeriodUnits 12
certutil.exe -setreg CA\CRLOverlapPeriod 'Hours'
certutil.exe -setreg CA\ValidityPeriodUnits 10
certutil.exe -setreg CA\ValidityPeriod 'Years'
certutil.exe -setreg CA\AuditFilter 127
Restart-Service -Name certsvc
certutil.exe -crl
}
TestScript = {
if ((Get-CACrlDistributionPoint).Uri -contains 'http://pki.contoso.com/pki/<CAName><CRLNameSuffix&gt;.crl')
{
return $true
}
else
{
return $false
}
}
DependsOn = '[xAdcsCertificationAuthority]Root_CA'
}
LocalConfigurationManager
{
CertificateId = $node.Thumbprint
ConfigurationMode = 'ApplyandAutoCorrect'
RebootNodeIfNeeded = $true
}
}
}
RootCA -ConfigurationData $ConfigData -OutputPath "$PSScriptRoot\RootCA"

Starting DSC

Armed with the required .MOF files I need to get them copied into the VM and and start DSC. Once again a helper function, (pardon my poor naming conventions)

Set-VmLcm.ps1

  1. Stop the VM if it’s running
  2. Mount the VHDX to a temp folder
  3. Copy in a new AtStartup.ps1 (DSC-Init.ps1)
  4. If LocalHostMofFolder set
    1. Copy in LocalHost.mof and LocalHost.Meta.Mof
  5. Else copy
    1. CofnigMof as Pending.mof
  6. Create metaconfig.mof from supplied string
  7. Dismount VHDX saving changes

I initially was going to go with copy in Pending and set Metaconfig.mof directly then use  Invoke-CimMethod, as shown by the PowerShell team blog However after fighting with a failing config I went to Start-DscConfiguration. This did not fix my issue with xNetworking but I have had better luck with this method.

This issue actually had to do having WMF5 on the host. Removing WMF5 preview and returning to WMF4 and my problems were resolved. so I have added xNetworking back in.

However I’m staying with the current use of start-DSCConfiguration rather than a WMI call. The debugging is simpler with -verbose.

DSC-Init.ps1

  1. Starts a transcript to DSC-Init.log
  2. Unregister the AtStartup scheduled task
  3. Remove c:\Unattend.xml
    1. The plain text password contained inside will be changed by DSC but not point in leaving a mess.
  4. Enable the Analytic and Debug logs for DSC
  5. Run Set-DSCLocalConfigurationManager
  6. Run Start-DscConfiguration with -Wait -Verbose
  7. Stop-Transcript

Enabling the log channels and using -wait -verbose with a transcript helps a lot with debugging

Putting it all together

I put all the functions into a quickly made module to simplify importing them.

#requires -Version 1
. $PSScriptRoot\Get-VmCert.ps1
. $PSScriptRoot\Set-VmLcm.ps1
. $PSScriptRoot\Start-Deploy.ps1
view raw Functions.psm1 hosted with ❤ by GitHub

I created a wrapper script that will put all this together and build my Root-CA

Build-RootCA.ps1

  1. Set the a number of variables
    1. VM-Name
    2. Path to DSC Config Sript
    3. Start-Deploy parameters for splating
    4. Get-VmCert parameters for splating
    5. Set-VmMof Parameters fro splatting
    6. Create the clixml files containing the Passwords
  2. Import-Module on the sub folder holding all my functions
  3. Start-Deploy
  4. Get-VMCert
  5. Dot Source the DSC Config Script
  6. Set-VmLcm
  7. Start the VM
  8. Remove-Module -Name Functions

The reason for saving the passwords to XML avoids the need to enter credentials on subsequent runs of the script. I want to be able to tare down and rebuild the lab from scratch at any time.

One other file you will need is the unattend.xml

The folder structure for all of this is

G:\BuildLab
│   Build-DC.ps1
│   Build-RootCA.ps1
│   Root-CA.LocalAdminCred.xml
│   Root-CA.RemoteUserCred.xml
│   RootCA_Config.ps1
│   Unattend.xml
│
├───Functions
│       Functions.psd1
│       Functions.psm1
│       Get-VmCert.ps1
│       Set-VmLcm.ps1
│       Start-Deploy.ps1
│       Unattend.xml
│
├───Helpers
│       New-SelfSignedCertificateEx.ps1
│
├───Resorces_RootCA
│   ├───xAdcsDeployment
│   ├───xComputerManagement
│   ├───xDscDiagnostics
│   └───xNetworking
│
├───RootCA
│       localhost.meta.mof
│       localhost.mof
│
└───VMScripts
        DSC-Init.ps1
        FirstRun.ps1
        SSC-Init.ps1

Wrapping up

This script took a lot longer then planed and I got to use xDscDiagnostics a lot to track errors. That is outside of the scope of this post but I plan to write about it at some time. I did have to enable DHCP on the router, and that is something I hope to address and update this post. I want to use a DSC configured DHCP. but in this case I have a clasic chicken and egg issue. I need the CA, DC and Pull server first.

One thought on “Building the basics Part 1 | PKI: RootCA

Leave a comment