Перейти к содержанию

Umbryx — Контроль доступа

Обзор

Umbryx использует двухуровневую модель доступа:

  1. Глобальная роль (User.role) — системные права
  2. Проектная роль (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).

  1. Глобальная роль (User.role) — см. выше.
  2. Роль в организации (OrganizationMember.role) — управляет одной Organization: её реестрами, участниками, командами. Использует тот же словарь MemberRole, но отдельную таблицу членств (organization_members). Сама по себе org-роль не несёт ни одного проектного права из матрицы — как она доходит до проекта, ниже.
  3. Проектная роль (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-участника на каждом запросе (без кэша), в таком порядке:

  1. Активная прямая запись ProjectMember всегда побеждает (ручная выдача в проект).
  2. Owner/admin организации → всегда проектный admin (полный обход) — с записью ORG_ADMIN_BYPASS.
  3. Отозванное членство блокирует fallback. Неактивная запись ProjectMember (сознательное удаление из проекта) останавливает team/org-fallback ниже — удаление из проекта реально отзывает доступ, пока человек состоит в организации-владельце. Только owner/admin org (шаг 2) сохраняет доступ по всей организации.
  4. Членство в команде — если проект привязан к Team (ProjectTeam), применяется TeamMember.default_project_role (или .role).
  5. Любое иное членство в 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 (одна строка на вход: PKCE code_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       - мои права в проекте

Добавить участника:

{ "user_id": "u...", "role": "editor" }

Сменить роль:

{ "role": "viewer" }

Права текущего пользователя:

{
  "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"