Зомби-доступы: почему уволенный сотрудник всё ещё читает ваш GitLab
Вы нажали «Disable Account» в Active Directory, но бывший коллега всё так же пуллит прод-репозитории, листает Grafana и видит переписку. Разбираем, почему так происходит и как прекратить этот цирк.
Пятница, 18:00: иллюзия победы
Картина, знакомая каждому сисадмину. Из кадров падает срочная задача: ведущий разработчик или тимлид покинул компанию, нужно отозвать все права.
Вы открываете оснастку AD, находите учётку, жмёте правой кнопкой — Disable Account. На иконке загорается заветная чёрная стрелочка вниз. Закрываете тикет, захлопываете ноутбук и идёте на заслуженное пиво.
А теперь реальность.
В 22:00 бывший коллега садится на диван с домашним макбуком:
- Вкладка GitLab в Chrome прекрасно обновляется. Токен сессии живой, закрытый репозиторий с исходниками и боевыми переменными окружения отдаётся без лишних вопросов.
- Grafana послушно рендерит графики нагрузки и боевые дашборды.
- В корпоративном мессенджере бодро сыплются уведомления из закрытых каналов.
- А в консоли без единой ошибки отрабатывает
git pullчерез сохранённый персональный токен (PAT).
Вы заблокировали пароль в каталоге. Но вы не закрыли доступ.
Добро пожаловать в клуб любителей «зомби-доступов» — состояния, в котором компания уверена, что человек отключён, а человек продолжает ходить в прод как к себе домой.

---
Куда на самом деле уходят доступы: 4 черных дыры
Почему так происходит? Потому что между вашей Active Directory и прикладным софтом лежит архитектурная пропасть. Большинство систем не ходят в AD на каждый чих пользователя (иначе контроллер домена просто задохнулся бы от нагрузки).
Вот 4 сценария, из-за которых уволенные сотрудники годами висят в корпоративных сервисах:
1. Бессмертные куки в браузере
Когда сервис подключен к AD через обычный LDAP-bind, он проверяет логин и пароль ровно один раз — в момент ввода на форме логина.
Дальше сервис выписывает браузеру сессионную куку, кладёт её в Redis или базу и забывает про Active Directory. В GitLab, Nextcloud, Grafana и куче других сервисов стандартное время жизни сессии — от 7 до 30 дней. А если включена галочка «Запомнить меня» — сессия может жить месяцами.
Серверу абсолютно всё равно, что вы сделали с пользователем в AD. Пока жива кука, пользователь для него легитимен.
2. Токены, о которых забыли все (Personal Access Tokens)
Разработчики не вводят пароль при каждом коммите. Они один раз идут в настройки профиля и выпускают PAT (Personal Access Token) со сроком жизни «бессрочно» или «на год».
Этот токен сохраняется локально в базе самого GitLab. Он не имеет никакого отношения к доменному паролю. Даже если сотрудник сменил пароль или был заблокирован в домене, его токен с правами api и write_repository продолжит пушить и стягивать код.
3. Синхронизация по крону: «подождите до утра»
Если система не умеет аутентифицировать пользователей на лету, админы вешают синхронизацию пользователей по расписанию:
# Типичный костыль в crontab
0 */6 * * * /opt/scripts/ldap_sync_users.py
Синхронизация раз в 6 часов? Отлично, у обиженного сотрудника есть ровно 359 минут, чтобы спокойно сдампить клиентскую базу или исходники.
4. Зависший VPN-туннель
С OpenVPN и WireGuard история ещё веселее. Проверка учётки происходит при рукопожатии (handshake). После того как сетевой туннель поднят, сокет открыт и пакеты бегают.
Блокировка в AD не рвёт установленную TCP/UDP-сессию. Туннель будет жить до тех пор, пока не сработает reneg-sec (если он вообще настроен), пока не перезагрузят шлюз или пока админ вручную не сбросит конкретное подключение.
---
Почему ручной чек-лист увольнения — это самообман
В 9 из 10 компаний процедура офбординга выглядит как унылый список в Confluence:
- [ ] Заблокировать в AD
- [ ] Заблокировать в 1С
- [ ] Снести в GitLab
- [ ] Забрать доступы в Grafana
- [ ] Выкинуть из корпоративного чата
- [ ] Отозвать ключ VPN
- [ ] Сменить пароли на общих ящиках
В идеальном мире сисадмин методично проходит по всем пунктам. В реальном мире:
- Тикет упал в 17:55 перед праздниками;
- В половине сервисов отдельные админки, куда надо заходить под суперадмином;
- Часть сервисов подняли сами отделы (Shadow IT), и сисадмин вообще не в курсе, что у маркетологов крутится своя вики;
- Человеческий фактор: на 5-й системе отвлекли звонком про упавший коммутатор — и до GitLab руки дошли только через неделю.
Каждый ручной шаг в чеклисте — это гарантированная дыра в безопасности.
---
Архитектурная правда: прямой LDAP — это прошлый век
Исторически все привыкли прописывать LDAP-настройки прямо в конфиг каждого сервиса:
# gitlab.rb
gitlab_rails['ldap_enabled'] = true
gitlab_rails['ldap_servers'] = {
'main' => {
'host' => 'dc1.company.local',
'bind_dn' => 'CN=ldap_bind,OU=Service,DC=company,DC=local',
'password' => 'SuperSecret123'
}
}
У такого подхода три фатальных недостатка:
1. Сервисы видят пароли сотрудников в открытом виде. Пользователь вбивает свой доменный пароль прямо в веб-форму GitLab или Grafana. Если в сервисе есть уязвимость или включены debug-логи — корпоративные пароли утекают на диск.
2. Нет контроля сессий. Сервис забирает пароль, проверяет bind и дальше живёт своей жизнью.
3. MFA превращается в ад. Чтобы прикрутить второй фактор, вам нужно настраивать его в каждом сервисе отдельно. Где-то есть TOTP из коробки, где-то только в платной Enterprise-подписке, а где-то нет вообще.
---
Как это чинится по-человечески: OIDC и единая точка отзыва
Правильная архитектура строится на простом принципе: сервисы не должны знать пароли пользователей и не должны сами решать, когда заканчивается сессия.
Вместо россыпи LDAP-подключений ставится единый шлюз доступа (Identity Provider):

