-
Notifications
You must be signed in to change notification settings - Fork 0
Securing Credentials
Important
SPSConfigKit compiles real credentials — SharePoint service accounts, the farm
passphrase, and every PFX password — into the MOF for each node. Encrypting
them with a DSC document-encryption certificate is mandatory. Compiling in
clear text (PSDscAllowPlainTextPassword = $true with no certificate) is not a
supported configuration and must never leave an authoring host.
A DSC v1/v2 MOF is a plain-text file until you encrypt it. Without a
document-encryption certificate, every MSFT_Credential block looks like this:
instance of MSFT_Credential as $c1ref
{
Password = "SuperSecret123!";
UserName = "CONTOSO\\svcspssetup";
};
That is a domain service-account password in clear text. Anyone who can read the
MOF — on the authoring host, on the pull server's Configuration folder, or on
the SMB share — has the farm's credentials. Encryption turns each password into a
CMS blob that only the target node (holder of the certificate's private key) can
decrypt:
instance of MSFT_Credential as $c1ref
{
Password = "-----BEGIN CMS-----\nMIIBsQYJKoZI...\n-----END CMS-----";
UserName = "CONTOSO\\svcspssetup";
};
and the configuration document is marked accordingly:
instance of OMI_ConfigurationDocument
{
Version="2.0.0";
ContentType="PasswordEncrypted";
Name="CfgAppSps";
};
Encryption is driven by a single self-signed Document Encryption certificate
(EKU 1.3.6.1.4.1.311.80.1). The public .cer encrypts credentials at
compile time; the private .pfx, imported on each node, decrypts them at apply
time. The kit wires this up for you.
$pfxPwd = Read-Host 'PFX password' -AsSecureString
.\scripts\init\Initialize-DscEncryption.ps1 -PfxPassword $pfxPwdInitialize-DscEncryption.ps1:
- creates (or reuses) a
CN=DSC Encryptioncertificate valid for 10 years; - exports the public
DscEncryption.cerand the password-protectedDscEncryption.pfxto the share (default\\PULL\Softwarepackages); -
patches every
Cfg*.psd1in the repo: the wildcard'*'AllNodes block getsPSDscAllowPlainTextPassword = $false,CertificateFile = '<share>\DscEncryption.cer', andThumbprint = '<thumbprint>'. Because these live on the wildcard block, every named node inherits them — no per-node duplication. A.bakof each.psd1is created the first time it is patched.
Note
After this step, PSDscAllowPlainTextPassword is $false farm-wide, so
compilation will fail until every target node has imported the .pfx
(next step). That failure is the safety net working as intended.
scripts/init/Initialize-DscNode.ps1 imports the .pfx automatically during node
bootstrap. To do it (or re-do it) by hand on a node:
Import-PfxCertificate -FilePath '\\PULL\Softwarepackages\DscEncryption.pfx' `
-CertStoreLocation Cert:\LocalMachine\My `
-Password (Read-Host 'PFX password' -AsSecureString)Do this on every SharePoint and Office Online Server node. A node that is missing the certificate cannot compile (authoring) or decrypt (apply).
The LCM must be told which certificate to decrypt with. Importing the
.pfxis necessary but not sufficient: the Local Configuration Manager needs itsCertificateIDset to the encryption thumbprint, or it refuses the whole document with "The Local Configuration Manager is not configured with a certificate". The kit handles this for you:
Push — the
Cfg*.ps1configurations setLocalConfigurationManager { CertificateID = $Node.Thumbprint }, so the compiled*.meta.mofalready carries it. Apply it before the main MOF:Set-DscLocalConfigurationManager -Path C:\DSC\MOF\SPS -ComputerName 'APP1' -VerbosePull —
scripts/pull/CfgLcmPull.ps1resolves the thumbprint from the localCN=DSC Encryptioncertificate (or-CertificateThumbprint) and setsCertificateIDin the LCMSettingsautomatically.
.\scripts\sps\CfgAppSps.ps1The generated MOFs now hold CMS-encrypted credential blobs and the
ContentType="PasswordEncrypted" marker.
.\scripts\test\Invoke-MofEncryptionTest.ps1 -MofPath .\scripts\sps\MOFThe Pester guard-rail scans every *.mof and fails with exit code 1 if any
credential is still clear text, or if a MOF carrying credentials is not marked
PasswordEncrypted. Wire it into CI or a release script right after compilation
so a mis-configured encryption certificate can never ship. A healthy run prints:
All N check(s) passed. Every credential in the MOF(s) is encrypted.
To replace the encryption certificate (expiry, compromise, policy):
$pfxPwd = Read-Host 'PFX password' -AsSecureString
.\scripts\init\Initialize-DscEncryption.ps1 -PfxPassword $pfxPwd -Force-Force removes the existing CN=DSC Encryption cert, creates a fresh one, and
refreshes the CertificateFile / Thumbprint values in every Cfg*.psd1.
Re-import the new .pfx on every node, then recompile and re-apply.
-Forcerequires-PfxPassword. Rotating the certificate without exporting the matching.pfxwould leave a stale private key on the share while the.cerand the patched.psd1move to the new thumbprint — every node would then encrypt against a key whose private half it never receives and fail at apply with "Decryption failed". The script refuses-Forcewithout-PfxPasswordand, after export, verifies the.cer, the.pfxand the live certificate all share one thumbprint.
| Symptom | Cause | Fix |
|---|---|---|
| Compilation error: "the certificate ... could not be found" / thumbprint not found | The authoring host cannot read the .cer at CertificateFile, or the node hasn't imported the .pfx
|
Confirm the share path in the patched .psd1; import the .pfx on the node |
| Guard-rail reports clear-text credentials |
PSDscAllowPlainTextPassword is still $true, or the .psd1 wasn't patched |
Run Initialize-DscEncryption.ps1; confirm the wildcard block shows $false + CertificateFile + Thumbprint
|
| Apply fails: "The Local Configuration Manager is not configured with a certificate" | The LCM CertificateID was never set |
Apply the *.meta.mof with Set-DscLocalConfigurationManager (push), or run CfgLcmPull.ps1 (pull), before the main MOF |
| Apply fails: "Decryption failed" | The node holds a different CN=DSC Encryption cert than the one that encrypted the MOF (e.g. a locally-generated cert, or a stale .pfx) |
Compare the share's .cer thumbprint with the node's cert; remove the wrong one and import the correct .pfx. Initialize-DscNode.ps1 now flags this mismatch |
| Apply fails to decrypt on the node | The node is missing the certificate private key | Re-import the .pfx (not just the .cer) into Cert:\LocalMachine\My
|
MOF has ContentType="PasswordEncrypted" but no credentials encrypted |
Mixed state after a partial edit | Delete the MOF, recompile from a clean patched .psd1
|
- Getting Started — where certificate generation sits in the overall workflow.
- Usage — compiling and applying the MOFs.
-
scripts/init/Initialize-DscEncryption.ps1— the generator (full comment-based help). -
scripts/test/Invoke-MofEncryptionTest.ps1— the post-compile guard-rail.