Единый вход
Единый вход позволяет внешнему провайдеру идентификации (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,
одноразовое и недолговечное.
Что проверяет вход
Заголовок раздела «Что проверяет вход»Внутри проверенного нативного модуля и сервиса, по порядку:
- подпись утверждения (обязательна всегда; подпись конверта ответа — опционально, зависит от конфигурации) — структурно устойчива к обёртыванию подписи, поскольку проверенная полезная нагрузка привязана к проверяемому элементу,
- эмитент совпадает с настроенным IdP,
- аудитория называет этого поставщика услуг,
- назначение — это наш URL ACS,
InResponseToсовпадает с выпущенным нами ожидающим AuthnRequest (незапрошенные ответы, инициированные IdP, по умолчанию отключены),- окна времени жизни укладываются в расхождение часов (по умолчанию 90 с) и
- идентификатор утверждения раньше не встречался (кэш повторов).
Ошибки возвращаются типизированными кодами — подпись, истёкший срок, аудитория, назначение, эмитент, несовпадение запроса, — а эндпоинты отвечают обобщёнными ошибками, поэтому атакующий не узнаёт, почему подделка не прошла.
Аккаунты и закрепление субъекта
Заголовок раздела «Аккаунты и закрепление субъекта»Первый вход через SSO закрепляет за аккаунтом неизменяемый субъект
провайдера (issuer плюс SAML NameID / OIDC sub или SCIM externalId).
С этого момента вход идентифицирует субъект, а не адрес электронной почты.
Если кто-то другой позже предъявит тот же адрес электронной почты от того же
или другого IdP, закреплённый аккаунт его отклонит. Именно это не даёт
переиспользованному у провайдера адресу электронной почты превратиться в
захват аккаунта.
Провизионинг и депровизионинг
Заголовок раздела «Провизионинг и депровизионинг»Провизионинг JIT происходит при входе (SsoProvisioner). SCIM — отдельная
поверхность push-обновлений (ScimService), которая закрепляет тот же
субъект в строке пользователя; членство в рабочих пространствах она не
выдаёт. Депровизионинг — SCIM active: false или DELETE — отказывает, если
аккаунт является владельцем рабочего пространства (сначала передайте
владение). В остальных случаях он сначала отзывает все учётные данные
устройства (сеансы обрываются за считаные секунды благодаря отслеживанию
устройств), убирает все членства в рабочих пространствах и помечает аккаунт
деактивированным. Строка пользователя остаётся: атрибуция сохраняется
навсегда. Повторная активация возвращает вход, но членство заново не выдаёт.
Что остаётся за рамками
Заголовок раздела «Что остаётся за рамками»- Сопоставление группа→роль происходит при входе — по атрибутам SAML / утверждениям OIDC; группы, переданные через SCIM, не сохраняются.
- Роль владельца из SSO недостижима — ни как роль по умолчанию, ни через сопоставление групп.
- Реле WebSocket никогда не является каналом SSO; телефоны, которые дотягиваются до сервера только через реле, не могут выполнить против него обмен с браузером.