Umbryx — Контроль доступа¶
Обзор¶
Umbryx использует двухуровневую модель доступа:
- Глобальная роль (
User.role) — системные права - Проектная роль (
ProjectMember.role) — права внутри конкретного проекта
Глобальные роли¶
| Роль | Описание |
|---|---|
platform_admin |
Суперадмин: видит все проекты и обходит проектные проверки (каждый обход аудируется) |
user |
Обычный пользователь: видит только проекты, где состоит |
viewer |
Legacy read-only аккаунт: только GET-запросы на уровне middleware. Не то же, что проектная роль viewer — это глобальная системная роль. Избегайте для новых пользователей; предпочитайте user + проектное членство viewer. |
Проектные роли¶
Проектных ролей 11 (backend/app/core/enums.py::MemberRole).
Полный авторитетный источник — backend/app/core/permissions.py
(ROLE_PERMISSIONS); если эта страница и код расходятся — верьте коду
и обновляйте страницу.
| Роль | Описание |
|---|---|
owner |
Полный доступ, управление участниками, удаление проекта, передача владения |
admin |
Полный операционный доступ плюс управление участниками (кроме действий владельца) |
editor |
CRUD ко всем данным проекта без управления участниками |
manager |
Широкий операционный доступ (CRUD хостов/учётных данных/находок/заметок/loot, обновление сети, запуск сканов/плейбуков, чтение infra) без управления участниками и экспорта — облегчённый editor |
team_lead |
Как manager, но read-only по топологии сети; запускает сканы, создаёт и правит хосты/учётные данные/находки/заметки/loot |
operator |
Операционные действия: хосты, находки, активности, создание учётных данных и ограниченные удаления (хосты, loot) |
analyst |
С упором на чтение: полное чтение хостов/учётных данных/находок/заметок/loot/сети/отчётов плюс create/update только находок и заметок — без записи хостов/учётных данных |
reviewer |
Фокус на ревью находок: чтение находок/заметок/отчётов, findings.approve, reports.export, project.export, audit.read — без доступа к хостам/учётным данным/сети |
viewer |
Read-only на все данные проекта без секретов |
observer |
Как viewer, но у́же набор чтения (учётных данных нет вообще, даже в отредактированном виде) |
auditor |
Чтение отчётов, timeline, находок и audit.read; без секретов и правок |
Матрица ключевых прав¶
| Право | owner | admin | editor | manager | team_lead | operator | analyst | reviewer | viewer | observer | auditor |
|---|---|---|---|---|---|---|---|---|---|---|---|
| project.read | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| project.update | ✓ | ✓ | — | ✓ | — | — | — | — | — | — | — |
| project.delete | ✓ | — | — | — | — | — | — | — | — | — | — |
| project.manage_members | ✓ | ✓ | — | — | — | — | — | — | — | — | — |
| project.export | ✓ | ✓ | ✓ | — | — | — | — | ✓ | — | — | — |
| project.import | ✓ | ✓ | — | — | — | — | — | — | — | — | — |
| project.transfer_ownership | ✓ | — | — | — | — | — | — | — | — | — | — |
| hosts.read | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | ✓ | ✓ |
| hosts.create/update | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | — | — | — |
| hosts.delete | ✓ | ✓ | ✓ | — | — | ✓ | — | — | — | — | — |
| credentials.read | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ | — | — |
| credentials.read_secret | ✓ | ✓ | ✓ | — | — | ✓ | — | — | — | — | — |
| credentials.create/update | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | — | — | — |
| findings.read | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| findings.create/update | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | update only | — | — | — |
| findings.delete | ✓ | ✓ | ✓ | — | — | — | — | — | — | — | — |
| findings.approve | — | — | — | — | — | — | — | ✓ | — | — | — |
| network.manage_nodes/links | ✓ | ✓ | ✓ | — | — | — | — | — | — | — | — |
| network.update | ✓ | ✓ | ✓ | ✓ | — | — | — | — | — | — | — |
| topology.apply | ✓ | ✓ | ✓ | — | — | — | — | — | — | — | — |
| scans.run | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | — | — | — |
| playbooks.run | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | — | — | — | — |
| audit.read | — | — | — | — | — | — | — | ✓ | — | — | ✓ |
| reports.export | ✓ | ✓ | — | — | — | — | — | ✓ | — | — | ✓ |
Это сводка наиболее security-значимых прав, не полная матрица —
авторитетный источник ROLE_PERMISSIONS в
backend/app/core/permissions.py.
Запомните два различия: credentials.read_secret (только
owner/admin/editor/operator) и findings.approve (только reviewer) —
остальные роли видят версию с сокрытием секретов или read-only. Перепроверяйте при изменении
ROLE_PERMISSIONS.
Роли организаций и связь org ↔ проект¶
В Umbryx на самом деле три оси ролей, а не две — этот раздел описывает третью (OrganizationMember.role).
- Глобальная роль (
User.role) — см. выше. - Роль в организации (
OrganizationMember.role) — управляет однойOrganization: её реестрами, участниками, командами. Использует тот же словарьMemberRole, но отдельную таблицу членств (organization_members). Сама по себе org-роль не несёт ни одного проектного права из матрицы — как она доходит до проекта, ниже. - Проектная роль (
ProjectMember.role) — то, на что опираютсяROLE_PERMISSIONSи матрица.
Как org-роль доходит до проекта¶
Проект опционально принадлежит организации (Project.org_id).
Привязка/отвязка — PATCH /api/projects/{pid} с {"org_id": "..."}
или {"detach_org": true} — требует org-admin-полномочий над привязываемой org (и над org, от которой отвязывается, если проект уже был привязан); простой editor проекта не может отдать видимость проекта org, которой он не управляет. После
привязки core/access.py::_resolve_role() резолвит эффективную роль
org-участника на каждом запросе (без кэша), в таком порядке:
- Активная прямая запись
ProjectMemberвсегда побеждает (ручная выдача в проект). - Owner/admin организации → всегда проектный
admin(полный обход) — с записьюORG_ADMIN_BYPASS. - Отозванное членство блокирует fallback. Неактивная запись
ProjectMember(сознательное удаление из проекта) останавливает team/org-fallback ниже — удаление из проекта реально отзывает доступ, пока человек состоит в организации-владельце. Только owner/admin org (шаг 2) сохраняет доступ по всей организации. - Членство в команде — если проект привязан к
Team(ProjectTeam), применяетсяTeamMember.default_project_role(или.role). - Любое иное членство в org (manager/team_lead/analyst/…/auditor)
→ fallback 1:1 на org-роль как эффективную проектную. Это даёт
каждому участнику org видимость проектов своей организации без
ручной записи
ProjectMember.
GET /api/projects объединяет те же источники для списка проектов.
Администрирование org-ролей¶
| Набор | Роли | Может… |
|---|---|---|
ORG_ADMIN_ROLES |
owner, admin | Настраивать org: реестры, профиль, приглашать/удалять участников и команды, менять роль (PATCH /api/orgs/{id}/members/{uid}) |
ORG_USER_MANAGER_ROLES |
owner, admin, manager, team_lead | Онбординг новых платформенных аккаунтов сразу в org (POST /api/orgs/{id}/users) без участия platform_admin; события жизненного цикла участников/команд видны в аудите своей org |
Выдать org-роль выше своей нельзя (на добавлении, создании, правке) — потолок: owner(6) > admin(5) > manager(4) > team_lead(3) > остальные(1). нарушение потолка даёт 403, не молчаливый даунгрейд.
Динамический enforcement, без кэша токенов¶
JWT несёт только идентичность (sub, username) плюс глобальную
роль для удобства — каждый запрос перечитывает models.User из БД, и
каждая проектная/org-проверка запрашивает таблицы членств live.
Отдельного кэша прав нет: даунгрейд роли, удаление из участников,
деактивация аккаунта действуют со следующего запроса.
Аудит¶
Добавление/удаление/смена роли участника org или команды пишется в
core/audit.py::log_action (ACT_MEMBER_ADD / ACT_MEMBER_REMOVE /
ACT_MEMBER_ROLE_CHANGE). GET /api/audit/log отдаёт их
platform_admin всегда и ORG_USER_MANAGER_ROLES в рамках их org.
OIDC / SSO-вход¶
Org-scoped OpenID Connect поверх парольного + TOTP-MFA входа. Операционный how-to (Keycloak/Okta/Azure AD/Google Workspace, JIT, сопоставление ролей, enforce SSO) — Настройка OIDC SSO. Здесь — дизайн:
- Модель данных:
OIDCProvider(org-scoped,client_secretчерезcore/crypto.py::encrypt_str),OIDCIdentity(устойчивая связь(provider_id, subject) → user_id),OIDCAuthState(одна строка на вход: PKCEcode_verifier+ nonce — удаляется при использовании, колбэк не реплеится). Эндпоинты всегда из discovery. - Флоу: Authorization Code + PKCE (S256), полная валидация id_token
(JWKS,
iss,aud,exp,nonce).redirect_uriвсегда строится на сервере изPUBLIC_BASE_URL— параметр закрыт для подмены. Каждый URL проходит SSRF-guard. - Линковка аккаунтов никогда не автоматична по email — подтверждение требует реальный пароль аккаунта. Спуфнутый email у менее доверенного IdP не захватит существующий аккаунт.
- JIT-provisioning (per-provider) создаёт аккаунт глобальной роли
user— никогдаplatform_admin— плюс членство в org с ролью изrole_mapping/default_role; неизвестная группа ничего не даёт. - Композиция MFA: требуется ли платформенная TOTP-MFA после OIDC —
per-provider тумблер
require_platform_mfa. При включении валидный id_token даёт только тот жеmfa_pending-токен, что и вход по паролю, — без cookie и access-токена, пока/api/auth/mfa/verifyне подтвердит TOTP-код. - «Обязательный OIDC» (
OIDCProvider.enforced) отключает парольный вход для участников org (platform_adminвсегда исключён). Включается только после первого успешного verified-входа через этого провайдера — сломанная конфигурация не запрёт org разом. - План-гейтинг: создание провайдера требует
PlanFeature.SSOв плане org; редактирование/удаление доступны всегда.
API¶
Управление участниками¶
GET /api/projects/{pid}/members - список участников
POST /api/projects/{pid}/members - добавить участника
PATCH /api/projects/{pid}/members/{uid} - сменить роль
DELETE /api/projects/{pid}/members/{uid} - удалить участника
POST /api/projects/{pid}/transfer-ownership - передать владение
GET /api/projects/{pid}/permissions/me - мои права в проекте
Добавить участника:
Сменить роль:
Права текущего пользователя:
{
"project_id": "p...",
"role": "editor",
"permissions": ["project.read", "hosts.read", "hosts.create", ...],
"is_super_admin": false
}
Правила безопасности¶
- Инвариант дизайна: пользователь не может добраться до объектов чужого
проекта прямым id — каждый роутер обязан вызывать
check_pid_access/check_object_accessпоpid/project_idсамого объекта, никогда по значению из body/query. GET /api/projectsвозвращает только проекты с членством (прямым, через команду или через организацию)credentials.secretбезcredentials.read_secretвозвращается пустой строкой- просмотр расшифрованных секретов учётных данных аудируется в timeline проекта
- просмотр конфиденциальных заметок аудируется в timeline проекта
- просмотр чувствительного loot или скачивание защищённых файлов аудируется в timeline проекта
- Экспорт требует
project.export; импорт —project.import - WebSocket (
/ws/{pid}) аутентифицируется только session cookie (query-токены не принимаются) и проверяет прямое членство в проекте — org-участник без записиProjectMemberимеет REST-доступ, но не откроет WS-комнату проекта - Создатель проекта автоматически становится
owner - При импорте проекта текущий пользователь становится
owner - Последнего
ownerнельзя удалить из проекта - Глобальный
platform_adminобходит все проектные проверки (каждый обход аудируется) POST /api/projects/{pid}/purgeтребует полномочий удаления проекта и также стирает файлы загрузок проекта с диска
Существующая миграция данных¶
При старте бэкенд автоматически создаёт таблицу project_members и
назначает первого пользователя admin владельцем каждого проекта без
Индексы:
idx_pm_project_id— быстрый поиск по проектуidx_pm_user_id— быстрый поиск по пользователюidx_pm_project_user— уникальное ограничение владельца.
Ручное тестирование¶
# 1. Создать пользователя bob
curl -X POST /api/admin/users \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"username":"bob","password":"pass","role":"user"}'
# 2. Bob не видит проекты админа
curl /api/projects -H "Authorization: Bearer $BOB_TOKEN"
# → []
# 3. Админ добавляет Bob как viewer
curl -X POST /api/projects/{pid}/members \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-d '{"user_id":"<bob_id>","role":"viewer"}'
# 4. Bob видит проект, но не может создать хост
curl -X POST /api/hosts \
-H "Authorization: Bearer $BOB_TOKEN" \
-d '{"pid":"...","ip":"10.0.0.1",...}'
# → 403 Insufficient permissions
# 5. Bob как viewer не видит секретов
curl /api/creds?pid=... -H "Authorization: Bearer $BOB_TOKEN"
# → secret: ""
# 6. Проверить права
curl /api/projects/{pid}/permissions/me \
-H "Authorization: Bearer $BOB_TOKEN"