Windows Server для самых маленьких. Часть 11: Что такое контроллер только для чтения?

Все части серии «Windows Server для самых маленьких»

В прошлой части появился сайт Branch без контроллера. Пользователи филиала входят через канал до главного офиса: каждый вход — обмен Kerberos с dc1 или dc2, каждое применение групповой политики — чтение SYSVOL через WAN. Когда канал падает, филиал не работает; когда он просто медленный, вход занимает минуту. Обычный ответ — поставить контроллер в филиал. Но обычный контроллер хранит хеши паролей всех пользователей домена, включая администраторов, и принимает изменения, которые разойдутся по всему лесу. Сервер в комнате без замка, которую уборщица открывает своим ключом, для этого не подходит.

Для этого в Windows Server 2008 появился RODC — контроллер домена только для чтения. Он делает для филиала всё, что делает контроллер: отвечает на LDAP, выдаёт билеты Kerberos, раздаёт политики, работает DNS-сервером. Но он не хранит ничего, что нельзя потерять, и не может изменить ничего, что видят остальные.


Чем RODC отличается

Пять отличий, и каждое — отдельный механизм.

База только для чтения. Клиент, пытающийся что-то записать на RODC по LDAP или обновить запись DNS, по умолчанию получает отсылку (referral) на контроллер с правом записи. ADSI и модуль ActiveDirectory следуют ей сами, а приложению на чистом LDAP это нужно разрешить (LDAP_Write_Referral); если записываемый контроллер недоступен, запись не удаётся. Немногие операции RODC не отклоняет, а пересылает записываемому контроллеру сам, и главная из них — смена пароля: смена через Ctrl+Alt+Del и по Kerberos приходит на RODC и уходит дальше, а смена по LDAP сразу идёт на записываемый контроллер, и RODC о ней не узнаёт.

Репликация в одну сторону. RODC получает изменения от контроллеров с правом записи и никому ничего не отдаёт: в топологии KCC у него есть только входящие соединения. Если RODC скомпрометирован и в его базу что-то записали, это останется в нём. Отсюда следствие для приложений: то, что пишет в каталог, должно уметь идти по отсылкам и для записи — а лучше и для чтения записанного обратно — искать записываемый контроллер, иначе оно прочитает с RODC старое значение, пока изменение не доехало. Exchange, по документации Microsoft, RODC не использует вовсе.

Отфильтрованный набор атрибутов (filtered attribute set, FAS). Часть атрибутов на RODC не реплицируется никогда, что бы ни было в политике паролей. По умолчанию это ключи восстановления BitLocker (ms-FVE-RecoveryPassword, ms-FVE-KeyPackage), данные владельца TPM (ms-TPM-OwnerInformation) и три атрибута перемещаемых учётных данных (ms-PKI-DPAPIMasterKeys, ms-PKI-AccountCredentials, ms-PKI-RoamingTimeStamp); пароли Windows LAPS схема тоже помечает как отфильтрованные. Признак — бит 0x200 в searchFlags атрибута в схеме, и свой атрибут прячут от RODC так же: ставят этот бит, а заодно бит 0x80 «конфиденциальный» и снимают право чтения у Authenticated Users. Системно критичные атрибуты в набор не добавляются. Набор защищает, только если в лесу нет контроллеров Windows Server 2003: скомпрометированный RODC мог бы запросить такие атрибуты у них, и они бы их отдали. В лесу с контроллерами 2025 их нет и быть не может.

Политика репликации паролей (Password Replication Policy, PRP). Хеши паролей — то, ради чего всё затевалось. По умолчанию RODC не хранит ни одного: все учётные записи в состоянии «не кешировать». Администратор разрешает кешировать пароли пользователей и компьютеров филиала, и только они попадут на RODC. Остальные пользователи тоже смогут войти через RODC, но их пароль каждый раз будет проверять контроллер в офисе. Как это происходит — ниже.

