Zduplikowane rekordy SRV kontrolerów domeny

January 14, 2020  ‐ 7 minut(a) czytania

W Windows Server 2016 i nowszych usługa serwera nazw (DNS) wspiera rejestrację rekordów SRV uwzględniając wielkość liter w nazwie hosta (case-sensitive). O czym wiele osób przekonało się wkrótce po wprowadzeniu nowej wersji systemu operacyjnego do środowiska Active Directory.

Kontrolery domeny działające pod kontrolą systemu Windows Server 2016 lub nowszych, których nazwy zawierają jedną lub więcej wielkich liter w nazwie, mogą rejestrować rekordy SRV zawierające tylko małe litery, gdy usługa serwera nazw, z której korzystają jest uruchomiona w systemie Windows Server 2012 R2 lub starszym, a dodatkowo rekordy zawierające mieszane lub wyłącznie wielkie literami, gdy serwer nazw jest uruchomiony na Windows Server 2016 lub nowszym.

Powodowane jest to obsługą rozróżniania wielkości liter w przesłanym w żądaniu rejestracji informacjach RDATA do serwera DNS.

Czy zduplikowane rekordy mogą być szkodliwe?

W większości wypadków nie będzie to miało większego wpływu na dostępność usługi ani wydajność kontrolerów domeny. Niemniej te kontrolery domeny, które zarejestrują lokatory usług podwójnie będą występować dwukrotnie częściej na liście serwerów przy zapytania klientów (więcej o mechniźmie DC Locator). To z kolei może oznaczać nadmierną utylizację serwerów z powielonymi rekordami, nierównomierny rozkład klientów oraz generować opóźnia w obsłudze żądań klientów domeny.

Poniższy zrzut ekranu prezentuje zduplikowane rekordy SRV _kerberos zarejestrowane przez kontroler domeny o nazwie PFE-DC1 (zawiera wielkie litery). 

image

Kolejny przykład zawiera rekordy SRV _ldap zarejestrowane przez ten sam kontroler domeny. 

image

Przyznacie, że sprawa wygląda nieciekawie? Co w tej sytuacji zrobić? 

Powyższe zachowanie serwera DNS, na którym rejestrowany lub odświeżany jest rekord jest poprawnym zachowaniem dla Windows Server 2016 i nowszych. Zatem jest to “problem”, a nie problem. Klienci dysponujący wsparciem mogą otworzyć zgłoszenie i otrzymają prywatną poprawkę (private fix).  

Docelowo, w marcowym zbiorczym pakiecie poprawek oraz towarzyszącym mu wydaniu szablonów administracyjnych zasad grup znajdzie się nowe ustawienie, które pozwoli zablokować rejestrację zdublowanych rekordów.

Nowe ustawienie dostępne będzie w następującej ścieżce: 

Computer Configuration\Policies\Administrative Templates\System\Net Logon\DC Locator DNS Records\Use lowercase DNS host names when registering domain controller SRV records

I będzie stosowane domyślnie (czyli nawet wówczas, gdy nie będzie skonfigurowane (Not configured)). 

Do tego czasu zalecana jest zmiana nazwy kontrolerów domeny, na taką która zawierać będzie tylko małe litery (tak, wspieramy to, choć w niektórych wypadkach należy pamiętać o dodatkowych czynnościach). Przy okazji warto zaznajomić się z dokumentacją, w pierwszej kolejności polecam artykuł Computer Naming, a następnie Naming conventions in Active Directory.

 A co z pozostałościami? Jeśli zgodnie z rekomendacjami stosujecie automatyczne czyszczenie stref z przestarzałych rekordów (scavenging), rekordy zostaną usunięte automatycznie. 

image

Jeśli nie stosujecie scavenging lub nie chcecie czekać na wyniki jego działania, możecie usunąć pozostałości samodzielnie. W dalszej części opiszę jak skryptowo wylistować oraz usunąć rzeczone zduplikowane rekordy. 

Jak sprawdzić jakie oraz ile rekordów SRV w mojej domenie zarejestrowano dla kontrolerów domeny z nazwami zawierającymi wielkie litery? 

Na potrzeby tego ćwiczenia przygotowałem skrypt PowerShell, który pozwala zidentyfikować rekordy SRV w strefie funkcjonalnej Active Directory (_msdcs). Skrypt można pobrać z mojego repozytorium lub skopiować poniżej. 

<# 
.SYNOPSIS 
Get-UppercaseDomainSrvRecords_v1.ps1 - Finds SRV records with upper-case letters in name registered under _msdcs.  
 
.DESCRIPTION  
This script will locate your Active Directory integrated DNS servers and find SRV records with upper-case letters in name registered under _msdcs. 
 
.OUTPUTS 
Object, allows sorting, filtering and pipe output date.  
 
