temporality
Изменить страницу↗
Инженерные документы

Федеративная аутентификация и провижининг пользователей (Auth Federation)

Дата: 2026-10-16. Отвечает на блокер «500 пользователей»: список, структура, массовые операции, синхронизация с корпоративными каталогами и интеграция с корпоративной аутентификацией (Keycloak, AD / LDAP, IdP по SAML/OIDC).


#1. Цель

Подключить Temporality к корпоративной аутентификации одним интеграционным слоем навсегда: независимо от того, что стоит у заказчика — Keycloak, Active Directory, ADFS, Entra ID или зоопарк из нескольких IdP — на нашей стороне код не меняется.

Ключевой принцип документа: ядро говорит только на OIDC. Всё остальное — протоколы, маппинг атрибутов, ротацию сертификатов, quirks конкретных IdP — решает либо существующий IdP заказчика, либо identity-брокер (Authentik, Keycloak), подключённый рядом как конфигурация, а не как наш код.

Второй принцип: список пользователей — проекция IdP, а не мастер-копия. Пользователи появляются при первом входе (JIT-provisioning), роли и подразделения выводятся из клеймов по редактируемой таблице маппинга, исчезновение из каталога деактивирует учётную запись. Ручное заведение сотен записей исчезает как класс.


#2. Проблематика, которую решала фаза 1 (история)

До фазы 1: аутентификация была только локальной (env-токены, токены workspace-пользователей, POST /v1/auth/login name-or-email + bcrypt), SSO не было, провижининг — ручной, ListUsers без пагинации и массовых операций. Фаза 1 закрыла SSO/JIT (см. §4–§5); пагинация/фильтры и массовые операции остались на будущее (§8).


#3. Принцип: один протокол на нашей стороне

#3.1. Разделение проблем

Вопрос «интеграция с корпоративной аутентификацией» — это три разные проблемы с разными решениями:

Проблема Решение Где живёт
Аутентификация (кто ты) OIDC: Authorization Code + PKCE Один слой в ядре
Зоопарк протоколов (LDAP, SAML, несколько IdP) Identity-брокер перед нами Конфигурация стенда, не наш код
Провижининг (создание/жизненный цикл, 500 юзеров) JIT + reconcile Небольшой слой в ядре

#3.2. Топологии — один и тот же код

  1. Малая команда без IdP. Как сегодня: локальные пароли/токены, ноль конфигурации. SSO просто не настроен.
  2. Корпорация с OIDC-IdP (Keycloak, Entra ID, Okta, Google). Temporality — обычный OIDC-клиент этого IdP. Брокер не нужен.
  3. Корпорация с AD/LDAP или смешанной средой. Рядом ставится брокер (рекомендуем Authentik, см. §10), который федерит источники и предъявляет нам единый OIDC. Вся боль LDAP-схем, SAML-метаданных и сертификатов — в его админке.

#3.3. Чего сознательно нет в ядре


#4. Основные понятия


#5. Архитектура аутентификации

#5.1. Поток входа SPA (Authorization Code + PKCE)

SPA ──1. GET /v1/auth/oidc/start ──────────────► ядро
      ◄─ 2. 302 {issuer}/authorize?client_id&PKCE&state&nonce
SPA ──3. логин на IdP/брокере (AD/Keycloak/…)
IdP ──4. 302 redirect /v1/auth/oidc/callback?code&state ──► ядро
      ядро: 5. обмен code на токены (backchannel)
            6. валидация id_token (issuer, aud, nonce, подпись по JWKS)
            7. JIT + маппинг (§6) + аудит входа
      ◄─ 8. Set-Cookie: httpOnly-сессия; 302 в SPA
SPA ──9. дальше все запросы с cookie-сессией

Ядро — единственная сторона, знающая client secret; SPA секретов не видит. state/nonce одноразовые, живут в коротком окне в store (таблица auth_request или переиспользуемый кэш с TTL).

#5.2. Сессии

#5.3. Gate: четвёртый тип принципала

Gate.Authenticate сейчас различает env-токены, DB-токены и loopback. Добавляется проверка cookie-сессии: валидная сессия → Principal из workspace_user по тому же пути, что PrincipalForUser (workspace/tokens.go) — роли, проекты, орг-цепочка переиспользуются как есть. Порядок проверки: env/DB Bearer → loopback → сессия. JWT-валидация на каждый запрос не нужна: токен проверяется один раз при обмене code, далее живёт наша сессия.

Опционально (фаза 2+) — приём access-токенов IdP как Bearer (валидация по JWKS) для не-браузерных клиентов, которым выдаёт токены сам IdP. Для CLI и агентов по умолчанию остаются статические/DB токены: они уже есть и не требуют участия человека.

#5.4. JIT и клеймы

Минимальный контракт клеймов: sub (обязателен), preferred_username или email (имя для отображения), опционально groups / roles (массив или строка — нормализуем). Нестандартные имена клеймов настраиваются в конфиге IdP-подключения (§5.5).

#5.5. IdP конфигурация

Таблица auth_idp_config (фаза 1 — одна запись):

Редактирование — admin-only API + UI, изменение пишет аудит-событие.

#5.6. Break-glass

Локальный вход паролем не отключается, а ограничивается: при включённом SSO он остаётся доступен только пользователям с ролью admin. Это спасает при недоступности IdP. У admin-пользователя локальный пароль задаётся заранее. Пароль SSO-пользователя без локального он может установить сам (POST /v1/auth/password с пустым текущим) — принципал уже аутентифицирован IdP, повторной проверки нечему проводить.


#6. Провижининг и жизненный цикл

#6.1. Создание: JIT

