OGOwnGate

Зомби-доступы: почему уволенный сотрудник всё ещё читает ваш 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):

Архитектура корпоративного шлюза доступа OwnGate


       [ 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 и убедитесь, что доступ во все системы гаснет сразу, а не через неделю.

Когда за отзыв доступов отвечает архитектура, а не память уставшего сисадмина — компания перестаёт кормить армию «зомби-доступов».

← Все статьи