From 6ec2eb61a02f9e55ef5b8d22a5ca61ca53ca05e7 Mon Sep 17 00:00:00 2001 From: heqnx Date: Fri, 11 Jul 2025 21:55:20 +0300 Subject: added initial setup for domain controller --- ansible/files/adcs/ADCSTemplate/ADCSTemplate.psm1 | 466 ++++++++++++++++++++++ 1 file changed, 466 insertions(+) create mode 100644 ansible/files/adcs/ADCSTemplate/ADCSTemplate.psm1 (limited to 'ansible/files/adcs/ADCSTemplate/ADCSTemplate.psm1') diff --git a/ansible/files/adcs/ADCSTemplate/ADCSTemplate.psm1 b/ansible/files/adcs/ADCSTemplate/ADCSTemplate.psm1 new file mode 100644 index 0000000..39da019 --- /dev/null +++ b/ansible/files/adcs/ADCSTemplate/ADCSTemplate.psm1 @@ -0,0 +1,466 @@ +#requires -Version 5.0 -Modules ActiveDirectory + +Function Get-RandomHex { +param ([int]$Length) + $Hex = '0123456789ABCDEF' + [string]$Return = $null + For ($i=1;$i -le $length;$i++) { + $Return += $Hex.Substring((Get-Random -Minimum 0 -Maximum 16),1) + } + Return $Return +} + +Function IsUniqueOID { +param ($cn,$TemplateOID,$Server,$ConfigNC) + $Search = Get-ADObject -Server $Server ` + -SearchBase "CN=OID,CN=Public Key Services,CN=Services,$ConfigNC" ` + -Filter {cn -eq $cn -and msPKI-Cert-Template-OID -eq $TemplateOID} + If ($Search) {$False} Else {$True} +} + +Function New-TemplateOID { +Param($Server,$ConfigNC) + <# + OID CN/Name [10000000-99999999].[32 hex characters (MD5hash)] + OID msPKI-Cert-Template-OID [Forest base OID].[1000000-99999999].[10000000-99999999] <--- second number same as first number in OID name + #> + do { + $OID_Part_1 = Get-Random -Minimum 10000000 -Maximum 99999999 + $OID_Part_2 = Get-Random -Minimum 10000000 -Maximum 99999999 + $OID_Part_3 = Get-RandomHex -Length 32 + $OID_Forest = Get-ADObject -Server $Server ` + -Identity "CN=OID,CN=Public Key Services,CN=Services,$ConfigNC" ` + -Properties msPKI-Cert-Template-OID | + Select-Object -ExpandProperty msPKI-Cert-Template-OID + $msPKICertTemplateOID = "$OID_Forest.$OID_Part_1.$OID_Part_2" + $Name = "$OID_Part_2.$OID_Part_3" + } until (IsUniqueOID -cn $Name -TemplateOID $msPKICertTemplateOID -Server $Server -ConfigNC $ConfigNC) + Return @{ + TemplateOID = $msPKICertTemplateOID + TemplateName = $Name + } +} + + +<# +.SYNOPSIS +Returns the properties of either a single or all Active Directory Certificate Template(s). +.DESCRIPTION +Returns the properties of either a single or list of Active Directory Certificate Template(s) +depending on whether a DisplayName parameter was passed. +.PARAMETER DisplayName +Name of an AD CS template to retrieve. +.PARAMETER Server +FQDN of Active Directory Domain Controller to target for the operation. +When not specified it will search for the nearest Domain Controller. +.EXAMPLE +PS C:\> Get-ADCSTemplate +.EXAMPLE +PS C:\> Get-ADCSTemplate -DisplayName PowerShellCMS +.EXAMPLE +PS C:\> Get-ADCSTemplate | Sort-Object Name | ft Name, Created, Modified +.EXAMPLE +PS C:\> ###View template permissions +(Get-ADCSTemplate pscms).nTSecurityDescriptor +(Get-ADCSTemplate pscms).nTSecurityDescriptor.Sddl +(Get-ADCSTemplate pscms).nTSecurityDescriptor.Access +ConvertFrom-SddlString -Sddl (Get-ADCSTemplate pscms).nTSecurityDescriptor.sddl -Type ActiveDirectoryRights +.NOTES +Requires Enterprise Administrator permissions, since this touches the AD Configuration partition. +#> +Function Get-ADCSTemplate { +param( + [parameter(Position=0)] + [string] + $DisplayName, + + [string] + $Server = (Get-ADDomainController -Discover -ForceDiscover -Writable).HostName[0] +) + If ($PSBoundParameters.ContainsKey('DisplayName')) { + $LDAPFilter = "(&(objectClass=pKICertificateTemplate)(displayName=$DisplayName))" + } Else { + $LDAPFilter = '(objectClass=pKICertificateTemplate)' + } + + $ConfigNC = $((Get-ADRootDSE -Server $Server).configurationNamingContext) + $TemplatePath = "CN=Certificate Templates,CN=Public Key Services,CN=Services,$ConfigNC" + Get-ADObject -SearchScope Subtree -SearchBase $TemplatePath -LDAPFilter $LDAPFilter -Properties * -Server $Server +} + + +<# +.SYNOPSIS +Adds an ACL to an Active Directory Certificate template. +.DESCRIPTION +Adds an ACL to an Active Directory Certificate template. +Default permission is read (without the Enroll or AutoEnroll switches). +.PARAMETER DisplayName +Name of an AD CS template to receive the ACL. +.PARAMETER Server +FQDN of Active Directory Domain Controller to target for the operation. +When not specified it will search for the nearest Domain Controller. +.PARAMETER Type +ACL type: Allow or Deny +.PARAMETER Identity +String or string array of Active Directory identities (users or groups) +.PARAMETER Enroll +Set the Enroll permission +.PARAMETER AutoEnroll +Set the AutoEnroll permission +.EXAMPLE +PS C:\> Set-ADCSTemplateACL -DisplayName PowerShellCMS -Type Allow -Identity 'CONTOSO\Servers Group' -Enroll +.EXAMPLE +PS C:\> Set-ADCSTemplateACL -DisplayName PowerShellCMS -Type Allow -Identity 'CONTOSO\Servers Group','CONTOSO\Workstations Group' -Enroll -AutoEnroll +.EXAMPLE +PS C:\> Set-ADCSTemplateACL -DisplayName PowerShellCMS -Type Deny -Identity 'CONTOSO\Servers Group' +.NOTES +Requires Enterprise Administrator permissions, since this touches the AD Configuration partition. +#> +Function Set-ADCSTemplateACL { +param( + [parameter(Mandatory)] + [string]$DisplayName, + [string]$Server = (Get-ADDomainController -Discover -ForceDiscover -Writable).HostName[0], + [ValidateSet('Allow','Deny')] + [string]$Type = 'Allow', + [string[]]$Identity, + [switch]$Enroll, + [switch]$AutoEnroll +) + ## Potential issue here that the AD: drive may not be targetting the selected DC in the -SERVER parameter + $TemplatePath = "AD:\" + (Get-ADCSTemplate -DisplayName $DisplayName -Server $Server).DistinguishedName + $acl = Get-ACL $TemplatePath + $InheritedObjectType = [GUID]'00000000-0000-0000-0000-000000000000' + ForEach ($Group in $Identity) { + $account = New-Object System.Security.Principal.NTAccount($Group) + $sid = $account.Translate([System.Security.Principal.SecurityIdentifier]) + + If ($Type -ne 'Deny') { + # Read, but only if Allow + $ObjectType = [GUID]'00000000-0000-0000-0000-000000000000' + $ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ` + $sid, 'GenericRead', $Type, $ObjectType, 'None', $InheritedObjectType + $acl.AddAccessRule($ace) + } + + If ($Enroll) { + $ObjectType = [GUID]'0e10c968-78fb-11d2-90d4-00c04f79dc55' + $ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ` + $sid, 'ExtendedRight', $Type, $ObjectType, 'None', $InheritedObjectType + $acl.AddAccessRule($ace) + } + + If ($AutoEnroll) { + $ObjectType = [GUID]'a05b8cc2-17bc-4802-a710-e7c15ab866a2' + $ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule ` + $sid, 'ExtendedRight', $Type, $ObjectType, 'None', $InheritedObjectType + $acl.AddAccessRule($ace) + } + } + Set-ACL $TemplatePath -AclObject $acl +} + + +<# +.SYNOPSIS +Returns a JSON string with the properties of an Active Directory certificate template. +.DESCRIPTION +Returns a JSON string with the properties of an Active Directory certificate template. +By default returns only the PKI-related properties of the object. These properties are +sufficient for passing to the New-ADCSTemplate function. +.PARAMETER DisplayName +DisplayName for the certificate to export. +.PARAMETER Server +FQDN of Active Directory Domain Controller to target for the operation. +When not specified it will search for the nearest Domain Controller. +.PARAMETER Detailed +Includes all ADObject properties of the template. These are not required for +use with the New-ADCSTemplate function. +.NOTES +C.R.U.D. AD CS Template Operations in this module. +No longer have to use the cert GUI to clone a template and build a new one. +Create one manually the first time in the GUI, then export it to JSON. +Pass the JSON in your new environment (file, here string, DSC, etc.) to build from scratch. +Requires Enterprise Administrator permissions, since this touches the AD Configuration partition. +.EXAMPLE +PS C:\> Export-ADCSTemplate -DisplayName PowerShellCMS +.EXAMPLE +PS C:\> Export-ADCSTemplate -DisplayName PowerShellCMS -Detailed +.EXAMPLE +### Backup all the templates to JSON +md C:\ADCSTemplates -ErrorAction SilentlyContinue +cd C:\ADCSTemplates +(Get-ADCSTemplate).name | ForEach-Object {"Exporting $_"; Export-ADCSTemplate -DisplayName $_ | Out-File .\$_.json -Force} +dir +.EXAMPLE +PS C:\> New-ADCSTemplate -DisplayName PowerShellCMS-NEW -JSON (Export-ADCSTemplate -DisplayName PowerShellCMS-OLD) +#> +Function Export-ADCSTemplate { +param( + [parameter(Mandatory)] + [string]$DisplayName, + [string]$Server = (Get-ADDomainController -Discover -ForceDiscover -Writable).HostName[0], + [switch]$Detailed # Detailed output is not required for export/import. Use for documentation/backup purposes. +) + If ($Detailed) { + Get-ADCSTemplate -DisplayName $DisplayName -Server $Server | + ConvertTo-Json + } Else { + Get-ADCSTemplate -DisplayName $DisplayName -Server $Server | + Select-Object -Property name, displayName, objectClass, flags, revision, *pki* | + ConvertTo-Json + } +} + +<# +.SYNOPSIS +Creates a new Active Directory Certificate Services template based on a JSON export. +.DESCRIPTION +Creates a new Active Directory Certificate Services template based on a JSON export. +Optionally can permission and publish the template (best practice). +.PARAMETER DisplayName +DisplayName for the certificate template to create. This does not have to match +the original name of the exported template. +.PARAMETER JSON +JSON string output from Export-ADCSTemplate. Defines the template to create. +Can be retrieved from file using Get-Content -Raw. +.PARAMETER Server +FQDN of Active Directory Domain Controller to target for the operation. +When not specified it will search for the nearest Domain Controller. +.PARAMETER Identity +String or string array of Active Directory identities (users or groups). +This is optional for permissioning the template. +.PARAMETER AutoEnroll +Default permission is Read and Enroll. Use this switch to also grant AutoEnroll +to the identity. Only used when Identity parameter is used. +.PARAMETER Publish +Publish the template to *ALL* Certificate Authority issuers. Use with caution +in production environments. You may want to manually publish to only specific +Certificate Authorities in production. In a lab this is ideal. +.NOTES +This function does not use the official (complicated) API for PKI management. +Instead it creates the exact same AD objects that are generated by the API, +including AD forest-specific OIDs. +Requires Enterprise Administrator permissions, since this touches the AD Configuration partition. +.EXAMPLE +PS C:\> New-ADCSTemplate -DisplayName PowerShellCMS -JSON (Get-Content .\pscms.json -Raw) +.EXAMPLE +PS C:\> New-ADCSTemplate -DisplayName PowerShellCMS -JSON (Get-Content .\pscms.json -Raw) -Server dc1.contoso.com -Identity G_DSCNodes -AutoEnroll -Publish + +# From a client configured for AD CS autoenrollment: +$Req = @{ + Template = 'PowerShellCMS' + Url = 'ldap:' + CertStoreLocation = 'Cert:\LocalMachine\My' +} +Get-Certificate @Req +# Note: If you have the Carbon module installed, it conflicts with Get-Certificate native cmdlet. + +$DocEncrCert = (dir Cert:\LocalMachine\My -DocumentEncryptionCert | Sort-Object NotBefore)[-1] +Protect-CmsMessage -To $DocEncrCert -Content "Encrypted with my new cert from the new template!" +.EXAMPLE +PS C:\> New-ADCSTemplate -DisplayName PowerShellCMS-NEW -JSON (Export-ADCSTemplate -DisplayName PowerShellCMS-OLD) +#> +Function New-ADCSTemplate { +param( + [parameter(Mandatory)] + [string]$DisplayName, # name in JSON export is ignored + [parameter(Mandatory)] + [string]$JSON, + [string]$Server = (Get-ADDomainController -Discover -ForceDiscover -Writable).HostName[0], + [string[]]$Identity, # = "$((Get-ADDomain).NetBIOSName)\Domain Computers", + [switch]$AutoEnroll, + [switch]$Publish +) + ### Put GroupName and AutoEnroll into a parameter set + + # Manually import AD module to get AD: drive used later for permissions + Import-Module ActiveDirectory -Verbose:$false + + $ConfigNC = $((Get-ADRootDSE -Server $Server).configurationNamingContext) + + #region CREATE OID + <# + CN : 14891906.F2AC4390685318BD1D950A66EDB50FF4 + DisplayName : TemplateNameHere + DistinguishedName : CN=14891906.F2AC4390685318BD1D950A66EDB50FF4,CN=OID,CN=Public Key Services,CN=Services,CN=Configuration,DC=contoso,DC=com + dSCorePropagationData : {1/1/1601 12:00:00 AM} + flags : 1 + instanceType : 4 + msPKI-Cert-Template-OID : 1.3.6.1.4.1.311.21.8.11489019.14294623.5588661.594850.12204198.151.6616009.14891906 + Name : 14891906.F2AC4390685318BD1D950A66EDB50FF4 + ObjectCategory : CN=ms-PKI-Enterprise-Oid,CN=Schema,CN=Configuration,DC=contoso,DC=com + ObjectClass : msPKI-Enterprise-Oid + #> + $OID = New-TemplateOID -Server $Server -ConfigNC $ConfigNC + $TemplateOIDPath = "CN=OID,CN=Public Key Services,CN=Services,$ConfigNC" + $oa = @{ + 'DisplayName' = $DisplayName + 'flags' = [System.Int32]'1' + 'msPKI-Cert-Template-OID' = $OID.TemplateOID + } + New-ADObject -Path $TemplateOIDPath -OtherAttributes $oa -Name $OID.TemplateName -Type 'msPKI-Enterprise-Oid' -Server $Server + #endregion + + #region CREATE TEMPLATE + # https://docs.microsoft.com/en-us/powershell/dsc/securemof#certificate-requirements + # https://blogs.technet.microsoft.com/option_explicit/2012/04/09/pki-certificates-and-the-x-509-standard/ + # https://technet.microsoft.com/en-us/library/cc776447(v=ws.10).aspx + $import = $JSON | ConvertFrom-Json + $oa = @{ 'msPKI-Cert-Template-OID' = $OID.TemplateOID } + ForEach ($prop in ($import | Get-Member -MemberType NoteProperty)) { + Switch ($prop.Name) { + { $_ -in 'flags', + 'msPKI-Certificate-Name-Flag', + 'msPKI-Enrollment-Flag', + 'msPKI-Minimal-Key-Size', + 'msPKI-Private-Key-Flag', + 'msPKI-Template-Minor-Revision', + 'msPKI-Template-Schema-Version', + 'msPKI-RA-Signature', + 'pKIMaxIssuingDepth', + 'pKIDefaultKeySpec', + 'revision' + } { $oa.Add($_,[System.Int32]$import.$_); break } + + { $_ -in 'msPKI-Certificate-Application-Policy', + 'pKICriticalExtensions', + 'pKIDefaultCSPs', + 'pKIExtendedKeyUsage', + 'msPKI-RA-Application-Policies' + } { $oa.Add($_,[Microsoft.ActiveDirectory.Management.ADPropertyValueCollection]$import.$_); break } + + { $_ -in 'pKIExpirationPeriod', + 'pKIKeyUsage', + 'pKIOverlapPeriod' + } { $oa.Add($_,[System.Byte[]]$import.$_); break } + + } + } + $TemplatePath = "CN=Certificate Templates,CN=Public Key Services,CN=Services,$ConfigNC" + New-ADObject -Path $TemplatePath -OtherAttributes $oa -Name $DisplayName.Replace(' ','') ` + -DisplayName $DisplayName -Type pKICertificateTemplate -Server $Server + #endregion + + #region PERMISSIONS + ## Potential issue here that the AD: drive may not be targetting the selected DC in the -SERVER parameter + If ($PSBoundParameters.ContainsKey('Identity')) { + If ($AutoEnroll) { + Set-ADCSTemplateACL -DisplayName $DisplayName -Server $Server -Type Allow -Identity $Identity -Enroll -AutoEnroll + } Else { + Set-ADCSTemplateACL -DisplayName $DisplayName -Server $Server -Type Allow -Identity $Identity -Enroll + } + } + #endregion + + #region ISSUE + If ($Publish) { + ### WARNING: Issues on all available CAs. Test in your environment. + $EnrollmentPath = "CN=Enrollment Services,CN=Public Key Services,CN=Services,$ConfigNC" + $CAs = Get-ADObject -SearchBase $EnrollmentPath -SearchScope OneLevel -Filter * -Server $Server + ForEach ($CA in $CAs) { + Set-ADObject -Identity $CA.DistinguishedName -Add @{certificateTemplates=$DisplayName.Replace(' ','')} -Server $Server + } + } + #endregion +} + + +<# +.SYNOPSIS +Removes a certificate template from Active Directory. +.DESCRIPTION +Removes the template from any issuers where it is published. +Removes the template itself. +Removes the unique OID object of the template. +.PARAMETER DisplayName +DisplayName for the certificate template to delete. +.PARAMETER Server +FQDN of Active Directory Domain Controller to target for the operation. +When not specified it will search for the nearest Domain Controller. +.EXAMPLE +PS C:\> Remove-ADCSTemplate -DisplayName PowerShellCMS +.EXAMPLE +PS C:\> (Get-ADCSTemplate).name | Where-Object {$_ -like "PowerShellCMS*"} | ForEach-Object {Remove-ADCSTemplate -DisplayName $_ -Verbose} +.NOTES +Use with caution! +Requires Enterprise Administrator permissions, since this touches the AD Configuration partition. +#> +Function Remove-ADCSTemplate { +[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact='High')] +param( + [parameter(Mandatory)] + [string]$DisplayName, + [string]$Server = (Get-ADDomainController -Discover -ForceDiscover -Writable).HostName[0] +) + if ($pscmdlet.ShouldProcess($DisplayName, 'Remove certificate template')) { + $ConfigNC = $((Get-ADRootDSE -Server $Server).configurationNamingContext) + + $Template = Get-ADCSTemplate -DisplayName $DisplayName -Server $Server + + #region REMOVE ISSUE IF IT EXISTS + $EnrollmentPath = "CN=Enrollment Services,CN=Public Key Services,CN=Services,$ConfigNC" + $CAs = Get-ADObject -SearchBase $EnrollmentPath -SearchScope OneLevel -Filter * -Server $Server + ForEach ($CA in $CAs) { + Set-ADObject -Identity $CA.DistinguishedName -Remove @{certificateTemplates=$Template.cn} -Server $Server -Confirm:$false + } + #endregion + + #region REMOVE TEMPLATE + Remove-ADObject -Identity $Template.distinguishedName -Server $Server -Confirm:$false + #endregion + + #region REMOVE OID + $TemplateOIDPath = "CN=OID,CN=Public Key Services,CN=Services,$ConfigNC" + Get-ADObject -SearchBase $TemplateOIDPath -LDAPFilter "(DisplayName=$DisplayName)" -Server $Server | Remove-ADObject -Confirm:$false + #endregion + } +} + + +<# +.SYNOPSIS +Maps a PowerShell drive to the Active Directory Certificate Services location. +.DESCRIPTION +Maps a PowerShell drive to the Active Directory Certificate Services location +of the Configuration partition under CN=Public Key Services,CN=Services,... . +The new drive is ADCS:. This is purely for convenience of checking the objects +updated by functions in the ADCSTemplate module. +.PARAMETER Server +FQDN of Active Directory Domain Controller to target for the operation. +When not specified it will search for the nearest Domain Controller. +.EXAMPLE +PS C:\> New-ADCSDrive +PS C:\> Set-Location ADCS: +.EXAMPLE +### Explore templates with drive +New-ADCSDrive +Get-PSDrive +cd ADCS: +dir + +# List templates +cd '.\CN=Certificate Templates' +dir +dir | fl * +dir *WebServer* + +# list CAs +cd \ +cd '.\CN=Enrollment Services' +dir +cd C: +.NOTES +Requires Enterprise Administrator permissions, since this touches the AD Configuration partition. +#> +Function New-ADCSDrive { +param( + [string]$Server = (Get-ADDomainController -Discover -ForceDiscover -Writable).HostName[0] +) + $ConfigNC = $((Get-ADRootDSE -Server $Server).configurationNamingContext) + New-PSDrive -Name ADCS -PSProvider ActiveDirectory -Root "CN=Public Key Services,CN=Services,$ConfigNC" -Server $Server -Scope Global +} + + +Export-ModuleMember -Function *-ADCS* -- cgit v1.2.3