Свой krbtgt. У каждого RODC собственная учётная запись krbtgt_12345 со своим ключом. Билеты TGT, которые выдаёт RODC, зашифрованы этим ключом. Билетам RODC записываемые контроллеры не доверяют: предъявленный им TGT от RODC они принимают, но список групп в нём (PAC) отбрасывают и собирают заново по каталогу. Поэтому украденный ключ krbtgt_12345 не открывает ни других сайтов, ни чужих привилегий, а учётные данные администраторов на RODC не попадают вообще. Работает и обратная проверка: если у Bob есть TGT от записываемого контроллера, а его пароль тем временем закешировался на RODC, RODC отвечает на такой билет ошибкой KDC_ERR_TGT_REVOKED, и рабочая станция запрашивает новый TGT уже у него.

И ещё два следствия. Роли FSMO на RODC не бывают. SYSVOL на нём — член DFS-R только для чтения: политики можно читать, а локальные изменения DFS-R откатывает; правят политики на записываемом контроллере (консоль управления групповыми политиками по умолчанию подключается к PDC-эмулятору).


Политика репликации паролей

PRP — четыре атрибута на учётной записи компьютера RODC и две группы домена, созданные при подготовке леса:

Что Атрибут По умолчанию
Разрешено кешировать msDS-RevealOnDemandGroup группа Allowed RODC Password Replication Group (пуста)
Запрещено кешировать msDS-NeverRevealGroup группа Denied RODC Password Replication Group (в ней Domain Admins, Enterprise Admins, Schema Admins, Group Policy Creator Owners, Cert Publishers, krbtgt, Enterprise Domain Controllers, Enterprise Read-Only Domain Controllers) и отдельными записями встроенные Administrators, Account Operators, Server Operators, Backup Operators
Уже раскрыто msDS-RevealedList список учётных записей, чьи секреты на этом RODC есть (строится из msDS-RevealedUsers, где хранится история передач)
Аутентифицировались через него msDS-AuthenticatedToAccountList кто входил через этот RODC, независимо от кеширования

Запрет сильнее разрешения: пользователь в обеих группах кешироваться не будет. Группы по умолчанию — общие для всех RODC леса, и для каждого RODC можно задать свои: обычно создают группу «Branch Users» и «Branch Computers» и добавляют их в разрешённый список конкретного RODC. Компьютеры в список нужно включать обязательно: рабочая станция тоже входит в домен своей учётной записью, и если пароль пользователя закеширован, а её — нет, при упавшем канале пользователь не войдёт: RODC не сможет выдать билет службы для этой станции. Для входа без канала нужно ещё, чтобы RODC был глобальным каталогом или в сайте было включено кеширование членства в универсальных группах из прошлой части.

Что происходит при входе пользователя Bob из филиала через dc3:

 ws-branch              dc3 (RODC)                         dc1 (RW DC)
    │                       │                                    │
    ├─ AS-REQ bob ─────────►│                                    │
    │                       │ секрета bob нет →                  │
    │                       ├─ переслать AS-REQ по RPC ─────────►│
    │                       │                                    │ проверить пароль, выдать TGT
    │                       │◄── AS-REP ─────────────────────────┤
    │◄─ AS-REP ─────────────┤                                    │
    │                       │ bob в разрешённом списке PRP? →    │
    │                       ├─ запрос RSO: дай секреты bob ─────►│
    │                       │◄── репликация одного объекта ──────┤  msDS-RevealedUsers += bob
    │                       │ теперь секрет bob есть локально    │
    │                       │                                    │
    ├─ AS-REQ bob (завтра) ►│                                    │
    │◄─ AS-REP от dc3 ──────┤  без обращения в офис              │

Первый вход — через офис, со всей задержкой канала. Получив ответ, RODC одновременно отдаёт его клиенту и просит записываемый контроллер реплицировать учётные данные Bob; тот сверяется с PRP и, если политика разрешает, передаёт их репликацией одного объекта (RSO, replicate single object): хеш пароля и ключи Kerberos. Все последующие входы — локально, и при падении канала Bob войдёт. Пользователь не из списка (администратор, приехавший в филиал) входит через офис каждый раз, и при падении канала войти не сможет — так и задумано.

