Перейти к содержимому

Единый вход

Единый вход позволяет внешнему провайдеру идентификации (IdP) решать, кому можно входить на ваш сервер Control Center. Поддерживаются два протокола — SAML 2.0 и OpenID Connect — и оба приводят в одну и ту же точку: только что выпущенные учётные данные устройства, которые дальше уже неотличимы от приглашения или сопряжения по QR-коду.

Входы SAML идут через браузер. IdP никогда не открывает соединение с вашим сервером — всё несёт браузер пользователя:

browser ──> /saml/login ──> IdP ──POST SAMLResponse──> /saml/acs
│ verify
▼
provision user (maybe)
│
▼
browser handoff: device PSK in the URL
fragment (never a query parameter)

Поскольку курьер здесь — браузер, сервер за NAT прекрасно подходит для входов: localhost, локальная сеть, Tailscale, VPN или туннель одинаково работают; только браузер пользователя должен дотягиваться до URL ACS. Реле WebSocket, через которое работает удалённое управление с телефона, не может протащить HTTP-обмен туда и обратно, поэтому точки входа SSO всегда нацелены на HTTP-origin сервера.

OpenID Connect устроен так же, но с обменом по коду авторизации + PKCE; утверждения читаются с конечной точки токена эмитента по TLS, так что браузер вообще не передаёт данные идентичности.

Control Center намеренно разделяет ответственность:

Компонент Где За что отвечает
Нативный cc_saml packages/cc_natives/native/saml/ проверка XML-DSig, канонизация, валидация профиля SAML — делегированы закреплённому крейту saml на чистом Rust за C ABI без состояния
SamlService / OidcService cc_server_core состояние входа (трекеры ожидающих запросов, дедупликация повторов), параметры политики валидации, общий вызов провизионинга
SsoProvisioner cc_server_core найти или создать пользователя, закрепить субъект провайдера, выдать членство по политике автоматического членства
ScimService cc_server_core поверхность push-обновлений SCIM 2.0 (жизненный цикл провизионинга и депровизионинга)
SsoSettingsService cc_server_core сохранённые строки подключений, повторное применение на лету, токен SCIM

Резервной реализации криптографии SAML на чистом Dart намеренно не существует: написанные вручную XML-DSig и канонизация — как раз то место, где живут уязвимости обёртывания подписи, поэтому при отсутствии библиотеки cc_saml отказывают и вход SAML, и загрузка сервера. Нативный модуль по замыслу не хранит состояние — межзапросное состояние живёт в сервисах Dart, одноразовое и недолговечное.

Внутри проверенного нативного модуля и сервиса, по порядку:

  1. подпись утверждения (обязательна всегда; подпись конверта ответа — опционально, зависит от конфигурации) — структурно устойчива к обёртыванию подписи, поскольку проверенная полезная нагрузка привязана к проверяемому элементу,
  2. эмитент совпадает с настроенным IdP,
  3. аудитория называет этого поставщика услуг,
  4. назначение — это наш URL ACS,
  5. InResponseTo совпадает с выпущенным нами ожидающим AuthnRequest (незапрошенные ответы, инициированные IdP, по умолчанию отключены),
  6. окна времени жизни укладываются в расхождение часов (по умолчанию 90 с) и
  7. идентификатор утверждения раньше не встречался (кэш повторов).

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

Первый вход через SSO закрепляет за аккаунтом неизменяемый субъект провайдера (issuer плюс SAML NameID / OIDC sub или SCIM externalId). С этого момента вход идентифицирует субъект, а не адрес электронной почты. Если кто-то другой позже предъявит тот же адрес электронной почты от того же или другого IdP, закреплённый аккаунт его отклонит. Именно это не даёт переиспользованному у провайдера адресу электронной почты превратиться в захват аккаунта.

Провизионинг JIT происходит при входе (SsoProvisioner). SCIM — отдельная поверхность push-обновлений (ScimService), которая закрепляет тот же субъект в строке пользователя; членство в рабочих пространствах она не выдаёт. Депровизионинг — SCIM active: false или DELETE — отказывает, если аккаунт является владельцем рабочего пространства (сначала передайте владение). В остальных случаях он сначала отзывает все учётные данные устройства (сеансы обрываются за считаные секунды благодаря отслеживанию устройств), убирает все членства в рабочих пространствах и помечает аккаунт деактивированным. Строка пользователя остаётся: атрибуция сохраняется навсегда. Повторная активация возвращает вход, но членство заново не выдаёт.

  • Сопоставление группа→роль происходит при входе — по атрибутам SAML / утверждениям OIDC; группы, переданные через SCIM, не сохраняются.
  • Роль владельца из SSO недостижима — ни как роль по умолчанию, ни через сопоставление групп.
  • Реле WebSocket никогда не является каналом SSO; телефоны, которые дотягиваются до сервера только через реле, не могут выполнить против него обмен с браузером.