В этой статье
Пароль знают все, а поменять его не может никто
Пароль сервисной учётной записи записан в трёх местах. Менять его страшно: неизвестно, какая служба упадёт следующей. Переведём совместимую службу на gMSA и проверим, кому теперь доверено получать её пароль.
Привет, коллеги. Меня зовут Максим, я системный администратор. Представим знакомую ситуацию: ночной отчёт работает от доменной учётной записи, её пароль однажды сообщили подрядчику, потом записали в инструкцию, а затем использовали ещё для двух заданий. Когда один человек увольняется, выясняется, что смена пароля — это небольшое расследование с неопределённым временем восстановления.
Решение в лоб — поставить «Пароль не истекает» и убрать запись из общего блокнота. От этого неизвестные зависимости не исчезнут. Секрет останется общим, а ответственность за его смену снова будет отложена до первой аварии.
Я предлагаю изменить саму модель: люди не разносят пароль по службам; Windows получает и обслуживает его для согласованных компьютеров. Это групповая управляемая служебная учётная запись — group Managed Service Account, или gMSA. Пароль у неё есть. Его получение становится отдельным контролируемым полномочием.
Мы пройдём от поиска зависимостей до пилотной службы, разберём KDS, разрешённые узлы, права и SPN, воспроизведём полезную операцию и подготовим откат. Только практика, конфиги и команды — с понятной областью применения и без обещания, что любое старое приложение внезапно научится работать с gMSA.
Проблема: четыре долга одной служебной учётки
Прежде чем строить защиту, нужно определить, где накопился риск. У одной записи svcReports могут быть четыре разные проблемы.
- Неизвестные потребители (dependency debt). Учётная запись указана у трёх служб, одного пула IIS и задания на старом сервере. Смена пароля ломает не тот компонент, который администратор сейчас проверяет.
- Избыточные полномочия (privilege debt). Для устранения прежней ошибки её включили в локальные администраторы или дали запись на весь файловый ресурс. Новое управление паролем не отменяет эти разрешения.
- Неясный владелец имени (identity debt). SPN остался у старой записи, приложение работает через псевдоним, а успешный вход на самом деле использует NTLM. Перенос только поля Log On не переносит всю модель аутентификации.
- Непроверенный возврат (recovery debt). Старый пароль неизвестен или уже отозван, инструкция восстановления состоит из фразы «вернуть как было». Переключить имя обратно недостаточно.
Поэтому маршрут состоит из четырёх уровней: инвентаризация и совместимость; подготовка AD и получателей пароля; права и пилот; проверка операции, ротации и возврата. Мы не ищем способ спрятать старый секрет поглубже. Мы хотим уменьшить число мест, где он вообще должен обслуживаться человеком.
Границы примера: один домен, один узел, одна новая служба
Сквозной сценарий — вымышленный домен ad.example.com с NetBIOS-именем EXAMPLE. Контроллеры домена уже существуют, APP01 — доменный сервер приложений, APP02 — контрольный узел, FS01 — отдельный файловый сервер. Эти имена нужно заменить значениями своего изолированного стенда, а не выполнять команды в случайном открытом домене.
Основной ориентир — Windows Server 2022/2025 и 64-разрядная Windows PowerShell 5.1. gMSA появилась раньше, но поддержка механизма операционной системой не означает поддержку конкретным приложением и его установщиком. Для лаборатории заранее подтверждаем подходящие уровни домена и леса, доступный записываемый DC, исправные DNS, время и репликацию. AD PowerShell на APP01 устанавливается через RSAT; превращать сервер приложений в контроллер домена не требуется.
В комплекте есть исходник небольшой службы AFGmsaPilot. Она не слушает сеть, не создаёт документы и не выполняет команды из внешних источников. Каждые 15 секунд записывает свою личность и UTC-время в отдельный каталог; при заданном UNC-пути пробует прочитать один байт учебного файла. Это настоящий процесс службы с ServiceBase, а не переименованный powershell.exe, который SCM не умеет обслуживать как Windows-службу. Основа реализации — модель ServiceBase Microsoft.
Честная граница проверки: команды сверены с документацией, файлы прошли статический разбор и сопоставление с текстом. В рамках подготовки этой публикации я не запускал Windows-домен и не измерял работу службы. Поэтому ниже даны ожидаемые результаты для воспроизведения, а не выдуманный протокол удачного внедрения.