Кеш можно заполнить заранее, не дожидаясь первого входа каждого пользователя, — это делают перед вводом RODC в работу, чтобы первый день не прошёл в ожидании. Заполнять нужно и пользователей, и их компьютеры. repadmin /rodcpwdrepl принимает RODC, записываемый контроллер, который применяет PRP, и DN учётных записей; для той, которую политика кешировать не разрешает, запрос не пройдёт:

PS C:\> 'Branch Users', 'Branch Computers' | ForEach-Object { Get-ADGroupMember $_ } | ForEach-Object { repadmin /rodcpwdrepl dc3.corp.example.com dc1.corp.example.com $_.DistinguishedName }

Установка в два этапа

RODC можно поднять одной командой Install-ADDSDomainController -ReadOnlyReplica под администратором домена. Но смысл RODC в филиале — чтобы администратор домена туда не ездил и чтобы его пароль там не вводили. Поэтому есть установка в два этапа: администратор домена из офиса создаёт учётную запись будущего RODC, а сотрудник филиала с обычными правами присоединяет к ней сервер.

Этап 1, в офисе:

PS C:\> New-ADGroup -Name 'Branch Admins' -GroupScope Global -Path 'OU=Groups,DC=corp,DC=example,DC=com'
PS C:\> New-ADGroup -Name 'Branch Users' -GroupScope Global -Path 'OU=Groups,DC=corp,DC=example,DC=com'
PS C:\> New-ADGroup -Name 'Branch Computers' -GroupScope Global -Path 'OU=Groups,DC=corp,DC=example,DC=com'
PS C:\> Add-ADGroupMember 'Branch Admins' -Members bob
PS C:\> Add-ADDSReadOnlyDomainControllerAccount -DomainControllerAccountName dc3 -DomainName corp.example.com -SiteName Branch -DelegatedAdministratorAccountName 'CORP\Branch Admins' -InstallDns -AllowPasswordReplicationAccountName 'CORP\Allowed RODC Password Replication Group', 'CORP\Branch Users', 'CORP\Branch Computers', 'CORP\Branch Admins' -Credential (Get-Credential CORP\alice.admin)

Message                                       Context                 RebootRequired  Status
-------                                       -------                 --------------  ------
Operation completed successfully              DCPromo.General.1               False  Success

В OU Domain Controllers появляется учётная запись DC3 с пометкой «неиспользуемая учётная запись контроллера», в сайте Branch — объект сервера, в каталоге — krbtgt_ для него, и в PRP прописаны группы филиала. -DelegatedAdministratorAccountName — делегированный администратор: группа, члены которой получат на dc3 права локального администратора (ставить обновления, менять драйверы, перезагружать), не имея никаких прав в домене. Это называется разделением административных ролей, и на обычном контроллере его нет: там локальных администраторов не существует. Менять содержимое SYSVOL делегат по умолчанию не может. В разрешённый список PRP он сам по себе не входит, а без кешированного пароля при упавшем канале не войдёт даже на dc3, поэтому Branch Admins есть в -AllowPasswordReplicationAccountName. Группу по умолчанию, Allowed RODC Password Replication Group, там тоже стоит перечислить явно, как это делает сценарий, который сохраняет мастер установки.

Этап 2, в филиале, под учётной записью Bob — обычного пользователя домена, члена Branch Admins:

PS C:\> Install-WindowsFeature AD-Domain-Services -IncludeManagementTools
PS C:\> Install-ADDSDomainController -DomainName corp.example.com -UseExistingAccount -Credential (Get-Credential CORP\bob) -SafeModeAdministratorPassword (Read-Host -AsSecureString) -ReplicationSourceDC dc1.corp.example.com -Force

-UseExistingAccount — присоединиться к заранее созданной учётной записи; права на это есть у того, кто указан делегированным администратором, и у Domain Admins. Остальное — сайт, DNS, глобальный каталог, PRP — берётся из заготовки, поэтому в этой команде таких параметров нет. Сервер должен называться dc3 и ещё не состоять в домене. Дальше всё как у обычного контроллера: репликация разделов (без секретов и без отфильтрованных атрибутов), SYSVOL, перезагрузка, регистрация в DNS. Для филиала с медленным каналом сюда же подходит IFM, причём носитель для RODC создаётся отдельной командой ifm "create rodc C:\IFM" (или create sysvol rodc C:\IFM вместе с SYSVOL), которая убирает из него кешированные секреты; создать такой носитель можно и на записываемом контроллере, и на другом RODC.