[ Active Directory ]
(хранит юзеров и хеши)
│
LDAP / Kerberos
│
▼
[ Шлюз OwnGate ] ◀─── Единый вход (SSO), MFA и аудит
│
OpenID Connect (OIDC)
┌───────────┼───────────┐
▼ ▼ ▼
[ GitLab ] [ Grafana ] [ Nextcloud ]
Что меняется технически:
1. Пароль вводится только один раз в шлюзе
Пользователь никогда не отдаёт доменный пароль приложениям. Шлюз проверяет пароль в AD, требует второй фактор (TOTP) и выдаёт приложению криптографически подписанный токен.
2. Короткоживущие Access-токены
Токен доступа живёт 5–15 минут. Чтобы продлить работу, приложение в фоне обращается к шлюзу с Refresh-токеном. Шлюз смотрит в AD: если учётка заблокирована — Refresh отклоняется, и пользователя мгновенно выкидывает на страницу входа.
3. Backchannel Logout — смерть сессиям за секунду
Для критичных сервисов работает протокол Backchannel Logout. В момент, когда в шлюзе отзывается сессия, шлюз рассылает прямые серверные запросы во все подключенные приложения:
POST /oauth/backchannel-logout
Content-Type: application/x-www-form-urlencoded
logout_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Приложения уничтожают локальные сессионные куки в ту же секунду. Никаких «подождём 30 дней, пока кука протухнет».
---
С чего начать, если у вас 50–200 человек
Не нужно затевать годовой проект по внедрению монструозного Enterprise IAM с интеграторами и бюджетом на космолет.
Нормальный эволюционный путь занимает пару дней:
1. Не трогайте Active Directory. AD остаётся главным источником правды для пользователей и групп.
2. Поставьте шлюз доступа внутри контура. Локально, без отправки паролей в сторонние облака.
3. Подключите 2 самых чувствительных сервиса. Например, GitLab и Grafana — они поддерживают OpenID Connect из коробки за 15 минут настройки.
4. Включите единый MFA. Один корпоративный TOTP вместо зоопарка приложений на смартфонах.
5. Проверьте увольнение. Заблокируйте тестовую учётку в AD и убедитесь, что доступ во все системы гаснет сразу, а не через неделю.
Когда за отзыв доступов отвечает архитектура, а не память уставшего сисадмина — компания перестаёт кормить армию «зомби-доступов».