Introduction to Cross-Forest Active Directory Trusts
An Active Directory forest represents a security boundary. A Cross-Forest Trust is a two-way or one-way trust relationship between two separate AD forests that allows users in one forest to authenticate to resources in another. This is commonly needed after mergers and acquisitions, for inter-company collaboration, or when separate forests exist for security segmentation (e.g., a DMZ forest, a lab forest, or a partner organization’s forest). Windows Server 2019 domain controllers support all AD trust types: external trusts, realm trusts, forest trusts, and shortcut trusts.
Trust Types Overview
A Forest Trust (the most powerful type) allows users from all domains in Forest A to access resources in any domain of Forest B. It requires both forests to be at Windows Server 2003 functional level or higher, and the trust is created between the forest root domains. An External Trust is more limited—it connects two specific domains across forests but does not automatically extend to other domains in either forest. Shortcut Trusts optimize authentication paths within a forest by creating a direct trust between child domains, reducing the number of hops in the trust chain.
Prerequisites
# Forest A: corp.local (our forest)
# Forest B: partner.local (partner organization's forest)
# Verify Forest Functional Level on both forests (must be 2003+)
Get-ADForest -Identity corp.local | Select-Object Name, ForestMode
Get-ADForest -Identity partner.local | Select-Object Name, ForestMode
# Verify DNS resolution between forests - each forest must be able to
# resolve the other's DNS names
# Option 1: Conditional forwarders (recommended)
# Option 2: Stub zones
# Option 3: Secondary DNS zones
# On corp.local DNS servers: add conditional forwarder for partner.local
Add-DnsServerConditionalForwarderZone -Name 'partner.local' `
-MasterServers '172.16.1.10','172.16.1.11' `
-ReplicationScope 'Forest' `
-PassThru
# On partner.local DNS servers: add conditional forwarder for corp.local
# (partner admin must do this)
# Add-DnsServerConditionalForwarderZone -Name 'corp.local' `
# -MasterServers '10.0.0.10','10.0.0.11' `
# -ReplicationScope 'Forest'
# Test DNS resolution
Resolve-DnsName -Name 'partner.local' -Type SOA
Resolve-DnsName -Name 'dc01.partner.local' -Type A
Verifying Firewall Requirements for Trust
Cross-forest trust creation and operation require multiple ports to be open between domain controllers in both forests:
# Ports required between domain controllers of both forests:
# TCP 135 - RPC Endpoint Mapper
# TCP 49152-65535 - RPC dynamic ports (or restrict to a range)
# TCP/UDP 53 - DNS
# TCP/UDP 88 - Kerberos
# TCP/UDP 389 - LDAP
# TCP 636 - LDAPS
# TCP/UDP 445 - SMB (Netlogon)
# TCP 3268 - Global Catalog
# TCP 3269 - Global Catalog over SSL
# UDP 123 - NTP
# Test port connectivity from corp DC to partner DC
$partnerDC = '172.16.1.10'
$ports = 53,88,135,389,445,636,3268
foreach ($port in $ports) {
$result = Test-NetConnection -ComputerName $partnerDC -Port $port -WarningAction SilentlyContinue
[PSCustomObject]@{
Port = $port
Success = $result.TcpTestSucceeded
}
} | Format-Table
# Restrict RPC dynamic port range on all DCs (makes firewall rules manageable)
netsh int ipv4 set dynamicport tcp start=49152 num=300
netsh int ipv4 set dynamicport udp start=49152 num=300
# Then allow TCP 49152-49452 in firewall rules
Creating the Forest Trust via PowerShell
The trust must be created from the forest root domain on both sides. Both actions can be performed from a single Domain Admin account that has appropriate access to both forest roots, or each forest admin creates their side of the trust:
# Create the trust from the corp.local side
# Run as Domain Admin in corp.local
$localForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
$remoteForest = [System.DirectoryServices.ActiveDirectory.Forest]::GetForest(
(New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext(
'Forest', 'partner.local', 'partneradmin', 'PartnerAdminP@ss!'
))
)
# Create a two-way forest trust
$localForest.CreateTrustRelationship(
$remoteForest,
[System.DirectoryServices.ActiveDirectory.TrustDirection]::Bidirectional
)
Write-Output 'Forest trust created successfully'
# Verify the trust was created
Get-ADTrust -Filter * | Select-Object Name, Direction, TrustType, TrustedDomain
# Alternative: create trust using netdom
# netdom trust corp.local /domain:partner.local /twoway /add `
# /userD:partneradmin /passwordD:* `
# /userO:corpadmin /passwordO:*
Validating the Trust
# Validate the trust from the corp.local side
$localForest.GetTrustRelationship('partner.local').VerifyTrustRelationship(
[System.DirectoryServices.ActiveDirectory.TrustDirection]::Bidirectional
)
# Using netdom
netdom trust corp.local /domain:partner.local /verify
# Using Active Directory Domains and Trusts MMC:
# Right-click corp.local > Properties > Trusts > partner.local > Validate
# Test authentication across the trust
# On a corp.local domain member, authenticate as a partner.local user:
$cred = Get-Credential 'partnerpartneruser'
Invoke-Command -ComputerName APP-SRV01.corp.local -Credential $cred -ScriptBlock {
$env:USERDOMAIN
whoami
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name
}
Enabling Selective Authentication (Recommended)
By default, a forest trust allows all users from the trusted forest to authenticate to all resources. Selective Authentication restricts this so that only users explicitly granted “Allowed to Authenticate” permission on specific computers can authenticate across the trust:
# Enable selective authentication on the trust (from corp.local perspective)
# This means partner.local users can ONLY authenticate to resources you explicitly permit
Set-ADTrust -Identity (Get-ADTrust -Filter { Name -eq 'partner.local' }) `
-SelectiveAuthentication $true
# Grant "Allowed to Authenticate" permission on specific servers
# This is set on the computer object in corp.local AD
$server = Get-ADComputer -Identity 'SHARED-APP01'
# Add the partner forest's Domain Users to "Allowed to Authenticate"
$partnerUsers = New-Object System.Security.Principal.SecurityIdentifier `
'S-1-5-21-partnerforest-sid-513' # Partner Domain Users SID
$acl = Get-Acl "AD:$($server.DistinguishedName)"
$rule = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$partnerUsers,
[System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight,
[System.Security.AccessControl.AccessControlType]::Allow,
[guid]'68b1d179-0d15-4d4f-ab71-46152e79a7bc' # Allowed to Authenticate GUID
)
$acl.AddAccessRule($rule)
Set-Acl -Path "AD:$($server.DistinguishedName)" -AclObject $acl
Configuring SID Filtering
SID filtering (also called SID quarantine) is enabled by default on forest trusts and prevents administrators in the trusted forest from including SIDs from the trusting forest in their accounts’ SIDHistory attribute—a critical security control that prevents privilege escalation across the trust:
# Verify SID filtering is enabled (it should be on by default for forest trusts)
netdom trust corp.local /domain:partner.local /quarantine
# Should output: "The SID filtering (quarantine) is enabled for the corp.local domain."
# Check via PowerShell
Get-ADTrust -Filter { Name -eq 'partner.local' } | Select-Object Name, SIDFilteringForestAware, SIDFilteringQuarantined
# Do NOT disable SID filtering unless there is a specific, documented requirement
# (e.g., when both forests need to share resources via SIDHistory after a migration)
# Disabling SID filtering creates a significant security risk
# If SIDHistory migration is needed, use migration tools and then re-enable filtering
netdom trust corp.local /domain:partner.local /quarantine:yes
Creating a Shortcut Trust Within a Forest
If users in child.corp.local frequently access resources in child2.corp.local, authentication must traverse up to corp.local and back down. A shortcut trust creates a direct path:
# Create a shortcut trust between two child domains in the same forest
$childDomain1 = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain(
(New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain','emea.corp.local'))
)
$childDomain2 = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain(
(New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain','apac.corp.local'))
)
$childDomain1.CreateTrustRelationship(
$childDomain2,
[System.DirectoryServices.ActiveDirectory.TrustDirection]::Bidirectional
)
# Verify shortcut trust
Get-ADTrust -Filter * -Server 'dc01.emea.corp.local' |
Select-Object Name, TrustType, Direction
Monitoring Trust Health
# Schedule regular trust verification
Get-ADTrust -Filter * | ForEach-Object {
try {
$result = netdom trust $_.TrustingDomain /domain:$_.TrustedDomain /verify 2>&1
[PSCustomObject]@{ Trust = $_.Name; Status = if ($result -match 'succeeded') { 'OK' } else { 'FAILED' }; Detail = $result }
} catch {
[PSCustomObject]@{ Trust = $_.Name; Status = 'ERROR'; Detail = $_.Exception.Message }
}
} | Format-Table -AutoSize
# Check event log for trust-related failures
Get-WinEvent -LogName System |
Where-Object { $_.Id -in 5722,5805 } |
Select-Object TimeCreated, Id, Message -First 10
# Netlogon log for secure channel issues
Get-Content 'C:Windowsdebugnetlogon.log' | Select-String -Pattern 'FAILED|ERROR' | Select-Object -Last 20
Conclusion
Cross-forest trusts on Windows Server 2019 enable controlled, auditable access between separate Active Directory forests. Forest trusts with Selective Authentication provide fine-grained control over which resources are accessible across the trust boundary, while SID filtering prevents privilege escalation. Proper DNS conditional forwarding, firewall rule management, and regular trust validation ensure reliable cross-forest authentication for the lifetime of the trust relationship.