Проверка:

PS C:\> Get-ADDomainController dc3 | Select-Object HostName, Site, IsReadOnly, IsGlobalCatalog, OperatingSystem

HostName             Site   IsReadOnly IsGlobalCatalog OperatingSystem
--------             ----   ---------- --------------- ---------------
dc3.corp.example.com Branch       True            True Windows Server 2025 Standard

PS C:\> Get-ADDomainControllerPasswordReplicationPolicy -Identity dc3 -Allowed | Format-Table Name, ObjectClass -AutoSize

Name                                    ObjectClass
----                                    -----------
Allowed RODC Password Replication Group group
Branch Admins                           group
Branch Computers                        group
Branch Users                            group

PS C:\> Get-ADDomainControllerPasswordReplicationPolicy -Identity dc3 -Denied | Format-Table Name, ObjectClass -AutoSize

Name                                   ObjectClass
----                                   -----------
Denied RODC Password Replication Group group
Account Operators                      group
Administrators                         group
Backup Operators                       group
Server Operators                       group

PS C:\> Get-ADUser -Filter 'Name -like "krbtgt*"' | Select-Object Name, Enabled

Name        Enabled
----        -------
krbtgt        False
krbtgt_31337  False

Менять политику потом — Add-ADDomainControllerPasswordReplicationPolicy -Identity dc3 -AllowedList 'Branch Printers' и -DeniedList (сам командлет обращается к записываемому контроллеру, а RODC указывают в -Identity). Что получится для конкретного пользователя с учётом всех групп, показывает Get-ADAccountResultantPasswordReplicationPolicy -Identity bob -DomainController dc3: Allow, DenyExplicit (учётная запись в запрещённом списке), DenyImplicit (ни в одном из списков) или Unknown.


DNS на RODC

RODC с -InstallDns — DNS-сервер с теми же зонами из каталога, но только для чтения: Get-DnsServerZone -ComputerName dc3 показывает их с IsReadOnly = True. Клиенты филиала спрашивают имена у dc3, и он отвечает из своей копии; рекурсия и форвардеры у него свои, как у любого DNS-сервера.

Динамические обновления RODC принимать не может. Когда рабочая станция филиала пытается зарегистрировать A-запись, dc3 отвечает на её SOA-запрос именем контроллера с правом записи, клиент отправляет обновление туда, а dc3, увидев попытку, сам запрашивает RSO-репликацию этой записи с того контроллера — по умолчанию через 30 секунд и с опросом раз в три минуты, так что новая запись появляется у него не позже чем через три с половиной минуты, не дожидаясь расписания связи сайтов. Свои собственные записи RODC регистрирует тоже через записываемый контроллер, и по умолчанию только записи своего сайта и CNAME: записей NS и делегирования он не регистрирует.

Из-за этого в опции 6 DHCP филиала первым должен стоять dc3, вторым — контроллер офиса: при падении канала филиал продолжит разрешать имена, а при живом канале обновления дойдут куда нужно.


Если RODC украли

Сценарий, ради которого всё строилось. Сервер унесли вместе с диском; на диске — база с хешами паролей тех, кто в msDS-RevealedList. Стереть эти секреты нельзя, их можно только обесценить сменой паролей, поэтому Microsoft советует сбросить все кешированные пароли и собрать RODC заново. Убрать пользователей из разрешённого списка недостаточно: то, что уже попало на RODC, там осталось. Порядок действий:

  1. Сохранить список раскрытых учётных записей: после удаления учётной записи RODC его уже не получить.
  2. Удалить учётную запись RODC в оснастке Active Directory Users and Computers, подключённой к записываемому контроллеру: OU Domain Controllers, dc3, Delete. Мастер удаления предлагает сбросить пароли всех кешированных на нём учётных записей пользователей и выгрузить их список в файл — оба флажка стоят по умолчанию — и отдельным флажком, который нужно поставить самому, сбросить пароли кешированных учётных записей компьютеров. Готового аналога этого мастера в PowerShell нет; если сбрасывать пароли командами, то так:
