Introduction to Privileged Access Management
Privileged Access Management (PAM) is a security practice and set of technologies designed to protect privileged accounts and limit the attack surface exposed by administrative access. On Windows Server 2019, PAM can be implemented using Microsoft Identity Manager (MIM) PAM, which is the enterprise-grade solution, or through a layered approach combining just-in-time (JIT) administration, tiered administrative model, Privileged Access Workstations (PAWs), and Credential Guard. The core principle of PAM is that privileged access should be temporary, audited, and granted only when needed rather than persistent. This dramatically reduces the window of opportunity for attackers who have compromised an administrative account.
Understanding the Privileged Access Tier Model
Microsoft recommends a three-tier administration model to isolate privileged access. Tier 0 contains the most sensitive assets including domain controllers, Active Directory, and identity management systems. Only Tier 0 accounts administered from dedicated Tier 0 PAWs should manage these systems. Tier 1 contains servers and applications managed by server administrator accounts. These accounts should not have access to Tier 0 assets. Tier 2 contains end-user devices and data managed by helpdesk and desktop administrator accounts. Accounts should never flow upward across tiers. Create OU structures reflecting this model:
New-ADOrganizationalUnit -Name "Tier 0" -Path "OU=Admin,DC=contoso,DC=com"
New-ADOrganizationalUnit -Name "Tier 0 Accounts" -Path "OU=Tier 0,OU=Admin,DC=contoso,DC=com"
New-ADOrganizationalUnit -Name "Tier 0 Devices" -Path "OU=Tier 0,OU=Admin,DC=contoso,DC=com"
New-ADOrganizationalUnit -Name "Tier 1" -Path "OU=Admin,DC=contoso,DC=com"
New-ADOrganizationalUnit -Name "Tier 1 Accounts" -Path "OU=Tier 1,OU=Admin,DC=contoso,DC=com"
New-ADOrganizationalUnit -Name "Tier 2" -Path "OU=Admin,DC=contoso,DC=com"
Installing Microsoft Identity Manager for PAM
MIM PAM provides a just-in-time access workflow where privileged group memberships are granted for a specified time period in response to an approved request. MIM requires a dedicated bastion forest that has a trust relationship with the production forest. Set up the bastion forest as a separate Windows Server 2019 domain with enhanced security settings. Install MIM PAM on the bastion forest domain controller after installing SQL Server and SharePoint prerequisites. The MIM installation is complex; here are the key database and service account preparations:
New-ADUser -Name "MIM Service" -SamAccountName "MIMService" -AccountPassword (ConvertTo-SecureString "MIMSvc@123!" -AsPlainText -Force) -Enabled $true -PasswordNeverExpires $true
New-ADUser -Name "MIM Monitor" -SamAccountName "MIMMonitor" -AccountPassword (ConvertTo-SecureString "MIMMon@123!" -AsPlainText -Force) -Enabled $true -PasswordNeverExpires $true
New-ADUser -Name "MIM PAM Component" -SamAccountName "MIMComp" -AccountPassword (ConvertTo-SecureString "MIMComp@123!" -AsPlainText -Force) -Enabled $true -PasswordNeverExpires $true
Configuring PAM with Windows Server 2019 Built-In Features
Windows Server 2019 includes a simplified PAM feature that provides time-limited group membership without requiring MIM. This feature is called PAM Trust and requires a bastion Active Directory forest. Enable the PAM feature on the forest functional level 2016 forest:
Enable-ADOptionalFeature "Privileged Access Management Feature" -Scope ForestOrConfigurationSet -Target "bastion.contoso.com" -Confirm:$false
Establish a forest trust from the bastion forest to the production forest:
$prodForest = Get-ADForest -Server "contoso.com"
$trustPassword = ConvertTo-SecureString "TrustP@ss123!" -AsPlainText -Force
New-ADTrust -Name "contoso.com" -TrustType Forest -Direction BiDirectional -TrustingDomainName "bastion.contoso.com" -TrustedDomainName "contoso.com" -AuthType Kerberos -Credential (Get-Credential)
Creating Shadow Principals and PAM Groups
In the bastion forest, create shadow groups that mirror privileged groups in the production forest. When a user is added to the shadow group in the bastion forest, they temporarily gain membership in the production forest’s privileged group. Create a shadow principal for the production Domain Admins group:
$productionDA = Get-ADGroup -Identity "Domain Admins" -Server "contoso.com" -Properties SID
New-ADGroup -Name "priv.DomainAdmins" -GroupCategory Security -GroupScope Universal -DisplayName "Privileged Domain Admins (Shadow)"
$shadowGroup = Get-ADGroup -Identity "priv.DomainAdmins"
Set-ADGroup -Identity $shadowGroup.DistinguishedName -Add @{"msDS-ShadowPrincipalSid"=$productionDA.SID.Value}
Granting Time-Limited Privileged Access
Grant a user privileged access for a specific time-limited period using PowerShell. The Add-ADGroupMember cmdlet with a TTL (time-to-live) parameter in Windows Server 2019 PAM-enabled forests allows temporary membership:
$ttl = New-TimeSpan -Hours 1
Add-ADGroupMember -Identity "priv.DomainAdmins" -Members "bastionuser01" -MemberTTL $ttl
Verify the temporary group membership:
Get-ADGroup -Identity "priv.DomainAdmins" -Properties member -Server "bastion.contoso.com" | Select-Object -ExpandProperty member
(Get-ADUser "bastionuser01" -Properties memberof).memberof
After the TTL expires, the membership is automatically removed. Create a PowerShell function to simplify JIT access requests:
function Request-JITAccess {
param(
[string]$UserSamAccountName,
[string]$PrivilegedGroup,
[int]$DurationHours = 1
)
$ttl = New-TimeSpan -Hours $DurationHours
Add-ADGroupMember -Identity $PrivilegedGroup -Members $UserSamAccountName -MemberTTL $ttl
Write-Host "Granted $UserSamAccountName membership in $PrivilegedGroup for $DurationHours hour(s)"
$expiry = (Get-Date).AddHours($DurationHours)
Write-Host "Access expires at: $expiry"
}
Request-JITAccess -UserSamAccountName "jsmith" -PrivilegedGroup "priv.DomainAdmins" -DurationHours 2
Implementing Just Enough Administration (JEA)
Just Enough Administration uses PowerShell remoting with constrained runspaces to provide users with only the specific commands needed to perform their job functions. Create a JEA role capability file that defines allowed commands for a DNS administrator role:
New-Item -ItemType Directory -Path "C:JEARoleCapabilities" -Force
New-PSRoleCapabilityFile -Path "C:JEARoleCapabilitiesDNSAdmin.psrc" -Description "DNS Administrator Role" -VisibleCmdlets @{
Name = "Get-DnsServerResourceRecord"
Name = "Set-DnsServerResourceRecord"
Name = "Add-DnsServerResourceRecord"
Name = "Remove-DnsServerResourceRecord"
Name = "Get-DnsServerZone"
Name = "Clear-DnsServerCache"
}
New-PSSessionConfigurationFile -Path "C:JEADNSAdminSession.pssc" -SessionType RestrictedRemoteServer -RoleDefinitions @{"CONTOSODNS-Admins" = @{RoleCapabilityFiles = "C:JEARoleCapabilitiesDNSAdmin.psrc"}} -RunAsVirtualAccount $true
Register-PSSessionConfiguration -Name "DNSAdminJEA" -Path "C:JEADNSAdminSession.pssc" -Force
Test the JEA endpoint:
Enter-PSSession -ComputerName DNS01 -ConfigurationName DNSAdminJEA
Get-Command # Only allowed commands appear
Auditing Privileged Access
Comprehensive auditing of privileged access is essential for detecting misuse and maintaining compliance. Configure PowerShell Script Block Logging to capture all PowerShell commands executed, even when obfuscated:
reg add "HKLMSOFTWAREPoliciesMicrosoftWindowsPowerShellScriptBlockLogging" /v "EnableScriptBlockLogging" /t REG_DWORD /d 1 /f
reg add "HKLMSOFTWAREPoliciesMicrosoftWindowsPowerShellScriptBlockLogging" /v "EnableScriptBlockInvocationLogging" /t REG_DWORD /d 1 /f
Enable PowerShell Module Logging and Transcription:
reg add "HKLMSOFTWAREPoliciesMicrosoftWindowsPowerShellModuleLogging" /v "EnableModuleLogging" /t REG_DWORD /d 1 /f
reg add "HKLMSOFTWAREPoliciesMicrosoftWindowsPowerShellTranscription" /v "EnableTranscripting" /t REG_DWORD /d 1 /f
reg add "HKLMSOFTWAREPoliciesMicrosoftWindowsPowerShellTranscription" /v "OutputDirectory" /t REG_SZ /d "\FileServerPSTranscripts" /f
Query privileged account activity from the Security log:
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4672; StartTime=(Get-Date).AddDays(-1)} | ForEach-Object {
$xml = [xml]$_.ToXml()
[PSCustomObject]@{
Time = $_.TimeCreated
User = $xml.Event.EventData.Data[0].'#text'
Privileges = $xml.Event.EventData.Data[4].'#text'
}
} | Format-Table -AutoSize