OGOwnGate

Вложенные группы в AD: как не положить контроллер домена рекурсией

Магия LDAP_MATCHING_RULE_IN_CHAIN кажется спасением, пока двадцать сервисов не начинают опрашивать вложенные группы одновременно. Разбираем, почему рекурсивный поиск в Active Directory вешает CPU контроллера на 100% и как нормализовать группы через шлюз доступа.

Рекурсивный поиск групп в AD против плоских кэшированных claims

Утро понедельника, 09:15. Сотрудники приходят в офис, включают ноутбуки, открывают корпоративную почту, GitLab и внутренний трекер задач.

И в этот момент мониторинг начинает истошно выть: процессор на основном контроллере домена уходит в полку на 100%, процесс lsass.exe пожирает все доступные ресурсы, а время ответа на простейший LDAP-запрос подскакивает с 5 миллисекунд до 35 секунд. Сервисы падают по таймауту, сисадмины нервно пьют корвалол.

Знакомая картина? Если да, передайте привет главному архитектурному костылю энтерпрайза — глубоко вложенным группам безопасности (Nested Groups).

---

Как строят «удобные» группы и почему это катастрофа

В теории концепция вложенных групп в Active Directory выглядит логично и красиво:

  • Есть группа отдела: Dep-Backend;
  • В неё входит группа проекта: Project-Billing-Devs;
  • В неё входит группа роли: GitLab-Maintainers;
  • А в неё входит группа доступа к серверам: SSH-Prod-Access.

Админу кажется, что он гений автоматизации: добавил нового джуна в Project-Billing-Devs — и он автоматически получил права везде, где нужно!

Но вот приходит веб-приложение (например, корпоративный портал или мониторинг) и задаёт контроллеру домена простой вопрос: *«А состоит ли пользователь Иванов в группе GitLab-Maintainers?»*.

---

Знакомьтесь: магический OID 1.2.840.113556.1.4.1941

Обычный атрибут memberOf в Active Directory возвращает только те группы, в которые пользователь добавлен напрямую. Вложенные группы он не видит.

Чтобы приложение могло узнать обо всех косвенных правах, разработчики раскапывают в документации Microsoft специальное заклинание:

LDAP_MATCHING_RULE_IN_CHAIN с OID 1.2.840.113556.1.4.1941.

Поисковый LDAP-фильтр начинает выглядеть так:


(&(objectCategory=user)(memberOf:1.2.840.113556.1.4.1941:=CN=GitLab-Maintainers,OU=Groups,DC=company,DC=local))

Что делает контроллер домена, получая такой запрос?

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

А теперь умножьте это на реальность:

1. Глубина матрешки в 4–6 уровней.

2. Циклические ссылки. Сисадмин Вася три года назад случайно добавил группу Admins-Secondary в Admins-Primary, а стажёр Петя через год сделал наоборот. Контроллер домена вынужден на лету отслеживать петли графа, сжигая циклы CPU.

3. Отсутствие кэширования в сервисах. Древний Jenkins или самописная админка дёргает этот рекурсивный фильтр на каждый HTTP-запрос от пользователя.

Когда сто человек одновременно открывают по 5 вкладок в браузере, контроллер домена получает лавину из десятков тысяч рекурсивных вычислений в секунду. Финал предсказуем.

---

3 шага к наведению порядка

Шаг 1. Отказ от циклов и чистка «матрешек»

Проведите аудит домена простым PowerShell-скриптом на поиск групп с глубиной вложенности больше двух.

Если для понимания того, почему разработчик имеет доступ к базе данных, вам нужно развернуть цепочку из пяти групп — эту цепочку нужно принудительно расплющить. Старый добрый принцип Role-Based Access Control (RBAC) предполагает, что права привязываются к понятным функциональным ролям, а не к бесконечным генеалогическим древам.

Шаг 2. Изоляция контроллера от сырых запросов приложений

Сервисы компании (GitLab, Grafana, Nextcloud, Wiki) вообще не должны ходить в Active Directory напрямую и уж тем более исполнять тяжелые поисковые запросы по дереву объектов.

Шаг 3. Делегирование расчёта прав шлюзу OwnGate

В современной архитектуре между Active Directory и сервисами встаёт шлюз доступа (OwnGate):


[Active Directory]

       │

 (1 запрос при логине: вычисление членства)

       ▼

 [Шлюз OwnGate] ──► Кэширует плоский массив ролей в JWT

       │

 (Отдаёт готовые claims: ["devops", "billing-admin"])

       ▼

[Сервисы: GitLab / Grafana / Nextcloud]

Как это решает проблему нагрузки:

1. Расчёт один раз. Шлюз вычисляет полное членство пользователя в группах ровно один раз — в момент прохождения аутентификации.

2. Плоский JSON вместо рекурсивного дерева. Шлюз формирует компактный массив строк:

"groups": ["Billing-Devs", "GitLab-Maintainers", "All-Staff"].

3. Нулевая нагрузка при работе. Сервисы читают готовый массив из подписанного токена JWT за 0.001 миллисекунды. Ни один пакет не летит на контроллер домена при переходах по страницам и кликах в интерфейсе.

Перестаньте мучить Active Directory рекурсивными фильтрами из прошлого века. Перевод авторизации на единый шлюз доступа снимет нагрузку с контроллера домена и навсегда избавит от утренних зависаний инфраструктуры.

← Все статьи