PS C:\> Get-ADDomainControllerPasswordReplicationPolicyUsage -Identity dc3 -RevealedAccounts | Select-Object Name, ObjectClass, DistinguishedName | Export-Csv C:\Temp\dc3-revealed.csv -NoTypeInformation -Encoding UTF8   # шаг 1, до удаления
PS C:\> $revealed = Import-Csv C:\Temp\dc3-revealed.csv
PS C:\> $revealed | Where-Object ObjectClass -eq 'user' | ForEach-Object { Set-ADAccountPassword $_.DistinguishedName -Reset -NewPassword (ConvertTo-SecureString (New-Guid).Guid -AsPlainText -Force); Set-ADUser $_.DistinguishedName -ChangePasswordAtLogon $true }
PS C:\> $cred = Get-Credential CORP\alice.admin
PS C:\> $revealed | Where-Object { $_.ObjectClass -eq 'computer' -and $_.Name -ne 'DC3' } | ForEach-Object { Invoke-Command -ComputerName $_.Name -ScriptBlock { Reset-ComputerMachinePassword -Server dc1.corp.example.com -Credential $Using:cred } }

Reset-ComputerMachinePassword меняет пароль компьютера на той машине, где выполняется, поэтому его запускают на каждой станции через Invoke-Command, и пароль меняется с обеих сторон. Компьютеры, пароли которых сбросил мастер, так не починить: их придётся заново ввести в домен.

  1. Выдать пользователям новые пароли: после сброса они не войдут, пока администратор не задаст пароль.
  2. Убедиться, что вместе с RODC исчезла и его учётная запись krbtgt_31337 (Get-ADUser -Filter 'Name -like "krbtgt*"'), и собрать RODC заново.

Список раскрытых стоит просматривать регулярно и без всякой кражи: если там оказался администратор домена, значит, кто-то добавил его в разрешённую группу, и это повод разобраться.


Смотрим руками

Входящая репликация без исходящей:

PS C:\> repadmin /showrepl dc3

Branch\DC3
DSA Options: IS_GC DISABLE_OUTBOUND_REPL
Site Options: (none)
...
==== INBOUND NEIGHBORS ======================================

DC=corp,DC=example,DC=com
    Office\DC1 via RPC
        DSA object GUID: 2f3e4d5c-6b7a-4899-8a7b-6c5d4e3f2a1b
        Last attempt @ 2026-10-15 12:30:04 was successful.
...

В параметрах DISABLE_OUTBOUND_REPL: исходящей репликации у RODC нет. Записываемые контроллеры и не возьмут его источником: KCC на них пропускает объекты NTDS Settings с категорией NTDS-DSA-RO, а у RODC она именно такая. Как видит его клиент:

PS C:\> nltest /dsgetdc:corp.example.com /site:Branch
           DC: \\dc3.corp.example.com
      Address: \\192.168.2.10
 Dc Site Name: Branch
Our Site Name: Branch
        Flags: GC DS LDAP KDC TIMESERV DNS_DC DNS_DOMAIN DNS_FOREST CLOSE_SITE SELECT_SECRET WS DS_8 DS_9 DS_10 DS_13 KEYLIST

Нет флага WRITABLE, а вместо FULL_SECRET записываемого контроллера стоит SELECT_SECRET: по спецификации это и есть признак RODC. Клиентам, которым нужна запись (nltest /dsgetdc:corp.example.com /writable), Locator вернёт контроллер из Office.

Кто уже кеширован и кто входил:

PS C:\> Get-ADDomainControllerPasswordReplicationPolicyUsage -Identity dc3 -RevealedAccounts | Select-Object Name, ObjectClass

Name      ObjectClass
----      -----------
Bob Jones user
WS-BRANCH computer
DC3       computer

