Всем привет! Меня зовут Сергей. Я последние два года работаю бэкенд-разработчиком в стартапе MindUp. До этого работал на вольных хлебах и прыгал с проекта в проект. Никогда до этого не писал статьи на Хабре — так что это мой первый опыт. Расскажу о библиотеке RBACX: зачем я её сделал, чем она отличается и как ею пользоваться.
Предыстория: что такое RBAC и ABAC
Чтобы понять, зачем нужен RBACX, кратко напомню главные и наиболее популярные (на мой взгляд) модели контроля доступа.
RBAC (Role-Based Access Control) — управление доступом на основе ролей. Права назначаются ролям, а пользователи получают права, входя в определённые роли. Например, у нас может быть роль admin
, которой разрешено удалять документы, а роль reader
— только просматривать. Преимущество RBAC — простота: добавил пользователя в роль, и он получил набор разрешений.
ABAC (Attribute-Based Access Control) — более гибкая модель, где решения принимаются на основе атрибутов субъекта, ресурса, действия и контекста. Например, можно разрешать доступ к документу, если у пользователя есть атрибут department: "finance"
и документ помечен как sensitivity: "public"
, или проверять время дня, уровень MFA и прочие параметры. ABAC позволяет описывать сложные бизнес-правила через сочетания атрибутов.
На практике обе модели удобно комбинировать: базовые роли (RBAC) + условия по атрибутам (ABAC).
Тем не менее для Python-разработчиков мало готовых «всё-в-одном» решений. Есть коммерческие системы и тяжёлые фреймворки вроде Casbin или Oso, поддерживающие RBAC/ABAC, но они требуют изучения собственных языков политик/конфигураций. Есть простые библиотеки под конкретные фреймворки (например, Flask-RBAC) или ORM. Но если нужен лёгкий, расширяемый движок авторизации «из коробки», с декларативным описанием правил, универсальными адаптерами и возможностью гибкого расширения — таких библиотек не так много.
Почему вообще родилась библиотека RBACX
За последние годы я несколько раз «въезжал» в чужие проекты, где авторизация была реализована по-разному: где-то руками на уровне вьюх, где-то в миддлварах, где-то даже на ORM-правилах. Общие проблемы:
Политики «расползаются» по коду, их сложно ревьюить и тестировать.
В одних проектах внедрён простой RBAC, в других — ABAC (единообразие мне только снилось); и на каждом новом проекте всё начинается «с нуля».
Наблюдаемость и аудит решений часто отсутствуют: непонятно, кто и почему получил доступ.
Хочется единого движка с простыми и понятными python-friendly политиками (JSON/YAML).
И всё это — без привязки к конкретной БД или монолиту, с чистым API на Python (чтобы можно было легко перевезти логику на другой фреймворк).
Из этих потребностей и вырос RBACX — компактный, понятный и расширяемый движок авторизации, который сочетает RBAC + ABAC, поддерживает алгоритмы комбинирования в духе XACML (deny-overrides
, permit-overrides
, first-applicable
) и даёт готовые адаптеры для FastAPI, Flask, Django, Starlette/Litestar (но можете написать свои). И что должно быть полезно - старался спроектировать библиотеку так, чтобы её было удобно интегрировать в проект (в том числе через новомодные IoC-контейнеры).
Кому будет полезна библиотека (и статья)
backend-разработчикам, которым надоело каждый раз писать авторизацию с нуля;
инженерам по безопасности/архитекторам, которым нужен объяснимый PDP (policy decision point) в микросервисах;
командам, у которых в проектах используется сразу несколько python-фреймворков (сервисов), авторизацию в которых нужно привести к единообразию;
тимлидам, для кого важны метрики/логи и hot-reload политик.
Вообще по задумке целевая аудитория проекта - стартапы, а также небольшие и средние проекты, кто хочет «завезти» в проект адекватную авторизацию, но не готов тянуть «тяжеловесов».
Коротко об архитектуре
Внутри RBACX есть четыре ключевых слоя:
Модель домена:
Subject
,Action
,Resource
,Context
— минимальный и понятный набор сущностей, из которых строится среда принятия решения.Движок Guard: принимает среду, исполняет политику(и), возвращает
Decision
с полями:allowed
,effect
(permit/deny
),reason
,rule_id
,policy_id
, плюс обязательства (obligations), например «требуется MFA».Политики: декларативный JSON (или YAML) с условиями (операторы сравнений,
in/contains/startsWith/endsWith
, логическиеand/or/not
, работа со списками и датами), а также policy set с алгоритмами комбинирования решений.Интеграции и наблюдаемость: адаптеры для веб-фреймворков, источники политик (файл/HTTP/S3) с ETag-кешированием, хот-перезагрузка, метрики (Prometheus/OpenTelemetry), аудит и контекст запросов (trace id).
Установка
pip install rbacx
# опционально:
pip install 'rbacx[adapters-fastapi,adapters-flask,adapters-django,metrics,otel,yaml,http,s3,dates]'
Экстры подключают только то, что нужно: адаптеры к фреймворкам, Prometheus/OpenTelemetry, YAML-поддержку, HTTP/S3-источники, операторы времени и т. д. Старался делать движок максимально легковесным — ядро без лишних зависимостей, остальное — по желанию.
Быстрый старт (10 строк кода)
Минимальная политика и проверка доступа в одном файле:
Код
from rbacx import Guard, Subject, Action, Resource, Context
policy = {
"algorithm": "deny-overrides", # запрещаем всё, что явно не разрешено
"rules": [
{
"id": "doc_read",
"effect": "permit",
"actions": ["read"],
"resource": {"type": "doc", "attrs": {"archived": False}},
# роли тоже можно проверять условием:
"condition": {"hasAny": [{"attr": "subject.roles"}, ["reader", "admin"]]},
}
],
}
g = Guard(policy)
d = g.evaluate_sync(
subject=Subject(id="u1", roles=["reader"]),
action=Action("read"),
resource=Resource(type="doc", id="42", attrs={"archived": False}),
context=Context(attrs={}),
)
print(d.allowed, d.reason)
Почему deny-overrides
? Это безопасный дефолт, который сочетает deny-by-default и least-privilege.
Политики: JSON-DSL, условия и policy sets
Ключевой идеей для меня было создание движка, правила для которого можно было бы легко читать (и писать), не погружаясь с головой в библиотеку — выбрал JSON-формат (без создания собственного языка). Поддерживаются:
Сопоставление действия и ресурса: одно/несколько
actions
,resource.type
,resource.id
(точно),resource.attrs
(частичное совпадение).Субъект: роли (
subject.roles
) и произвольные атрибуты (subject.attrs.*
).Условия (
condition
) с выражениями и операторами:==
,!=
,>
,>=
,in
,contains
,startsWith
,endsWith
, проверки списков (hasAny/hasAll
), даты/время и пр.Обязательства (obligations) — например,
{ "type": "require_mfa" }
, которые отдельно проверяются исполнителем обязательств и могут превратить «permit» в «не пускаем, пока не выполнено MFA» (вDecision.challenge
вернётся"mfa"
).
Для объединения нескольких политик есть policy set с алгоритмами в духе XACML:
deny-overrides
— при любой «deny» итог — «deny»;permit-overrides
— наоборот, «permit» приоритетнее;first-applicable
— «первое подходящее правило/политика завершает решение».
Хот-перезагрузка политик из файла/HTTP/S3
RBACX умеет подхватывать изменения политики без перезапуска приложения:
Код
from rbacx import Guard, HotReloader
from rbacx.store.file_store import FilePolicySource
guard = Guard(policy={}) # пустой старт
reloader = HotReloader(
guard=guard,
source=FilePolicySource("policy.json"),
poll_interval=5.0, # каждые 5 секунд
)
# дальше используем guard как обычно; он обновится, когда изменится файл
Для HTTPPolicySource
применяются условные запросы с ETag, чтобы не скачивать лишнего; для S3PolicySource
— работа поверх объектов S3. Внутри перезагрузчик использует аккуратный бэкофф и джиттер, чтобы не «забомбить» источник при ошибках сети.
Интеграции с веб-фреймворками
FastAPI
Код
# examples/fastapi_demo/app.py
import json, pathlib
from fastapi import Depends, FastAPI, Request
from rbacx import Guard, Subject, Action, Resource, Context
from rbacx.adapters.fastapi import require_access
policy = json.loads(pathlib.Path(__file__).with_name("policy.json").read_text("utf-8"))
guard = Guard(policy)
def build_env(request: Request):
user = request.headers.get("x-user", "anonymous")
return (
Subject(id=user, roles=["user"]),
Action("read"),
Resource(type="doc"),
Context(),
)
app = FastAPI()
@app.get("/doc", dependencies=[Depends(require_access(guard, build_env, add_headers=True))])
async def docs():
return {"ok": True}
require_access(...)
вернёт зависимость, которая заблокирует обработчик при отсутствии доступа и (по желанию) добавит служебные заголовки X-RBACX-Reason/Rule/Policy
— удобно для отладки.
Flask
Код
from flask import Flask, request
from rbacx import Subject, Action, Resource, Context
from rbacx.adapters.flask import require_access
# ... guard как в примере выше
def build_env(req):
return (
Subject(id=req.headers.get("x-user", "anon")),
Action("read"),
Resource(type="doc"),
Context(),
)
app = Flask(__name__)
@app.route("/doc")
@require_access(guard, build_env, add_headers=True)
def docs():
return {"ok": True}
Django / DRF
Код (Django + DRF)
# settings.py
MIDDLEWARE += ["rbacx.adapters.django.middleware.RbacxDjangoMiddleware"]
RBACX_GUARD_FACTORY = "myapp.auth.make_guard" # функция, которая вернёт Guard
# views.py
from rbacx.adapters.django.decorators import require
@require(action="read", resource_type="doc")
def view_doc(request):
...
# permissions.py (DRF)
from rbacx.adapters.drf import make_permission
RBACXPermission = make_permission(guard, build_env)
Роли и наследование
Если проекту нужна иерархия ролей — подключаем StaticRoleResolver
:
Код
from rbacx.core.roles import StaticRoleResolver
resolver = StaticRoleResolver({"admin": ["manager"], "manager": ["employee"]})
Guard(policy, role_resolver=resolver) # движок расширит роли субъекта перед проверкой
Аудит и наблюдаемость
Аудит решений: через
DecisionLogger
можно включить audit-mode — писать в лог каждое решение (без фактического запрета), с семплированием и безопасной маскировкой полей.Метрики: из коробки считаются счётчики и гистограммы — сколько решений и сколько времени заняла проверка (
rbacx_decision_total
,rbacx_decision_seconds
) для Prometheus и/или через OpenTelemetry.Трассировка: ASGI-middleware проставляет/прокидывает
X-Request-Id
, фильтр для логгера добавляетtrace_id
во все записи — удобно собирать картину в Loki/Grafana или любой вашей связке.
Как RBACX относится к «большим» движкам авторизации
Существуют популярные решения, которые вы наверняка видели:
Casbin (в т. ч. PyCasbin) — кросс-языковая библиотека с моделями ACL/RBAC/ABAC и адаптерами к хранилищам. На мой взгляд его сложновато внедрить (сконфигурировать), масштабировать и аудировать.
Oso — «батарейки в комплекте», со своей декларативной политикой и глубокой интеграцией в код приложения. Минусы в том, что надо знать их язык + тянуть oso cloud (облачная авторизация).
RBACX целится в немного другой формат (в золотую середину): простой JSON-формат без собственного языка, движок на Python (который легко мокать и тестировать), политика как обычный артефакт (файл/HTTP/S3) с хот-перезагрузкой и XACML-подобной логикой комбинирования решений. Я умышленно не добавлял все варианты алгоритмов и операторов, а брал только то, что актуально в реальной разработке и чего должно хватать для большинства потребностей. Если вам нужен минимум внешних зависимостей и максимально прозрачная интеграция — попробуйте.
Производительность
RBACX поддерживает предкомпиляцию политики (категоризация правил по типу ресурса и специфичности), чтобы сократить количество проверок на «горячем пути». Асинхронные вычисления аккуратно выносят синхронные куски в to_thread
, не блокируя event loop. Это не отменяет здравого смысла (кэшируйте дорогое, не навешивайте лишних условий), но «из коробки» движок работает быстро и предсказуемо. Дополнительно в исходниках найдете код для запуска оценки производительности (вывода бенчмарков).
Планы на развитие
Расширяемые обязательства (включая кастомные исполнители),
машинно-читаемые коды причин, чтобы не парсить фразу,
больше безопасности для источников политик (TLS, подписи/верификация политик),
строгий мод для ядра (без авто-коэрсий дат/чисел),
опциональный кэш решений ядра и многое другое.
Если вам чего-то не хватает — пишите мне в тг/на почту, или заведите issue / пул реквест (это ж опенсорс).
Попробовать сейчас
Установите
rbacx
и возьмите любой пример из папкиexamples/
(FastAPI/Django/Litestar).Переопределите
build_env(...)
, чтобы пробросить вашиSubject/Action/Resource/Context
.Сложите политики в
policy.json
или отдавайте их из конфигурационного сервиса по HTTP/S3.Включите аудит и метрики, чтобы видеть, кто и почему получил (или не получил) доступ.
Как я немного сжулил и чем мне помогли нейросети
Код я брал из своих предыдущих наработок (и разных периодов становления как разработчика), поэтому в нём были разношёрстные нейминги, комментарии и докстринги. С приведением к единому формату сильно помогли нейросети: формулируешь задачу — кидаешь файлы — получаешь единообразный стиль.
Ещё одна боль — английский. Если отдать готовый код современным моделям, они прилично пишут документацию на английском. Это ускорило выпуск проекта.
И — тесты. Мы все знаем, что их нужно писать, но… лень. Когда «мясо» кода было готово, нейросети помогли быстро нагенерировать пачку тестов. Изначально планировал покрыть только ядро (ручками), но потом «зуд перфекционизма» довёл до того, что обратился к нейросетям и покрыл тестами все модули.
Смешные казусы при подготовке проекта
Нейросети иногда фантазируют. В документации внезапно появилось, что библиотека поддерживает JSON и YAML (хотя был только JSON). Вместо того чтобы удалить слово «YAML» в одном файле, я просто добавил поддержку YAML-политик (ибо так может быть более гибко, как мне кажется). Так «баг» перерос в фичу.
Другой кейс: в одном из адаптеров сделал предварительную загрузку политики. Стартовую синхронную загрузку проектировал отдельно (думал, что один раз при сборке приложения сойдёт), а фоновое обновление — неблокирующим (в треде). По привычке писал асинхронный код и забыл, что «первая загрузка» у меня синхронная. В итоге при HTTP
-политике на каждый клиентский запрос улетал синхронный сетевой вызов, блокируя event loop. Пришлось (вместо исправления одной строчки кода в адаптере) переделать ядро так, чтобы оно было и синхронным, и асинхронным.
Вместо заключения
Авторизация — это «невидимая половина» безопасности. По данным OWASP, Broken Access Control — одна из главных проблем, и чаще всего она возникает не из-за криптографии, а из-за некорректности и непроработанности скучных, но жизненно важных деталей — из разряда невнятных правил, недоработки запретов и отсутствия нормальных логов. Давайте делать это просто и прозрачно.
Я буду очень рад вашей обратной связи. Подключайте RBACX в пет-проекты и боевые сервисы, пишите, что зашло, что нет, чего не хватает. И, конечно, звёздочка на GitHub — лучшая благодарность и мотивация продолжать.
Ссылки
Репозиторий: https://github.com/Cheater121/rbacx/
Документация: https://cheater121.github.io/rbacx/
Еще у меня есть бесплатный курс для начинающих python-разработчиков по FastAPI на степике (если кому пригодится - без рекламы и навязывания платных вещей): https://stepik.org/course/179694/
ermolalex
В десктоп приложение на PyQt можно встроить?
Cheater121 Автор
Про такое применение я не думал, но да, за счет того, что ядро не привязано ни к чему и не требует ничего дополнительного, то можно будет это сделать. Надо будет написать логику аутентификации в вашем приложении (если нужна, но скорее всего нужна), написать политику (какие действия кому доступны), один раз при страте собрать Guard, и потом на кликах/командах создаете среду принятия решения (формируете Subject/Action/Resource/Context) и запрашиваете решение. Так что ответ да