.COPYRIGHT
Grzegorz Glogowski - Microsoft Corporation 

.NOTES 
This script is provided "AS IS" with no warranties and confers no rights.
 
Change Log 
V1.00, 20200112 - Initial version
#> 

#Clear screen, useful when run in ISE
cls

<#
#Set up domain specification, borrowed from PyroTek3
#https://github.com/PyroTek3/PowerShell-AD-Recon/blob/master/Find-PSServiceAccounts
        if(-not $Domain)
        {
            $ADDomainInfo = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
            $Domain = $ADDomainInfo.Name
        }
#>

#Get Active Directory forest and domain information
$forest = Get-ADForest
$domain = Get-ADDomain

#Find all DCs/DNS servers
$dns = (Resolve-DnsName $($domain.DNSRoot) -type NS | ? {$_.type -eq "A"}).Name

<#Alternative way to get DNS servers (LDAP query) - Windows Server 2008 R2 and older

#Find DNS servers
#https://social.technet.microsoft.com/wiki/contents/articles/18996.active-directory-powershell-script-to-list-all-spns-used.aspx

$search = New-Object DirectoryServices.DirectorySearcher([ADSI]"")
$search.filter = "(servicePrincipalName=DNS*)"
$searchbase = "OU=Domain Controllers,"+$($domain.DistinguishedName)
$results = $search.Findall() | ?{ $_.path -like $searchbase }
$results = $search.Findall()
$dns = $results.Properties.dnshostname

#>

#$dcs = $domain.ReplicaDirectoryServers
$rootzone = $domain.DNSRoot
$msdcszone = "_msdcs." + $rootzone

#Get filtered set of SRV records from _msdcs
$dnsrecords = Get-DnsServerResourceRecord -ZoneName $msdcszone | Where-Object {$_.RecordType -ne "NS" -and $_.RecordType -ne "SOA" -and $_.RecordType -ne "CNAME" -and $_.RecordType -ne "A" }
#Get filtered set of SRV records from _msdcs with upper-case letters
#$dnsrecords = Get-DnsServerResourceRecord -ZoneName $msdcszone | Where-Object {$_.RecordType -ne "NS" -and $_.RecordType -ne "SOA" -and $_.RecordType -ne "CNAME" -and $_.RecordType -ne "A" -and $_.RecordData.DomainName -cmatch '[A-Z]' }

#Initialize the array
$OutputObj = @()

ForEach ($rr in $dnsrecords) {
     $name = $rr.HostName
     $type = $rr.RecordType
     $ttl = $rr.TimeToLive
     $created = $rr.Timestamp
     #$data = $rr.RecordData.DomainName 
     #$data = $rr.RecordData | select DomainName -ExpandProperty DomainName | Out-String
     $data = $rr.RecordData | select DomainName -ExpandProperty DomainName
     $uppercase = $data -cmatch '[A-Z]'   
     
    $OutputObj += New-Object -TypeName PSobject -Property @{
    Name = $name
    Type = $type
    TTL = $ttl
    Created = $created
    Data = $data
    CaseSensitive = $uppercase
    }

   }

#Get Active Directory sites
$sites = ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites).Name

#Divide output into generic and site-specific SRV records with upper-case letters 
foreach ($site in $sites) {
$currentpath = "_tcp." + $($site) + "._sites.dc." + $($msdcszone) -replace " ",""
#Write-Host "Records with UPPER-CASE in: " $currentpath.ToLower() "`r`n" -ForegroundColor DarkYellow
Write-Host "Records with UPPER-CASE in" $site "site container `r`n" -ForegroundColor DarkYellow
$OutputObj | where {$_.Name -match $site -and $_.Case -eq $true} | select Name,Data | Format-Table -AutoSize
}
Write-Host "Records with UPPER-CASE in generic containers `r`n" -ForegroundColor DarkYellow
$OutputObj | where {$_.Name -notmatch "._Sites." -and $_.Case -eq $true}| select Name,Data | Format-Table -AutoSize

#EOF

Powyższy skrypt automatycznie wyświetla wyniki z podziałem na kontenery, w których znajdują się wykryte rekordy z wielkimi literami w nazwie. 

image

Jeśli na wynika potrzebujecie wykonać sortowanie, filtrowanie lub chcecie je przesłać na wejście kolejnego polecenia, należy użyć następującego kodu.

<# 
.SYNOPSIS 
Get-UppercaseDomainSrvRecords_v2.ps1 - Finds SRV records with upper-case letters in name registered under _msdcs.  
 
.DESCRIPTION  
This script will locate your Active Directory integrated DNS servers and find SRV records with upper-case letters in name registered under _msdcs. 
 
.OUTPUTS 
Object, allows sorting, filtering and pipe output date.  
 