PS C:\> Get-ADDomainControllerPasswordReplicationPolicyUsage -Identity dc3 -AuthenticatedAccounts | Select-Object Name, ObjectClass

Name        ObjectClass
----        -----------
Bob Jones   user
WS-BRANCH   computer
Alice Admin user

Alice входила через филиал, но в раскрытых её нет: политика запретила. То же самое утилитой: repadmin /prp view dc3 reveal и repadmin /prp view dc3 auth2, а разрешённый и запрещённый списки — repadmin /prp view dc3 allow и deny. Первым аргументом всегда идёт RODC.

Кто выдал билет, видно в журнале Security: событие 4768 «запрошен TGT» пишет контроллер, который обработал запрос. Билеты Bob, чей секрет закеширован, выдаёт сам dc3, и события 4768 с его именем появляются в журнале dc3, а на dc1 их нет. Запрос Alice dc3 пересылает в офис, и событие 4768 о нём пишет dc1. Поле «Client Address» документация определяет как адрес компьютера, от которого получен запрос, так что там должен стоять адрес dc3, а не рабочей станции; для пересланных запросов это прямо не оговорено, и стоит проверить на своём стенде:

PS C:\> Get-WinEvent -ComputerName dc1 -FilterHashtable @{LogName='Security'; Id=4768} -MaxEvents 50 | Where-Object Message -match 'alice.admin' | Select-Object -First 1 -ExpandProperty Message

Сама рабочая станция знает меньше: klist tgt показывает билет и его ключ, но не контроллер, который его выдал; к каким контроллерам машина обращалась последнее время, показывает klist query_bind.

Какие атрибуты RODC не получает никогда:

PS C:\> $schema = (Get-ADRootDSE).schemaNamingContext
PS C:\> Get-ADObject -SearchBase $schema -LDAPFilter '(&(objectClass=attributeSchema)(searchFlags:1.2.840.113556.1.4.803:=512))' -Properties lDAPDisplayName | Sort-Object lDAPDisplayName | Select-Object -ExpandProperty lDAPDisplayName

msFVE-KeyPackage
msFVE-RecoveryPassword
msLAPS-EncryptedDSRMPassword
msLAPS-EncryptedDSRMPasswordHistory
msLAPS-EncryptedPassword
msLAPS-EncryptedPasswordHistory
msLAPS-Password
msPKI-AccountCredentials
msPKI-DPAPIMasterKeys
msPKI-RoamingTimeStamp
msTPM-OwnerInformation

Бит 512 (0x200) — RODC filtered. Рядом бит 128 (0x80) — конфиденциальные атрибуты, которые и на обычном контроллере читают только по явному праву. У паролей LAPS searchFlags равен 904: это 0x200 и 0x80 плюс 0x100 (не писать значения в аудит) и 0x8 (сохранять при удалении). Атрибуты LAPS в схеме появляются после Update-LapsADSchema; без него список короче на пять строк.

И проверка главного обещания — филиал работает без канала. Блокируем на dc3 трафик к офису (New-NetFirewallRule -DisplayName 'Test WAN down' -Direction Outbound -RemoteAddress 192.168.1.0/24 -Action Block), и на рабочей станции филиала:

PS C:\> runas /user:CORP\bob cmd           ← входит: пароль кеширован на dc3
PS C:\> runas /user:CORP\alice.admin cmd   ← «There are currently no logon servers available»

Правило не забыть удалить: Remove-NetFirewallRule -DisplayName 'Test WAN down'.


Источники


Три контроллера в двух сайтах, и каждый из них проверяет время в билетах с точностью до пяти минут. Чьё время считается правильным, откуда оно берётся и что происходит с доменом, у которого часы разошлись, — тема следующей части, последней в блоке о домене.

Дальше — часть 12: как устроено время в домене. Иерархия W32Time от PDC-эмулятора до рабочей станции, как члены домена находят источник времени и почему он подписан, настройка внешнего источника на PDC, виртуальные контроллеры и часы гипервизора, что ломается при расхождении часов и как это выглядит в журналах.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.