KsaiLab
06 Authorization

Модель администрирования capabilities

Модель администрирования capabilities

Статус документа

Этот документ фиксирует операционную модель, по которой команда администрирует доступы в KSAILab после перехода на Keycloak.

Его цель: снять двусмысленность между IAM, platform permissions и frontend visibility.

По состоянию на 2026-03-11 backend auth foundation уже landed, поэтому ниже описывается не только target-state, но и текущий runtime baseline.

По состоянию на 2026-04-28 backend MR ksailab-app-backend!30 добавил обязательную boundary-синхронизацию для учебных групп, access groups, question bank/test content scope и файлового доступа. Сводный публичный контракт находится в access-abac-boundaries.md.

Короткий ответ

Где хранится каталог возможностей

Каталог capabilities хранится как версионируемый продуктовый контракт в двух местах:

  • в backend как capability registry;
  • в документации как canonical human-readable catalog.

Новые capability keys не создаются из UI и не заводятся вручную в Keycloak.

Где capabilities администрируются

Повседневное администрирование выполняется в platform UI, а не в Keycloak:

  • админ собирает bundles;
  • назначает bundles пользователям и группам;
  • делает точечные allow/deny overrides;
  • проверяет итоговый effective access.

Где хранятся назначения прав

Рабочие назначения прав хранятся в БД платформы:

  • bundles;
  • assignments;
  • overrides;
  • связи между bundle и capability.

Для чего нужен Keycloak

Keycloak хранит IAM-контур:

  • identity;
  • credentials;
  • login/logout endpoints и SSO session lifecycle;
  • realm roles;
  • clients;
  • protocol mappers;
  • service account ksailab-admin-service.

Важно: ksailab-admin-service нужен backend для provisioning и lifecycle-операций. Это не human admin.

Keycloak не является основным хранилищем полного каталога platform permissions. Keycloak также не является владельцем canonical browser session bootstrap для KsaiLab UI: эту роль выполняет backend через /api/v1/auth/login, /api/v1/auth/callback и GET /api/v1/auth/me.

Принцип разделения ответственности

ЗонаГде живётКто администрируетКак часто меняется
Пользователь, пароль, login/logout endpointsKeycloakIAM/platform adminПо жизненному циклу аккаунтов
Базовые realm roles admin, teacher, studentKeycloakIAM/platform adminРедко
Capability definitionsBackend + docsРазработчикиПри добавлении новых фич
Permission bundlesПлатформа БДPlatform adminЧасто
Assignments user/group -> bundleПлатформа БДPlatform adminЧасто
Allow/deny overridesПлатформа БДPlatform adminТочечно
Resource-scoped ABACBackendРазработчикиПри развитии доменной модели

Runtime baseline после backend MR !10