.COPYRIGHT
Grzegorz Glogowski - Microsoft Corporation 

.NOTES 
This script is provided "AS IS" with no warranties and confers no rights.
 
Change Log 
V1.00, 20200112 - Initial version
#> 

#Clear screen, useful when run in ISE
#cls

<#
#Set up domain specification, borrowed from PyroTek3
#https://github.com/PyroTek3/PowerShell-AD-Recon/blob/master/Find-PSServiceAccounts
        if(-not $Domain)
        {
            $ADDomainInfo = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
            $Domain = $ADDomainInfo.Name
        }
#>

#Get Active Directory forest and domain information
$forest = Get-ADForest
$domain = Get-ADDomain

#Find all DCs/DNS servers
$dns = (Resolve-DnsName $($domain.DNSRoot) -type NS | ? {$_.type -eq "A"}).Name

<#Alternative way to get DNS servers (LDAP query) - Windows Server 2008 R2 and older

#Find DNS servers
#https://social.technet.microsoft.com/wiki/contents/articles/18996.active-directory-powershell-script-to-list-all-spns-used.aspx

$search = New-Object DirectoryServices.DirectorySearcher([ADSI]"")
$search.filter = "(servicePrincipalName=DNS*)"
$searchbase = "OU=Domain Controllers,"+$($domain.DistinguishedName)
$results = $search.Findall() | ?{ $_.path -like $searchbase }
$results = $search.Findall()
$dns = $results.Properties.dnshostname

#>

#$dcs = $domain.ReplicaDirectoryServers
$rootzone = $domain.DNSRoot
$msdcszone = "_msdcs." + $rootzone

#Get filtered set of SRV records from _msdcs
$dnsrecords = Get-DnsServerResourceRecord -ZoneName $msdcszone | Where-Object {$_.RecordType -ne "NS" -and $_.RecordType -ne "SOA" -and $_.RecordType -ne "CNAME" -and $_.RecordType -ne "A" }
#Get filtered set of SRV records from _msdcs with upper-case letters
#$dnsrecords = Get-DnsServerResourceRecord -ZoneName $msdcszone | Where-Object {$_.RecordType -ne "NS" -and $_.RecordType -ne "SOA" -and $_.RecordType -ne "CNAME" -and $_.RecordType -ne "A" -and $_.RecordData.DomainName -cmatch '[A-Z]' }

#Initialize the array
$OutputObj = @()

ForEach ($rr in $dnsrecords) {
     $name = $rr.HostName
     $type = $rr.RecordType
     $ttl = $rr.TimeToLive
     $created = $rr.Timestamp
     #$data = $rr.RecordData.DomainName 
     #$data = $rr.RecordData | select DomainName -ExpandProperty DomainName | Out-String
     $data = $rr.RecordData | select DomainName -ExpandProperty DomainName
     $uppercase = $data -cmatch '[A-Z]'   
     
    $OutputObj += New-Object -TypeName PSobject -Property @{
    Name = $name
    Type = $type
    TTL = $ttl
    Created = $created
    Data = $data
    CaseSensitive = $uppercase
    }

   }

#Get Active Directory sites
$sites = ([System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites).Name

#Divide output into generic and site-specific SRV records with upper-case letters 
foreach ($site in $sites) {
$currentpath = "_tcp." + $($site) + "._sites.dc." + $($msdcszone) -replace " ",""
#Write-Host "Records with UPPER-CASE in: " $currentpath.ToLower() "`r`n" -ForegroundColor Yellow
Write-Host "Records with UPPER-CASE in" $site "site container `r`n" -ForegroundColor Yellow
$OutputObj | where {$_.Name -match $site -and $_.CaseSensitive -eq $true} | select Name,Data | Format-Table -AutoSize
}
Write-Host "Records with UPPER-CASE in generic containers `r`n" -ForegroundColor Yellow
$OutputObj | where {$_.Name -notmatch "._Sites." -and $_.CaseSensitive -eq $true}| select Name,Data | Format-Table -AutoSize

#EOF

Wówczas, sami zdecydujecie o tym, co dalej zrobić z wykrytymi rekordami. 

image

Uwaga:  Do poprawnego działania Active Directory niezbędne są odpowiednie strefy oraz rekordy DNS. Zalecam dużą dozę ostrożności i proszę nie róbcie niczego pochopnie, aby nie musieć naprawiać lub odzyskiwać środowiska! 

O ile nie stwierdzono inaczej, opublikowane materiały autorskie udostępniane są na licencji Creative Commons Uznanie autorstwa-Użycie niekomercyjne-Na tych samych warunkach 4.0. W przypadku przedruków oraz tłumaczeń pewne prawa są zastrzeżone na rzecz oryginalnych twórców lub dystrybutorów.

Witrynę zasilają Netlify, Hugo i Doks.