Первый успешный вход:

  1. Ищем workspace_user по (external_issuer, external_id).
  2. Не нашли и jit_enabled → создаём: имя/email из клеймов, роль = default_role, sourced='federated', активен. Аудит-событие auth.user.jit_created.
  3. Нашли → обновляем имя/email из клеймов (руками их больше не правят), проверяем активность.

#6.2. Роли и подразделения: маппинг клеймов (фаза 2, не реализовано)

Таблица auth_claim_mapping (claim, value, install_role, org_unit_id): правило «если в клеймах есть значение — дать роль / привязать к подразделению». Пока реализованы только переименования клеймов (username_claim/email_claim/groups_claim).

#6.3. Деактивация: reconcile

Раз в час (настраиваемо) — фоновая задача:

  1. Пользователи sourced='federated' со last_login_at старше порога (по умолчанию 30 дней) перепроверяются у IdP (если есть userinfo/introspection или групповый клейм недоступен — помечаются кандидатами на деактивацию).
  2. Исчезнувший из каталога (по groups-клейму или явному ответу IdP) → active=false, аудит auth.user.deactivated, сессии гасятся.
  3. Удаление не выполняется никогда: на пользователей ссылаются раны, аудит, inbound-сообщения. Реактивация — повторным входом.

Практика: пока IdP не отдаёт групповое членство по API, reconcile ограничивается expire-политикой сессий и ручной деактивацией — это честно фиксируется в критериях фазы 2.

#6.4. Локальные поля

Каналы уведомлений, preferred channel, подписи в чатах остаются локальными полями workspace_user, ключ — external_id. Прямое редактирование федеративных полей (имя/email/роль) в UI скрывается.


#7. Модель данных

Таблицы auth_* живут в squashed baseline (migrations/workspace/ 0001_baseline_v2026.2.21); номера ниже — исторический план:

Secret для client_secret_ref — обычный Secret ресурс (docs/secrets-management.md §3): значение разрешается ядром на границе использования — та же механика, что у креденшелов провайдеров моделей (resolveProviderAPIKey).


#8. API

Все новые события пишутся в access audit (auth.login.succeeded, auth.login.failed, auth.user.jit_created, auth.user.deactivated, auth.config.updated, auth.logout).


#9. UI


#10. Брокеры: что рекомендовать

Когда Почему
Authentik Дефолт для стендов без готового IdP Лёгкий, простая админка, из коробки федерит LDAP/SAML/OIDC-источники с шедульным синком, MIT
Keycloak Корпоративный стандарт «только утверждённый софт» Часто уже развёрнут — тогда брокер не нужен вовсе; JVM/тяжелее, но админы его знают
Zitadel Go-стек, мультитенантность Технически хорош, в корпорациях менее заезжен

Мы не тащим брокера в свой compose как обязательность: дефолт — «подключите к вашему IdP», опция — «вот готовый Authentik рядом» (пример compose-профиля в docs, не в основном стеке).

#10.1. Dev-стенд с Authentik

docker-compose.authentik.yml в корне репозитория — опциональный спутник для приёмки SSO на дев-стенде: свой postgres/valkey, своя сеть, к стеку Temporality не привязан вовсе. Запуск:

docker compose -f docker-compose.authentik.yml up -d
# админка: http://localhost:9000 (akadmin; пароль — в логах server при
# первом старте: grep "Generated password")

Дальше в админке Authentik:

  1. Applications → Create → Web application: redirect URI <origin стенда>/kernel-api/v1/auth/oidc/callback.
  2. Скопировать client id/secret в Temporality: issuer http://localhost:9000/application/o/<slug>/, client secret — через secret reference (secret://authentik/client).
  3. Клеймы: Authentik из коробки отдаёт preferred_username и email — дефолты конфига Temporality менять не нужно.

Фаза 1 принимает стенд и с Keycloak: с точки зрения ядра это тот же OIDC-флоу, меняется только issuer.


#11. Безопасность и аудит


#12. Фазы

#Фаза 1 — OIDC-вход и JIT (реализовано, закрывает SSO-блокер)

  1. Миграции auth-таблиц (см. §7).
  2. coreos/go-oidc (+ golang-jwt): discovery, обмен code, валидация id_token, JWKS-кэш.
  3. Сессии + cookie в Gate.Authenticate; logout.
  4. JIT + default_role; auth/methods; экран входа SPA.
  5. Admin API конфига IdP; client secret через secret reference.
  6. Dev-стенд: Authentik в отдельном compose-профиле для приёмки.

#Фаза 2 — жизненный цикл и масштаб (закрывает «500 пользователей»)

  1. Пагинация/поиск/фильтры ListUsers + UI-таблица.
  2. Массовые операции (activate/deactivate/set_role).
  3. Маппинг клеймов → роли/подразделения + UI редактора.
  4. Reconcile-деактивация (порог last_login + группы, когда IdP даёт).
  5. CSV-импорт как переходный путь до включения SSO у заказчика.
  6. Локальные поля — только у локальных пользователей; у федеративных — только каналы.

#Фаза 3 — enterprise-хвосты (по требованию)

  1. Приём access-токенов IdP как Bearer на Gate (JWKS-валидация на запрос).
  2. SCIM 2.0 server (users/groups) — pre-provisioning без JIT.
  3. Несколько IdP одновременно (мультистрочность auth_idp_config).
  4. Нативный SAML-SP через crewjam/saml — только под конкретного заказчика без права поставить брокера.

#13. Критерии готовности

#Фаза 1

#Фаза 2

#Фаза 3


#14. Открытые вопросы


#15. Архитектурные ограничения