Сначала разберём существующую svcReports. Затем создадим отдельную gmsaReport и допустим только APP01. Не будем ради первой проверки переносить сразу все задания и продуктивный сервер отчётов.
Уровень 1. Инвентаризация: ищем зависимость, а не знакомое имя
Решение в лоб — открыть services.msc на главном сервере и найти старое имя. Так мы увидим только один класс потребителей на одном узле. Поле задания может содержать SID или UPN, а приложение — хранить собственные реквизиты независимо от личности процесса.
В комплекте есть Get-AFServiceInventory.ps1. Он опрашивает явно перечисленные компьютеры через CIM, собирает совпадения в службах и заданиях и отдельно сохраняет покрытие опроса. Ошибка доступа не превращается в «ничего не найдено». Утилита не извлекает пароли и не экспортирует аргументы заданий или строки запуска, в которых иногда лежат секреты.
# Windows PowerShell 5.1; replace the fictional lab names.
Import-Module ActiveDirectory
$old = Get-ADUser -Identity 'svcReports' -Properties UserPrincipalName
$aliases = @('EXAMPLE\svcReports',$old.UserPrincipalName,$old.SID.Value)
$aliases = @($aliases | Where-Object { $_ })
.\Get-AFServiceInventory.ps1 -ComputerName 'APP01','APP02','FS01' -AccountAlias $aliases -OutputDirectory 'C:\PrivateAudit\Reports-20261025'
# On each IIS server, locally, if WebAdministration is installed:
Import-Module WebAdministration
Get-ChildItem IIS:\AppPools | Select-Object Name,@{n='IdentityType';e={$_.processModel.identityType}},@{n='UserName';e={$_.processModel.userName}}
AccountAlias — это явный список эквивалентных представлений одной записи: DOMAIN\name, UPN, SID. Если встречается ещё один реальный вариант записи, добавьте его после проверки. Не подменяйте точное сравнение поиском подстроки Reports: так легко смешать независимые службы.
Как это работает: matches.csv отвечает «где найден потребитель», coverage.csv — «что удалось прочитать». Пустой matches при FAILED в coverage не доказывает отсутствие зависимости. Задания проверяются по Principal.UserId; Get-ScheduledTask поддерживает CIM-сеансы. IIS рассматривается отдельно локальной командой на его узле.
Дальше вручную сверяем SQL Agent jobs и прокси, планировщики приложений, интеграции, службы на выключенных узлах, конфигурационные хранилища и журналы аутентификации. Нужен хотя бы один полный бизнес-цикл наблюдения: ночная задача и месячное закрытие могут не встретиться в одном дне. Отсутствие события при выключенном аудите ничего не доказывает.
Результат уровня — таблица «потребитель → операция → ресурс → владелец → совместимость → возврат». Только после неё выбираем кандидата на пилот.
Где gMSA подходит, а где нужна другая модель
Я проверяю совместимость на трёх уровнях: сама Windows-служба умеет запускаться с управляемой записью; конкретная версия продукта поддерживается производителем; внешние ресурсы способны принять эту доменную личность. Совпадение одного пункта не закрывает остальные.
| Сценарий | Что проверить | Граница |
|---|---|---|
| Совместимая Windows-служба | Поддержка продукта, SCM, service logon, ресурсы | Не каждое изменение SCM поддержано производителем |
| Пул IIS | Версия приложения, identity пула, профиль, ключи | Настраивается отдельно от Windows-службы |
| Задание планировщика | Поддержанный logon type, batch logon, сетевой доступ | S4U не универсальная замена паролю для сети |
| SQL Server и приложения 1С | Точная версия, документация производителя, поддержанный инструмент | Не переносим учебный Change на SQL/1С вслепую |
| Приложение с собственным строковым паролем | Нативная поддержка управляемой записи или иной механизм | gMSA не выдаёт постоянный пароль для вставки в форму |
| Workgroup / Linux / контейнеры | Отдельная архитектура и подтверждённая поддержка | Данный доменный Windows-пилот их не покрывает |
| Failover Cluster | Что является кластерной службой, а что нагрузкой поверх неё | Поддержка нагрузки не означает смену личности Cluster service |
У Microsoft перечислены SCM-службы, пулы IIS и задания как применимые механизмы, но это не разрешение автоматически переконфигурировать любой продукт. Например, SQL Server требует своего поддержанного порядка изменения учётной записи; ряд установщиков меняет не только SCM, но и ACL, ключи и другие параметры. Управление gMSA.
Если приложение просит строковый пароль и самостоятельно выполняет LDAP bind, подключение к SFTP или внешнему API, поле gMSA не станет заменой этому секрету. У процесса может быть gMSA, а у интеграции — отдельный API-токен. Это две разные задачи.
Отдельно проверяем профиль пользователя, DPAPI, EFS, закрытые ключи сертификатов и настройки, сохранённые от имени прежней svcReports. У новой gMSA другой SID. Членство, доступ к приватному ключу и возможность расшифровать старые данные не переезжают вместе с названием службы.
Не расширяем тему до dMSA из Windows Server 2025, контейнерных credential spec или трансграничной архитектуры лесов: у них свои предпосылки и способы получения личности. Наш пилот — обычный доменный сервер и совместимая Windows-служба.
Уровень 2. KDS: не создаём новый корневой ключ на каждую службу
KDS — Key Distribution Service — участвует в формировании управляемых паролей на контроллерах домена. Разрешённый узел получает необходимые учётные данные через AD; отдельную сетевую службу «KDS-сервер приложения» для каждого проекта поднимать не нужно. Общая механика описана в обзоре gMSA.
Решение в лоб — найти Add-KdsRootKey в чужой инструкции и запускать его перед каждым New-ADServiceAccount. Так инфраструктурная подготовка превращается в накопление ключей без понимания последствий. Сначала читаем состояние, а не меняем его.
Import-Module ActiveDirectory
Get-ADForest | Select-Object Name,ForestMode
Get-ADDomain | Select-Object DNSRoot,NetBIOSName,DomainMode
Get-ADDomainController -Filter * | Select-Object HostName,OperatingSystem,Site,IsReadOnly
Get-KdsRootKey | Select-Object KeyId,CreationTime,EffectiveTime
repadmin.exe /replsummary
# On APP01, elevated; tests only, no repair operation:
Test-ComputerSecureChannel -Verbose
Resolve-DnsName -Type SRV '_ldap._tcp.dc._msdcs.ad.example.com'
w32tm.exe /query /status
Внимание: создание KDS root key — изменение уровня леса. Его не включаем в сценарий автоматического «исправления». Если ключ уже существует, проверяем его состояние и репликацию. Если ключей несколько, ничего не удаляем ради красивого списка: выясняем происхождение и используемые зависимости.
Если ключа действительно нет и операция согласована, выполняем отдельный шаг:
# ONE-TIME forest operation, only after approval and if no key exists.
# Run in an authorized DC session with the Kds module.
$keys = @(Get-KdsRootKey)
if ($keys.Count -ne 0) { throw 'A KDS root key already exists. Review it; do not create another.' }
Add-KdsRootKey -EffectiveImmediately
# STOP HERE. Allow at least the documented 10-hour convergence window,
# and verify replication before creating/using the pilot gMSA.
Get-KdsRootKey | Select-Object KeyId,CreationTime,EffectiveTime
Название EffectiveImmediately не отменяет ожидание. Microsoft описывает десятичасовое окно сходимости для безопасного начала использования. Даже после ожидания неисправная или редко выполняемая репликация остаётся неисправной; таймер её не лечит. Трюк с EffectiveTime десять часов назад допустим только в соответствующем одно-DC тестовом сценарии и здесь намеренно не включён в рабочие команды. Создание KDS root key.
Есть актуальный нюанс: Microsoft отдельно описывает отказы при нескольких ключах и определённых сочетаниях версий/обновлений DC после изменений апреля 2026 года. Это не диагноз по одному False и не повод немедленно создавать ещё один ключ. Сначала фиксируем KeyId, времена, версии и обновления DC, затем сопоставляем условия с конкретным разбором Microsoft.
Получатели пароля: маленькая группа вместо всех серверов
В нашем сценарии пароль нужен только APP01. Создаём отдельную security group для разрешённых компьютеров и добавляем именно объект компьютера. Администратор, который выполняет PowerShell-команду, и компьютер, который затем запускает службу, — разные субъекты.
# Authorized AD management session; fictional lab values.
Import-Module ActiveDirectory
$domain = Get-ADDomain
if ($domain.DNSRoot -ne 'ad.example.com') { throw 'Wrong domain: adapt and review the lab values first.' }
if (Get-ADGroup -Filter "SamAccountName -eq 'GG-gMSA-Report-Hosts'") { throw 'Host group already exists; inspect it.' }
if (Get-ADServiceAccount -Filter "Name -eq 'gmsaReport'") { throw 'gMSA already exists; inspect it.' }
$hostComputer = Get-ADComputer -Identity 'APP01'
$group = New-ADGroup -Name 'GG-gMSA-Report-Hosts' -SamAccountName 'GG-gMSA-Report-Hosts' -GroupScope Global -GroupCategory Security -Path $domain.UsersContainer -PassThru
Add-ADGroupMember -Identity $group -Members $hostComputer
$parameters = @{
Name = 'gmsaReport'
DNSHostName = 'gmsaReport.ad.example.com'
PrincipalsAllowedToRetrieveManagedPassword = $group
KerberosEncryptionType = @('AES128','AES256')
ManagedPasswordIntervalInDays = 30
Description = 'Report pilot; owner IT; authorized host APP01 only'
}
New-ADServiceAccount @parameters
Get-ADServiceAccount -Identity 'gmsaReport' -Properties PrincipalsAllowedToRetrieveManagedPassword,KerberosEncryptionType,ServicePrincipalNames | Select-Object Name,SamAccountName,PrincipalsAllowedToRetrieveManagedPassword,KerberosEncryptionType,ServicePrincipalNames
Как это работает: PrincipalsAllowedToRetrieveManagedPassword задаёт политику получения пароля. В примере она ссылается на одну группу, а та содержит APP01. Мы явно выбираем AES128/AES256 и интервал 30 дней при создании. Не полагаемся на RC4 и не обещаем, что этот выбор исправит несовместимый клиент. Параметры и ограничения описаны в New-ADServiceAccount.
DNSHostName в объекте gMSA не поднимает приложение, не создаёт автоматически нужную DNS-запись сервиса и не заменяет его конкретные SPN. Клиент всё ещё должен обращаться к настоящему имени настоящего сервиса.
Решение в лоб — добавить Domain Computers: тест на каждом сервере быстро станет удобнее. Но компрометация ненужного узла теперь окажется в той же области доверия. Я делю gMSA по назначению и риску, а не использую одну запись для SQL, резервирования, мониторинга и всех интеграций.
Интервал ротации выбирают при создании: не строим план на предположении, что любое его последующее изменение поддерживается тем же cmdlet. А Set-ADServiceAccount с PrincipalsAllowedToRetrieveManagedPassword задаёт полный новый список, а не добавляет один элемент. Перед изменением сохраняем весь прежний список и оставляем все необходимые узлы. Это отдельно подчёркивает руководство Microsoft по проверке gMSA.
Кто может получить пароль: смотрим дальше списка членов
Теперь главная интрига. У записи больше нет пароля в инструкции, но остаётся полномочие получить управляемый секрет. Атрибут msDS-GroupMSAMembership хранит security descriptor для соответствующей проверки доступа — это не просто декоративное поле со списком хостов. Описание атрибута Microsoft.
Я проверяю три уровня доверия. Первый — разрешённые субъекты и рекурсивный состав групп. Второй — владельцы и ACL gMSA и управляющих групп: кто может переписать политику получения пароля или изменить состав группы. Третий — контроль самих разрешённых компьютеров и службы: локальный администратор такого узла находится внутри существенной границы доверия к этой личности.
#requires -Version 5.1
#requires -Modules ActiveDirectory
[CmdletBinding()]
param([Parameter(Mandatory)][string]$Identity)
$ErrorActionPreference = 'Stop'
Import-Module ActiveDirectory
$account = Get-ADServiceAccount -Identity $Identity -Properties PrincipalsAllowedToRetrieveManagedPassword,ServicePrincipalNames,MemberOf,KerberosEncryptionType
$targets = New-Object 'System.Collections.Generic.List[object]'
$targets.Add([pscustomobject]@{Role='gMSA object'; DN=$account.DistinguishedName})
$retrievers = foreach ($principal in @($account.PrincipalsAllowedToRetrieveManagedPassword)) {
$object = Get-ADObject -Identity $principal -Properties sAMAccountName,objectSid
$targets.Add([pscustomobject]@{Role='Authorized principal'; DN=$object.DistinguishedName})
$members = @()
if ($object.ObjectClass -eq 'group') {
$members = @(Get-ADGroupMember -Identity $object.DistinguishedName -Recursive | Select-Object Name,SamAccountName,ObjectClass,DistinguishedName)
}
[pscustomobject]@{Name=$object.Name; Class=$object.ObjectClass; DN=$object.DistinguishedName; ExpandedMembers=$members}
}
$aclEvidence = foreach ($target in $targets) {
$acl = Get-Acl -LiteralPath ('AD:\'+$target.DN)
[pscustomobject]@{
Role=$target.Role; DN=$target.DN; Owner=$acl.Owner
ACEs=@($acl.Access | Select-Object IdentityReference,AccessControlType,ActiveDirectoryRights,ObjectType,InheritedObjectType,IsInherited,InheritanceType)
}
}
[pscustomobject]@{
CapturedUtc=(Get-Date).ToUniversalTime().ToString('o')
Account=$account.SamAccountName
SID=[string]$account.SID
MemberOf=@($account.MemberOf)
SPNs=@($account.ServicePrincipalNames)
Encryption=[string]$account.KerberosEncryptionType
RetrieverPrincipals=@($retrievers)
AclEvidence=@($aclEvidence)
EffectiveAccessComputed=$false
Limitations='Evidence only, not a complete effective-access graph. Review nested-group control, ancestor ACL changes, computer-object control, local admins, delegated rights and deny ACEs separately. No password attribute is requested.'
} | ConvertTo-Json -Depth 10
Это полный код Get-AFGmsaAuthorization.ps1 из комплекта. Запуск: .\Get-AFGmsaAuthorization.ps1 -Identity gmsaReport. Он выводит JSON с разрешёнными субъектами, раскрытыми членами, владельцами и ACE. Значение EffectiveAccessComputed=false оставлено намеренно: это доказательства для анализа, а не универсальный расчёт всех эффективных прав в лесу.
Отдельно проверяются администраторы вложенных групп, изменение наследования/ACL родительских контейнеров, права на компьютерные объекты, делегирование и запрещающие ACE. Даже корректное текущее членство не означает, что посторонний не может изменить его через другую управляемую группу.
Скрипт не запрашивает атрибут с самим паролем и не демонстрирует извлечение секрета. Для аудита нам нужны границы полномочий. Право получать пароль не равно праву читать отчёты; наоборот, ACL на папке не объясняет, какие компьютеры способны выступать этой личностью. Эти проверки нельзя объединить в одну зелёную галочку.
Практический результат — имя владельца каждого пути изменения. Если на вопрос «кто может добавить сюда ещё один компьютер?» никто не отвечает, миграция пока лишь убрала видимую запись из блокнота.
APP01 разрешён, но Test-ADServiceAccount возвращает False
Добавление APP01 в группу меняет каталог, но уже выданный контекст компьютера может ещё не отражать новое членство. Решение в лоб — несколько раз нажать gpupdate, а затем расширить разрешения до всех компьютеров. Это смешивает применение политик с обновлением контекста безопасности.
Для лаборатории планируем перезапуск APP01 после репликации изменения группы. Для продуктивного узла — согласованное окно и поддержанный порядок обновления контекста, а не скрытая команда перезагрузки внутри скрипта. Microsoft прямо указывает на возможность необходимости перезапуска при устаревшем билете/членстве. Диагностика доступа хоста к gMSA.
# APP01: elevated Windows PowerShell 5.1 after group-token refresh.
# Install this feature only if the AD module is absent and the change is approved.
Get-WindowsFeature RSAT-AD-PowerShell
# Install-WindowsFeature RSAT-AD-PowerShell
Import-Module ActiveDirectory
Get-ADComputer -Identity $env:COMPUTERNAME | Get-ADPrincipalGroupMembership | Select-Object Name
Install-ADServiceAccount -Identity 'gmsaReport'
Test-ADServiceAccount -Identity 'gmsaReport'
# Expected on APP01: True. On an explicitly unauthorized lab host: False.
# Never grant the second host access merely to turn a negative test green.
Install-ADServiceAccount выполняется локально с повышенными правами на разрешённом узле. Он не назначает запись всем службам и не выдаёт ей доступ к ресурсам. Для установки уже должна быть задана политика получателей. Install-ADServiceAccount.
Что означает True? Учётная запись готова к использованию с точки зрения проверки на этом хосте. Это не доказательство, что службе хватает прав на запуск, файл доступен по SMB, Kerberos выбран приложением или SQL разрешил вход. Test-ADServiceAccount проверяет свой слой.
На APP02, который намеренно не добавлен в группу, отрицательный результат полезен. Но False сам по себе ещё не доказывает отказ именно политики: сначала убеждаемся, что этот узел исправно видит AD и что другие предпосылки сопоставимы. Мы не лечим контрольный запрет расширением списка разрешённых.
Уровень 3. Права: получить пароль, войти службой и прочитать файл — разные вещи
У gmsaReport не должно появляться членство Domain Admins или локальных Administrators ради устранения одной ошибки доступа. Я выдаю минимально необходимое право для конкретной операции. Три вопроса проверяются независимо: может ли APP01 получить секрет; разрешён ли самой личности service logon; допускает ли ресурс нужное действие.
Право Log on as a service задаётся в Computer Configuration → Windows Settings → Security Settings → Local Policies → User Rights Assignment. Для лаборатории это отдельная политика на APP01. При изменении списка сохраняются существующие необходимые записи. Deny log on as a service имеет приоритет над разрешением; принадлежность к запрещённой группе тоже имеет значение. Управление правом service logon.
# APP01, elevated; choose a NEW private directory.
$dir = 'C:\PrivateAudit\GmsaRights-20261025'
if (Test-Path -LiteralPath $dir) { throw 'Choose a new private directory.' }
$null = New-Item -ItemType Directory -Path $dir
secedit.exe /export /mergedpolicy /cfg "$dir\rights.inf" /areas USER_RIGHTS /quiet
if ($LASTEXITCODE -ne 0) { throw 'secedit export failed.' }
Select-String -LiteralPath "$dir\rights.inf" -Pattern '^SeServiceLogonRight','^SeDenyServiceLogonRight'
gpresult.exe /scope computer /h "$dir\computer-policy.html"
if ($LASTEXITCODE -ne 0) { throw 'gpresult failed.' }
# Review the winning policy and group membership; a text match alone is not an effective-rights calculation.
secedit здесь только экспортирует объединённую политику, а gpresult помогает найти источник настройки. Это не массовое исправление user rights и не расчёт всех групповых пересечений. Мы не применяем чужой security template поверх production. Синтаксис экспорта описан в secedit /export.
Дальше — файлы. В учебной службе каталог бинарника доступен gMSA на чтение и выполнение, а отдельный State — на изменение. Права на родительский код не должны позволять обслуживающему пользователю незаметно подменить программу, запускаемую под более сильной личностью. В комплекте ACL задаются только для нового собственного каталога и никогда не применяются рекурсивно к чужой папке.
Для SMB одновременно нужны разрешения share и NTFS; для базы — отдельный login/роль с нужным минимумом; для сертификата — доступ к закрытому ключу. Совпадение имени старой и новой записи не переносит SID и ACL. Именно поэтому ошибка приложения после успешного запуска не означает, что gMSA «не умеет работать».
Пилот: настоящая служба без настоящих рабочих документов
Решение в лоб — назначить gMSA продуктивной службе и посмотреть, запустится ли она. Я сначала отделяю проверку механизма от риска бизнес-приложения. В комплекте находятся AFGmsaPilot.cs и New-AFGmsaLabService.ps1; первый содержит полный исходник, второй компилирует его штатным Add-Type в Windows PowerShell 5.1 и создаёт новую службу с ручным запуском.
Внимание: этот опыт создаёт службу и меняет ACL нового каталога только на изолированном APP01. Существующие каталог и служба вызывают остановку сценария. -WhatIf показывает намерение, но не компилирует код и не доказывает работоспособность. Скрипт не запускает службу автоматически. Изначально SCM указывает LocalSystem; перед первым запуском мы обязаны переключить и проверить gMSA.
# Isolated APP01 only; reviewed kit files must be in the current directory.
$ErrorActionPreference = 'Stop'
Import-Module ActiveDirectory
if (-not (Test-ADServiceAccount -Identity 'gmsaReport')) { throw 'Host is not ready for this gMSA.' }
.\New-AFGmsaLabService.ps1 -GmsaAccount 'EXAMPLE\gmsaReport$' -WhatIf
.\New-AFGmsaLabService.ps1 -GmsaAccount 'EXAMPLE\gmsaReport$'
# Do not start the initially stopped service under LocalSystem.
# Grant Log on as a service through the scoped lab policy first.
$svc = Get-CimInstance Win32_Service -Filter "Name='AFGmsaPilot'"
if ($svc.State -ne 'Stopped' -or $svc.StartName -ne 'LocalSystem') { throw 'Unexpected pilot state.' }
$svc | Select-Object Name,StartName,StartMode,State | Export-Clixml 'C:\ProgramData\AdminFuture\GmsaPilot\before-identity.xml'
# Empty string is intentional; do not enter or extract a gMSA password.
$result = Invoke-CimMethod -InputObject $svc -MethodName Change -Arguments @{StartName='EXAMPLE\gmsaReport$';StartPassword=''}
if ($result.ReturnValue -ne 0) { throw "Service change failed: $($result.ReturnValue)" }
$after = Get-CimInstance Win32_Service -Filter "Name='AFGmsaPilot'"
if ($after.StartName -ine 'EXAMPLE\gmsaReport$') { throw 'Identity mismatch: do not start.' }
Start-Service -Name AFGmsaPilot
Get-CimInstance Win32_Service -Filter "Name='AFGmsaPilot'" | Select-Object Name,StartName,State,ProcessId
Get-Content -LiteralPath 'C:\ProgramData\AdminFuture\GmsaPilot\State\status.txt'
# The first timer tick is asynchronous: if the file is not present yet, recheck
# the service state/logs, then read it again. Expected Identity ends in gmsaReport$.
Здесь используется CIM-метод Change: пустая строка StartPassword намеренна, это не ручной пароль записи. Проверяется ReturnValue, а затем фактически сохранённый StartName. Успешный вызов изменения ещё не означает успешный запуск. Общие параметры и коды возврата — в Win32_Service.Change.
Как это работает: SCM запускает совместимый сервис с gMSA, служба записывает WindowsIdentity своего процесса, SID и время. Имя заканчивается знаком $, но одного символа недостаточно для идентификации: сравниваем SID с объектом AD. Статус Running и наличие старого status.txt тоже недостаточны. Нужны новое UTC-время, правильная личность и обновление файла после перезапуска.
Отдельный исходник позволяет увидеть, что именно исполняется. Здесь нет загрузки бинарника с неизвестного сервера и нет скрытой отправки отчёта наружу. Но факт публикации исходника не заменяет вашу проверку кода и локальное испытание перед запуском. Для продуктивного приложения вместо этого учебного механизма применяется поддержанный производителем порядок изменения личности.
Проверяем полезную операцию из контекста gMSA
Когда администратор сам открыл сетевую папку, он проверил собственный токен. Служба от этого не получила ни одного дополнительного права. Нам нужен запрос из её процесса.
На FS01 заранее готовится отдельный учебный share GmsaPilot с безвредным canary.txt. gmsaReport получает чтение на уровне share и NTFS, без записи и без расширения на весь диск. Этот внешний ресурс и его ACL не создаются нашим установщиком службы: их готовит ответственный администратор файлового сервера по отдельному плану. Для отрицательной проверки готовится второй учебный ресурс без доступа gMSA.
# APP01 lab: existing canary on a separately prepared FS01 lab share.
# gMSA needs READ on both share and NTFS. Do not use a production document.
Set-Content -LiteralPath 'C:\ProgramData\AdminFuture\GmsaPilot\probe-target.txt' -Value '\\FS01.ad.example.com\GmsaPilot\canary.txt' -Encoding ASCII
# After the next 15-second probe, read a fresh UTC timestamp and UNC=read OK.
Get-Content -LiteralPath 'C:\ProgramData\AdminFuture\GmsaPilot\State\status.txt'
# Control case: use a second lab share that denies this gMSA, then restore the allowed path.
# Expect UNC=ERROR: UnauthorizedAccessException or the specific observed error.
# Never change a production ACL just to manufacture this failure.
Как это работает: конфигурация сообщает службе только UNC-путь; она открывает файл для чтения и записывает результат, не копируя содержимое в отчёт. Операция выполняется процессом службы, а не интерактивной PowerShell-сессией администратора. Поэтому «мой Проводник открыл папку» больше не подменяет нужный опыт.
| Проверка | Разрешённый случай | Отрицательный контроль | Что доказывает |
|---|---|---|---|
| Получение gMSA | APP01: Test=True | APP02: False при исправных предпосылках | Границу допуска хостов |
| Личность процесса | Свежий status с SID gMSA | Иная личность — провал приёмки | Кто выполняет код |
| Чтение SMB | Учебный файл: read OK | Другой ресурс: access denied | Доступ из процесса к ресурсу |
| Повторный запуск | Новый статус после stop/start | Старый файл не принимается | Новый вход и работу процесса |
| Ротация | Полезная операция после плановой смены | Не подменяем первым Test=True | Поведение на длительном цикле |
Нельзя считать одинаковыми Access denied, отсутствие DNS и недоступный TCP 445. В отрицательном опыте сервер и файл должны существовать, а отличие должно состоять именно в согласованном разрешении. Не меняем ради проверки ACL рабочего документа.
Затем останавливаем и запускаем только учебную службу и снова читаем свежий статус. Перезапуск APP01 проверяется отдельным согласованным опытом; поскольку служба создана Manual, автоматического старта после reboot от неё не ждём. Для проверки boot-сценария нужен явно изменённый startup type и критерии зависимостей. Полный цикл автоматической ротации — более длительный этап пилота, а не то, что можно доказать первым True за пять минут.
SPN: имя сервиса не переезжает вместе с Log On
AFGmsaPilot только инициирует исходящий SMB-запрос. Для этого не нужно регистрировать на gmsaReport HTTP/reports или забирать у FS01 его CIFS SPN. У сервера файлов остаётся собственная личность, а gMSA выступает клиентом.
Другая история — входящая Kerberos-аутентификация к приложению, например HTTP/reports.ad.example.com. Билет предназначен конкретному SPN и его владельцу в AD. Если процесс уже сменил личность, а имя осталось у svcReports, пароль gMSA может прекрасно получаться, но приложение не сможет использовать ожидаемые ключи. Автоматическая регистрация конкретного продукта тоже зависит от его реализации и прав.
# READ-ONLY examples for a DIFFERENT, inbound HTTP service.
# The outbound-only AFGmsaPilot service does NOT need this HTTP SPN.
setspn.exe -L 'EXAMPLE\svcReports'
setspn.exe -L 'EXAMPLE\gmsaReport$'
setspn.exe -F -Q 'HTTP/reports.ad.example.com'
# On a separate test client in the relevant user logon session:
klist.exe get 'HTTP/reports.ad.example.com'
klist.exe
# A ticket in cache does not prove the application used it; inspect server-side authentication evidence.
Решение в лоб — прописать тот же SPN на обе записи. Это создаёт конфликт, а не плавную миграцию. Для добавления используется setspn -S с проверкой дублей, а перед операцией ищется существующий владелец. Флаг -F расширяет запрос до леса. Синтаксис setspn.
Ниже отдельный пример изменения только для согласованного входящего HTTP-сервиса, не для нашей учебной службы:
# CHANGE WINDOW ONLY. Confirm old owner, service support and rollback first.
# Applies only to the inbound HTTP example, never to AFGmsaPilot.
setspn.exe -D 'HTTP/reports.ad.example.com' 'EXAMPLE\svcReports'
if ($LASTEXITCODE -ne 0) { throw 'Removal failed; stop and inspect ownership.' }
setspn.exe -S 'HTTP/reports.ad.example.com' 'EXAMPLE\gmsaReport$'
if ($LASTEXITCODE -ne 0) { throw 'Registration failed; inspect and restore the recorded old owner through the rollback procedure.' }
setspn.exe -F -Q 'HTTP/reports.ad.example.com'
# Do not proceed with service cutover unless the unique owner is correct.
Внимание: между удалением и новым назначением возможен перерыв аутентификации. Должны быть записаны прежний владелец, окно, порядок переключения приложения и обратная последовательность. Если добавление не прошло, останавливаемся, читаем состояние и возвращаем конкретное имя подтверждённому прежнему владельцу; не удаляем все SPN записи.
Старая сессия и билет в кэше могут исказить опыт. Проверяем новый вход с отдельного тестового клиента, по имени сервиса, с серверным подтверждением способа аутентификации. Не очищаем машинные билеты production целиком. И ещё: правильный SPN сам по себе не разрешает второй hop от имени пользователя. Делегирование — отдельная задача, оно не переносится автоматически со старой записи. Разбор Kerberos и gMSA Microsoft.
Ошибка запуска: идём по слоям, а не меняем пароль наугад
Вместо «gMSA не работает» я хочу четыре факта: какой узел запускает службу, какой StartName сохранён, что вернул Test-ADServiceAccount и на каком шаге появилась первая ошибка. Затем связываем время, имя записи и конкретный журнал.
# APP01; local checks only, no repair or ticket purge.
Test-ADServiceAccount -Identity 'gmsaReport'
Get-CimInstance Win32_Service -Filter "Name='AFGmsaPilot'" | Select-Object Name,State,StartName,ExitCode,ProcessId
$events = @{
LogName = 'System'
ProviderName = 'Service Control Manager'
StartTime = (Get-Date).AddHours(-2)
Id = @(7000,7009,7011,7031,7034,7038,7041)
}
Get-WinEvent -FilterHashtable $events -ErrorAction Stop | Select-Object TimeCreated,Id,Message
# No matches is not an incident; Get-WinEvent reports that case explicitly.
# Where auditing is enabled and access is authorized, correlate Security 4625
# with account, host, logon type and failure status, not Event ID alone.
| Наблюдение | Следующий слой | Не делать первым |
|---|---|---|
| Test=False | AD, группа/контекст компьютера, KDS, DNS и репликация | Не добавлять все Domain Computers |
| Test=True, служба не входит | Точное StartName, service logon и deny, SCM-события | Не выдавать локального администратора |
| Running, SMB запрещён | Личность на FS01, share + NTFS, DNS и сеть | Не проверять только Проводником админа |
| Приложение работает, Kerberos нет | Имя клиента, SPN-владелец, протокол нового входа | Не регистрировать один SPN на обеих записях |
| Сбой после ротации | Сохранённые журналы, все узлы, KDS/версии по условиям | Не удалять KDS root keys наугад |
Код 1069 не является уникальным признаком gMSA. Microsoft различает проблемы logon right, контакта с доменом, конфигурации управляемой записи и другие причины. Событие 7041 указывает на проблему права входа; 7038 требует разбора конкретного текста и контекста. Диагностика запуска службы под gMSA.
Состояние Running при ошибке доступа к ресурсу отправляет нас дальше: какое имя реально использовал сервер ресурса, каким протоколом прошёл вход, какие share/NTFS/DB-права сработали? В нашей службе различаются свежая личность процесса и результат UNC. Они не обязаны быть одновременно зелёными.
Решение в лоб — добавить локального администратора, включить старый протокол или создать новый KDS key. Такие действия расширяют риск и уничтожают чистоту опыта. Правильное исправление соответствует слою: исправить DNS, обновить согласованным способом контекст хоста, скорректировать выигравшую GPO или выдать ровно недостающее право на ресурс. После этого повторить тот же тест, который раньше не проходил.
Уровень 4. Миграция и откат: старую запись не удаляем на первом успехе
После лаборатории возвращаемся к таблице потребителей. Для одного совместимого продуктивного сервиса фиксируем исходную личность, startup type, зависимости, права, SPN, делегирование и способ восстановления приложения. В согласованном хранилище должен существовать актуальный прежний пароль либо проверенный способ восстановить прежнюю схему. XML с именем svcReports не содержит её пароля и не является полноценным откатом.
- До окна. Подтверждаем поддержку производителя, список узлов-получателей, права ресурсов и новый независимый вход администратора. Старую личность и файлы настроек сохраняем без публикации секретов.
- Переключение. Останавливаем только выбранный компонент по поддержанной процедуре, меняем личность и необходимые согласованные настройки, запускаем его. Не используем Stop-Service -Force для цепочки неизвестных зависимостей.
- Приёмка. Новая личность процесса, свежая полезная операция, серверное подтверждение аутентификации и отрицательный контроль. Для фермы — каждый возможный узел, а не один удачный сервер.
- Возврат. При заранее заданном критерии неуспеха останавливаем новый экземпляр, возвращаем прежнюю поддержанную конфигурацию и актуальные прежние реквизиты через защищённый интерфейс, восстанавливаем изменённые SPN/ACL по журналу и повторяем бизнес-проверку.
- Выдержка. Наблюдаем полный необходимый цикл, включая перезапуск и плановую ротацию. Лишь потом выводим старую запись из эксплуатации по отдельному согласованному плану.
В лаборатории возврат проще: остановить только AFGmsaPilot и оставить её Manual/Stopped. Необязательно немедленно удалять службу, каталог, группу и запись AD. Для повторного опыта сохраняем статус и конфигурацию; удаление учебных объектов — отдельная точная операция после проверки, что ими больше никто не пользуется.
Важная граница отзыва: удаление компьютера из группы не стирает уже полученный секрет, не завершает процесс и не аннулирует все выданные Kerberos-билеты мгновенно. При компрометации нужны изоляция узла и процедура реагирования для всей затронутой личности. Отключить общую gMSA без анализа — значит потенциально остановить и здоровые сервисы.
В штатной эксплуатации мониторим изменения группы получателей, ACL gMSA и групп, новые назначения службы и неожиданные источники входа. Автоматическая смена пароля уменьшает ручную работу, но не отменяет управление жизненным циклом полномочий.
Что получилось и что спросить у своей инфраструктуры
В архиве статьи — сборщик инвентаризации, просмотр границ получения пароля, исходник учебной службы, установщик с -WhatIf, две инструкции и матрица испытаний. Сценарии AD-изменений из статьи не запускаются архивом автоматически. Секретов, дампа пароля и массового перевода существующих служб в нём нет.
Мы прошли от неизвестных потребителей svcReports до отдельной gMSA с ограниченными получателями, от service logon до проверки процесса и реального чтения ресурса, от SPN до плана возврата. Это гораздо полезнее операции «убрать пароль из блокнота», потому что теперь видны границы ответственности.
Ключевая мысль: gMSA убирает ручное обслуживание пароля, но безопасность определяется тем, кто может получить эту личность и что ей разрешено. Обе стороны нужно уметь объяснить и проверить.
Спасибо, что дочитали. Коллеги, поделитесь в комментариях:
- Какая скрытая зависимость обнаружилась у вас при отказе от обычной сервисной записи?
- Кто в вашей схеме может менять состав группы получателей и как проверяется это делегирование?
- Как вы подтверждаете работу после ротации, а не только первый успешный запуск?
Следующая полезная тема — разделить права обслуживания приложения и право подменить код службы: управляемый пароль не спасает, если исполняемый файл может изменить любой оператор.