Сейчас в runtime уже подтверждено:

  • canonical browser login entry идёт через GET /api/v1/auth/login;
  • canonical browser callback идёт через GET /api/v1/auth/callback;
  • canonical browser logout идёт через POST /api/v1/auth/logout;
  • frontend bootstrap идёт через GET /api/v1/auth/me;
  • backend уже разрешает runtime access через access_assignment_mode, а итоговый access context считает по модели principal + access snapshot + bundles + overrides;
  • local POST /api/v1/auth/login и POST /api/v1/auth/refresh больше не являются рабочим auth flow и возвращают 410 Gone;
  • onboarding orchestration вынесен в отдельный POST /api/v1/onboarding/* контур;
  • минимальный capability baseline уже существует в коде;
  • отдельный access-group backend layer уже landed под /api/v1/access-groups, а unassigned bootstrap-only state уже считается валидным runtime-состоянием.

Это нужно читать как один непрерывный контракт:

  1. frontend инициирует browser login только через backend entrypoint;
  2. backend владеет code exchange и browser auth session;
  3. frontend потребляет только уже поднятый bootstrap-контекст через /auth/me.

Следующая logout/session wave добавляет сюда ещё четыре жёстких правила:

  • browser cookie = opaque session key only;
  • auth session и access/refresh/id tokens = server-side only;
  • Redis = target storage для browser auth session;
  • local app logout и full platform logout = разные уровни одного контракта.

Текущие runtime capability keys

На сейчас backend уже отдаёт:

  • auth.bootstrap.read
  • onboarding.teacher.create
  • onboarding.group.create
  • onboarding.group.assign_teacher
  • onboarding.student.bulk_create
  • onboarding.credentials.consume

Текущие runtime bundle identifiers

Сейчас backend возвращает минимальную bootstrap summary через технические bundle identifiers:

  • role:admin
  • role:teacher
  • role:student

Это не отменяет более богатую целевую модель bundles вроде admin.base или admin.permissions, но важно не путать текущий runtime baseline с будущим административным UX. Эти identifiers также нужно читать как read-only runtime markers для immutable system roles, а не как признак того, что системные роли уже стали редактируемой сущностью.

Текущий runtime access snapshot

GET /api/v1/auth/me теперь публикует отдельный access block:

  • mode показывает, идёт ли runtime через legacy_role, access_group или unassigned;
  • state фиксирует текущее состояние выдачи прикладного доступа;
  • groups, assigned_roles и assigned_bundle_keys описывают runtime membership/access assignment отдельно от coarse realm role;
  • unassigned / no access считается штатным bootstrap-only состоянием identity до выдачи доступа.

Read-only permissions source of truth wave

Первая delivery-wave для permission administration deliberately вводит отдельный backend read layer без CRUD:

  • GET /api/v1/permissions/catalog;
  • GET /api/v1/permissions/role-mappings;
  • GET /api/v1/permissions/bundles.

Этот слой code-backed и нужен, чтобы frontend Permissions -> Roles и Permissions -> Groups перестали читать docs или локальные массивы как runtime source of truth.

Важно:

  • /auth/me остаётся bootstrap endpoint только для current principal;
  • новый /api/v1/permissions/* слой не является generic /capabilities bootstrap endpoint;
  • role mappings и starter bundles в этой волне ещё не требуют отдельной DB-модели;
  • assignments, overrides и write-оркестрация остаются follow-up треком;
  • permissions/groups не является write-экраном для /api/v1/access-groups.

Current runtime boundaries for this wave

Прежде чем обсуждать целевую административную модель, текущий runtime нужно читать так:

  • coarse system roles admin, teacher, student остаются code-backed и immutable для текущего и ближайшего write-flow;
  • editable custom roles считаются отдельной будущей сущностью и не должны документироваться как уже landed CRUD по системным ролям;
  • Permissions -> Groups в этой wave остаётся snapshot/read-only surface и не подменяет ни рабочий контур Education -> Groups, ни отдельный access-group CRUD под /api/v1/access-groups;
  • Education -> Groups нужно описывать только как учебные группы/roster/onboarding на /api/v1/groups/management*, а не как permission-side access assignment;
  • permissions/groups не может быть fallback CRUD для access groups: это read-only registry/snapshot, который показывает starter bundles и composition;
  • identity может существовать без прикладного доступа, а unassigned / no access нужно читать как валидное состояние до membership assignment;
  • учебный контент, question bank, tests и files теперь требуют resource-scope checks поверх role/capability: отсутствие active learning/access scope должно приводить к 403;
  • direct bundles/grants в этой wave допустимо описывать только внутри явного single-role scope, без обещания multi-role/custom-role orchestration;
  • lifecycle для Keycloak-managed human accounts сейчас должен описываться как disable/offboarding, а не как обычный delete;
  • tenant-aware правило для organization_id уже публикуется backend как явный runtime contract: b2c -> optional, b2b/b2g -> required.

Целевая административная модель

Ниже описан target state для permission-side write model. Он не должен читаться как уже landed CRUD текущего backend read-only слоя.

1. Capability Definition

Capability definition — это стабильный ключ вида domain.resource.action.

Примеры:

  • education.courses.read
  • education.courses.manage
  • permissions.users.manage
  • monitoring.logs.read

Definition включает:

  • ключ;
  • display name;
  • описание;
  • тип (global или resource-aware);
  • backend enforcement note;
  • frontend usage note;
  • suggested starter bundles.

Capability definition добавляется только через релиз и одновременно фиксируется:

  • в backend registry;
  • в permission-catalog.md;
  • во frontend permission matrix, если capability влияет на экран или действие.

2. Permission Bundle

Bundle — это рабочий административный набор capabilities.

Именно bundles должны быть основной единицей повседневного администрирования, потому что они:

  • быстрее поддерживаются, чем ручные назначения по одному permission;
  • уменьшают хаос;
  • позволяют быстро включать новую фичу без правок Keycloak.

Рекомендуемый продуктовый стартовый набор bundles:

  • admin.base
  • admin.permissions
  • admin.monitoring
  • teacher.base
  • student.base

Эти bundle names нужно трактовать как target administrative design для платформенного UI.

3. Assignment

Assignment связывает subject с bundle.

Subject может быть:

  • пользователем;
  • access group;
  • в будущем организационным контекстом или workspace.

Основной путь выдачи прав:

  1. realm role даёт базовую классификацию пользователя;
  2. platform admin назначает membership в access group или другой явный access container;
  3. access container добавляет bundles, direct grants и explicit role context только в рамках разрешённого single-role scope;
  4. backend рассчитывает effective capabilities;
  5. frontend получает готовый capability set через GET /api/v1/auth/me.

Важно: этот шаг 5 не означает, что frontend владеет permissions. Он только потребляет backend-calculated result.

4. Overrides

Overrides нужны как исключение, а не как основная модель.

Разрешены два вида:

  • allow override
  • deny override

Приоритет:

  1. deny override
  2. allow override
  3. bundle grants
  4. starter mapping from realm role

Это правило нужно считать canonical precedence для всей платформы.

Как это администрируется в platform UI

Раздел Permissions

Целевой административный UX должен опираться на уже существующие frontend-разделы:

  • Permissions -> Users
  • Permissions -> Groups
  • Permissions -> Roles

Permissions -> Users

Экран должен покрывать:

  • просмотр пользователя;
  • его realm role;
  • список назначенных bundles;
  • effective capabilities;
  • точечные allow/deny overrides;
  • создание teacher accounts;
  • запуск one-time credential handoff;
  • lifecycle actions через отдельный backend credential/admin contract.

Текущий frontend runtime уже пришёл к этой модели частично:

  • teacher account provisioning идёт через onboarding contract;
  • временный пароль раскрывается только через one-time credential receipt flow;
  • каталог пользователей и safe lifecycle actions читаются из backend directory API;
  • обычный delete Keycloak-managed human accounts intentionally unavailable; текущий операторский путь — disable/offboarding;
  • follow-up password changes больше не живут в admin UI и уходят в self-service Settings -> Security flow;
  • assignments, effective capabilities, allow/deny overrides и admin credential lifecycle ещё ждут отдельный backend permission contract.

Settings -> Security

Self-service безопасность не должна смешиваться с административным CRUD раздела Permissions.

Текущий frontend contract здесь такой:

  • Settings -> Security открывает только backend-owned account security entrypoint GET /api/v1/auth/account/security;
  • frontend не вызывает legacy /users/password/* endpoints как рабочую модель;
  • delete-account и admin reset-password остаются явно unavailable, пока backend не отдаст отдельный lifecycle contract.

Permissions -> Groups

В текущей wave этот экран нужно трактовать как snapshot/read-only surface permission-side metadata, а не как скрытый CRUD второй модели групп.

Он должен покрывать:

  • просмотр starter bundle catalog и capability composition;
  • просмотр starter role mappings и boundary notes для текущей permission model;
  • честное указание, что runtime access-group lifecycle уже живёт отдельно под /api/v1/access-groups;
  • переход в Education -> Groups, если нужно управлять учебными группами и roster lifecycle.

Текущий frontend runtime здесь сознательно ограничен:

  • экран больше не использует моковые группы, fake counters или локальный CRUD;
  • собственный bundle catalog уже приходит из backend read layer;
  • создание учебных групп и roster actions остаются в Education -> Groups;
  • отдельный access-group CRUD уже живёт под /api/v1/access-groups, но dedicated permission-side write UI для него остаётся follow-up треком;
  • если следующая wave добавит write-сущность в permission-side model, она должна быть названа явно как access assignment, bundle assignment, grants group или access profile, а не как ещё одна безымянная group.

Permissions -> Roles

Этот экран в текущей wave нужно читать в двух слоях:

  • immutable system roles admin, teacher, student как runtime/read-only baseline;
  • отдельный future custom-role layer как follow-up write model.

Он должен покрывать:

  • аудит code-backed system role mappings и их starter bundles;
  • просмотр базового набора capabilities по role family;
  • аудит, какие capabilities приходят из role starter mapping, а какие из bundle/override;
  • честный shell для будущих custom roles без обещания CRUD по системным ролям.

Текущий frontend runtime здесь тоже уже подготовлен к следующей волне:

  • список, detail route и create route уже разведены как отдельные экраны;
  • список показывает runtime baseline role:admin, role:teacher, role:student и документированные starter bundles / scope groups;
  • backend read-model теперь поставляет этот snapshot через GET /api/v1/permissions/role-mappings, GET /api/v1/permissions/bundles и GET /api/v1/permissions/catalog;
  • create и edit intentionally не притворяются рабочим CRUD и не являются editor-ом системных ролей, пока backend role mapping orchestration и отдельный custom-role write contract ещё не отдали явный контракт.

При этом onboarding первой версии раскладывается так:

  • Permissions -> Users — teacher accounts;
  • Education -> Groups — создание groups;
  • Education -> Groups -> roster — bulk onboarding студентов.

Это важно, потому что student provisioning в KSAILab привязан к group membership, а не к абстрактной пользовательской карточке.

Tenant-aware organization policy

organization_id нельзя трактовать как локальную догадку фронта или как вечно optional поле по умолчанию.

Для этой wave canonical формулировка такая:

  • в b2c сценариях organization_id может отсутствовать;
  • в b2b и b2g сценариях organization_id должен быть required;
  • backend остаётся владельцем tenant mode и requiredness rule для create/update flows;
  • backend уже публикует tenant policy через contracts block в /api/v1/permissions/*, а frontend/docs должны читать именно этот contract вместо локальных догадок.

Как быстро добавлять новую capability

Целевой рабочий сценарий должен занимать один релиз и несколько минут админской настройки после него.

Добавление новой фичи

  1. Разработчик добавляет capability key в backend registry.
  2. Разработчик обновляет permission-catalog.md.
  3. Разработчик добавляет backend policy check.
  4. Если capability влияет на UI, обновляется frontend-permission-matrix.md.
  5. После релиза и после landing permission-side write model platform admin обновляет bundle/access-group composition в явном access admin surface.
  6. Пользователи получают новый доступ через существующие assignments или access-group membership.

Это и есть модель релиз + UI-настройка.

Удаление capability

Безопасный порядок:

  1. отметить capability как deprecated в каталоге;
  2. убрать её из bundles;
  3. удалить обращения из frontend;
  4. удалить backend policy usage;
  5. только после этого удалять definition.

Что меняется без релиза, а что нет

Без релиза можно

Ниже описан target operating mode после того, как permission-side write surface будет явно landed. Это не нужно читать как уже существующий CRUD текущего read-only runtime.

  • создать новый bundle;
  • выдать или снять bundle;
  • сделать точечный override;
  • поменять composition существующих bundles;
  • изменить access-group membership или другое явное access assignment для уже существующего access container;
  • отключить или включить пользователя через platform UI.

Для первой версии отдельно фиксируется:

  • manual onboarding выполняет только admin;
  • учётки teacher и student не создаются преподавателями;
  • временный пароль выдаётся через credential_receipt и одноразовый consume flow;
  • обычное добавление новой platform feature не требует правок в Keycloak.
  • starter mappings для immutable system roles остаются code-backed и не должны трактоваться как admin-editable без релиза.

Требует релиза

  • новый capability key;
  • новая backend policy;
  • новая resource-scoped ABAC логика;
  • новая frontend feature, если у неё ещё нет capability contract;
  • изменения в auth flow;
  • изменения клиентов, realm roles или mapper contract в Keycloak.

Как быстро настраивать Keycloak

Основной принцип

Keycloak нужно настраивать как Realm as Code, а не вручную через GUI как единственный способ работы.

Это означает:

  • realm/bootstrap/provisioning config хранится в keycloak-scripts-ksailab;
  • clients, roles и mappers заводятся через bootstrap/import;
  • ручные правки в UI допустимы только для диагностики и экстренного администрирования.

Отдельные repo boundaries для Keycloak-контура:

  • ci-pipelines/pipelines-ksailab — source of truth для pipeline logic;
  • infra-keycloak-ksailab — физический deployment Keycloak;
  • keycloak-theme-ksailab — login theme и branding.

Поддерживаемый scripting path для scripts repo — только Python. PowerShell fallback больше не считается supported operational path.

Что обязательно должно быть описано as code

  • realm;
  • clients ksailab-frontend, ksailab-backend, ksailab-admin-service;
  • redirect URIs;
  • logout URIs и post-logout redirect contract;
  • realm roles admin, teacher, student;
  • protocol mappers для базовых claims и roles;
  • service account permissions для backend orchestration.

При этом ownership clients нужно описывать явно:

  • ksailab-frontend может оставаться transition browser-login client, если runtime ещё опирается на него под backend initiation;
  • ksailab-backend не нужно путать с canonical browser-login client, пока он остаётся backend-facing audience/bearer contract;
  • target architecture предпочитает отдельный backend browser-auth client, чтобы убрать двусмысленность ownership.

Временная политика работы через Keycloak GUI

До полной стабилизации realm as code допускается использовать Keycloak GUI, но только как вспомогательный инструмент:

  • для первичного исследования настроек realm/client;
  • для smoke-валидации после деплоя;
  • для аварийного unblock, если нужно быстро подтвердить гипотезу.

При этом действуют жёсткие правила:

  1. Ручная GUI-правка не становится канонической сама по себе.
  2. Любое рабочее изменение должно быть перенесено в keycloak-scripts-ksailab в том же delivery cycle.
  3. Если GUI-изменение влияет на backend/frontend contract, это отдельно фиксируется в docs и связанных issue.
  4. Долгоживущая конфигурация, существующая только в GUI, считается техническим долгом.

Отдельная boundary по human admin bootstrap:

  • первый human admin platform-admin создаётся через отдельный operator flow в Keycloak;
  • backend runtime может только проверять presence и синхронизировать профиль при login;
  • ksailab-admin-service не должен описываться как human admin surrogate.

Когда Keycloak реально нужно менять

Правки в Keycloak нужны, если меняется:

  • login flow;
  • client configuration;
  • redirect URI policy;
  • post-logout redirect policy;
  • realm roles;
  • federation;
  • SSO/integration topology;
  • claims или mapper contract.

Для обычного добавления новой platform feature Keycloak менять не нужно. Для обычного изменения bundles, assignments, overrides или capability rollout Keycloak также не должен становиться точкой ежедневного администрирования.

Что должен рассчитывать backend

Backend должен возвращать в GET /api/v1/auth/me уже готовый effective access context:

  • principal;
  • classification;
  • realm_roles;
  • access;
  • effective_capabilities;
  • bundles.

Backend остаётся единственным источником истины по effective permissions, потому что только он видит:

  • realm roles из IAM;
  • access-group membership и текущий access_assignment_mode;
  • bundles и assignments из БД платформы;
  • overrides;
  • ABAC-контекст ресурса.

Отдельно:

  • /auth/me не является прямым чтением Keycloak token payload для UI;
  • /auth/me не заменяет backend session ownership;
  • /auth/me не отменяет обязательную backend ABAC-проверку реального действия.

Критерии правильной модели

Модель считается операционно удачной, если:

  • Keycloak не превращается в хранилище сотен platform permissions;
  • новые capability keys появляются только через контролируемый релиз;
  • админ может за минуты выдать новую фичу через bundle composition;
  • frontend строит меню и экраны по GET /api/v1/auth/me;
  • backend централизованно проверяет доступ и не полагается на UI.

Cross-repo references and explicit follow-up

Repo-local backend truth для текущей волны:

Frontend runtime constraints для этой wave:

Явные follow-up треки, которые нельзя описывать как уже landed:

  • полноценный custom-role CRUD и metadata contract;
  • отдельная permission-side write model для access assignments / bundle assignments;
  • dedicated frontend/admin orchestration поверх уже landed /api/v1/access-groups;
  • dedicated /courses API и UI-contract, который заменит текущую topic-based course-like модель;
  • расширение текущего published tenant contract на будущие permission-side write surfaces.

Связанные документы