Введение
Всем привет!
Сегодня мы поговорим о том, как сделать вашу систему авторизации надежной, гибкой и простой в управлении с помощью библиотеки Casbin. Если вы когда-нибудь задумывались о том, как настроить права доступа в своем приложении, но вас пугала сложность этого процесса, то эта статья для вас. Мы разберем основные понятия и покажем, что настройка авторизации может быть не такой уж и сложной задачей.
Casbin — это мощная и легко расширяемая библиотека для управления доступом, которая поддерживает различные модели контроля доступа. Вот несколько примеров:
RBAC (Role-Based Access Control) — модель, основанная на ролях, где права доступа назначаются ролям, а не конкретным пользователям.
ABAC (Attribute-Based Access Control) — модель, основанная на атрибутах, где решения принимаются на основе атрибутов пользователя, ресурса, действия и контекста.
ACL (Access Control List) — список управления доступом, где каждому ресурсу сопоставляется список пользователей и их прав.
Сегодня мы сосредоточим наше внимание на RBAC, одной из самых популярных и понятных моделей.
Что такое RBAC
RBAC (Role-Based Access Control), или управление доступом на основе ролей, — это модель контроля доступа, в которой права доступа назначаются не конкретным пользователям, а ролям. Пользователи получают права, принимая на себя определенные роли. Эта модель значительно упрощает управление правами доступа в системе, особенно когда пользователей много и их права часто меняются.
Визуальное представление RBAC (я не разобрался как в md
редакторе на забре вставить диаграмму с локальной машины xD, потому можете посмотреть диаграму на моем google drive)
https://drive.google.com/file/d/1kWD5Xds3e3jZh3fkqECBNuqvqLtRP7xu/view?usp=sharing
На диаграмме показаны:
-
Роли:
Роль представляет собой набор прав доступа.
Например, роль
user
, которая может выполнятьCRUD
с своими ресурсами и читать ресурсы других пользователей.
-
Пользователи:
Пользователь — это любой субъект (человек или процесс), которому нужно иметь доступ к ресурсам.
Пользователям назначаются одна или несколько ролей. Например, пользователь Bob может иметь роль
user
.
-
Права:
Права определяют, какие действия могут выполняться над какими ресурсами.
Например, право
read
может разрешать чтение данных своих или чужих постов.
-
Связи:
Связи между ролями и правами определяют, какие права принадлежат каким ролям.
Связи между пользователями и ролями определяют, какие роли назначены каким пользователям.
Создание RBAC модели
Создание модели RBAC с группировками
Casbin использует файл модели для определения структуры контроля доступа. Давайте создадим файл модели rbac-model.conf
:
rbac-model.conf
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
g2 = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.act == p.act && ; проверяем, что action совпадает как в политике, так и в кортеже, который мы получаем в request.
g(r.sub, p.sub) && ; это внутрення функция, которая сопоставляет sub с request и сравнивает с ролями пользователя в политики.
(g2(p.obj, r.obj) || p.obj == "*") ; мы проверяем, что если req.obj != * (доступ ко всем ресурсам), то выполняется внутрення функция сравнения группировок ресурсов пользователя в политики.
request_definition: Определяет формат запроса (кто, что, какое действие).
policy_definition: Определяет формат политики.
role_definition: Определяет формат ролей и группировок (группы g и g2).
policy_effect: Определяет эффект политики (разрешить или запретить).
matchers: Определяет логику сопоставления запроса с политиками, включая дополнительные группировки.
По сути мы видим, что у нас есть r (role_definition)
- кортеж, который мы будем передавать с запроса, и есть p (policy_definition)
- политика, о которой мы поговорим в следующем пункте.
Мы объявляем g
и g2
(role_definition)
- это особенность RBAC
. По сути своей это всего лишь группировки, пример так же будет в блоке про политики.
g
отвечает за роли "пользователя". Например у нас есть роль owner
и у него может быть несколько видов доступных для него действий - read, edit, delete
.
g2
- это группировка для ресурсов. Допустим у пользователя Bob
может быть несколько ресурсов, которые будут храниться в группе Bob-Resources
.
Раздел policy_effect
определяет, как Casbin
должен обрабатывать результат сопоставления политики. Он отвечает за то, что происходит, когда один или несколько правил политики применяются к запросу на доступ. Проще говоря, он решает, разрешить или запретить действие, основываясь на правилах, которые были найдены. Таким образом, policy_effect
определяет, что если хотя бы одно правило позволяет доступ, то запрос будет одобрен.
Ну и сам алгоритм проверки - matchers
. Их мы можем настраивать как нашей душе угодно, но в примере приведен пример для RBAC
модели.
Примечание Так же стоит заметить порядок сравнений в
matchers
. Так сделано для того, чтобы увеличить скорость. В случае если у нас произойдет ошибка во время проверки на действие нам не придется выполнять лишнии функции.
Настройка политик доступа
Теперь создадим файл с политиками policy.csv
, в котором определим права доступа и группировки:
policy.csv
<!-- определяем пользователей, даем доступ к группе ресурса и доступ к ролям -->
p, alice, admin-res, admin
p, bob, bob-res, user
p, bob, guest-res, guest
p, clare, guest-res, guest
<!-- создаем роль crud, чтобы не дублировать имена действий -->
g, crud, create
g, crud, read
g, crud, update
g, crud, delete
<!-- выдаем ролям действия -->
g, admin, crud
g, user, crud
g, guest, read
<!-- создаем группы ресурсов -->
g2, all-resources
g2, admin-res, all-resource
g2, bob-posts
g2, bob-friends
g2, bob-groups
g2, bob-res, bob-posts
g2, bob-res, bob-groups
g2, bob-res, groups
g2, guest-res, all-resource
p: Определяют права для ролей.. Например, роль
admin
имеет права crud наadmin-res
.g: Определяет отношения ролей. Например,
alice
являетсяadmin
, аbob
являетсяuser
.g2: Определяет группировки ресурсов. Например,
all-resources
принадлежат группеadmin-res
иguest-res
Как и было обещано в предыдущем блоке - буду объяснять что да как.
Первым делом определяются политики - их очень легко понять, По сути буквой p
мы указываем, что это будем политика. Далее мы указываем имя пользователя (в настоящем приложении это может быть токен или идентификатор), далее мы указываем ресурсы к которым наш пользователь будем иметь доступ, ну и указываем действие, которые может выполнять пользователь с ресурсами, к которым у него есть доступ.
И так, далее мы видим тот самый role_definition - g
. Мы создаем группировку действий crud
, по сути это можно воспринимать как переменную - мы создаем переменную, чтобы после ее переиспользовать. Чтобы каждому пользователю не назначать каждый раз одинаковые действий мы будем назначать ему группировку. Сейчас покажу почему это сделано именно так: вместо того, чтобы делать так
p, bob, bob-res, create
p, bob, bob-res, read
p, bob, bob-res, update
p, bob, bob-res, delete
мы создали группировку и можем назначать ее любому количеству пользователей
p, den, res, crud
p, emily, res, crud
p, frank, res, crud
Точно такая же ситуация и с группировками для ресурсов, потому что у нас могут общие ресурсы, могут быть приватные ресурсы и их может быть очень много, поэтому лучше разбить их на группы и подгруппы и выдавать конкретным пользователям.
Интеграция Casbin в приложение
Установка и настройка Casbin
Создадим директория для проекта
mkdir casbin-rbac
cd casbin-rbac
Инициализируем приложение и установим зависимость
go mod init casbinrbac
go get github.com/casbin/casbin/v2
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/persist/file-adapter"
)
func main() {
// так же есть возможность подключить БД адаптеры, смотреть ниже
adapter := fileadapter.NewAdapter("policy.csv")
// enforcer это основной компонент, который отвечает за
// 1. Загрузка модели и политик - загружает модель (rbac-model.conf) и (policy.csv)
// 2. Принятие решений о доступе - принимает запросы на доступ
// и сопоставляет их с политиками для принятия решения, разрешить или запретить доступ
// 3. Управление ролями и пользователями - Позволяет добавлять и удалять пользователей,
// назначать роли и управлять правами доступа
enforcer, err := casbin.NewEnforcer("rbac-model.conf", a)
var (
sub = "bob"
obj = "bob-posts"
act = "read"
)
isAuth, _ := enforcer.Enforce(sub, obj, act)
if ok {
fmt.Printf("sub %s successfully authorized to %s resource, action - %s", sub, obj, act)
} else {
fmt.Printf("sub %s not-authorized to %s resource, action - %s", sub, obj, act)
}
}
Запустим наше приложение и посмотрим что из этого вышло:
go run main.go
Так же `casbin` поддерживает другие виды адаптеров, например `postgres`:
Будем использовать официальный инструмент
go get github.com/casbin/casbin-pg-adapter
Создание БД и миграции
База данны обязательно должна называться casbin и иметь таблицу сasbin_rule
Для начала подними postgres
образ в docker
:
docker run --name casbin -p 5432:5432 -e POSTGRES_PASSWORD=casbin_path -e POSTGRES_USER=casbin_user -e POSTGRES_DB=casbin -d postgres
Вы можете использовать любой инструмент для миграций, я просто оставлю sql
:
create extension if not exists "uuid-ossp";;
create table if not exists casbin_rule
(
id uuid default gen_random_uuid() primary key,
ptype text not null,
v0 text not null,
v1 text not null,
v2 text,
v3 text,
v4 text,
v5 text
);
insert into casbin_rule (ptype, v0, v1, v2) values
('p', 'alice', 'admin-res', 'admin'),
('p', 'bob', 'bob-res', 'user'),
('p', 'bob', 'guest-res', 'guest'),
('p', 'clare', 'guest-res', 'guest'),
('g', 'crud', 'create', ''),
('g', 'crud', 'read', ''),
('g', 'crud', 'update', ''),
('g', 'crud', 'delete', ''),
('g', 'admin', 'crud', ''),
('g', 'user', 'crud', ''),
('g', 'guest', 'read', ''),
('g2', 'all-resources', '', ''),
('g2', 'admin-res', 'all-resources', ''),
('g2', 'bob-posts', '', ''),
('g2', 'bob-friends', '', ''),
('g2', 'bob-groups', '', ''),
('g2', 'bob-res', 'bob-posts', ''),
('g2', 'bob-res', 'bob-friends', ''),
('g2', 'bob-res', 'bob-groups', ''),
('g2', 'guest-res', 'all-resources', ''),
Запись в таблицу нужна только для примера нашего приложения. Выполните эти миграции и можете идти дальше.
Создадим адаптер:
package adapter
import (
"os"
pgadapter "github.com/casbin/casbin-pg-adapter"
"github.com/casbin/casbin/v2/persist"
)
// Casbin.Adapter должен имплементировать интерфейс persist.Adapter,
// который должен реализовать методы для работы с политиками:
/**
type Adapter interface {
// LoadPolicy loads all policy rules from the storage.
LoadPolicy(model model.Model) error
// SavePolicy saves all policy rules to the storage.
SavePolicy(model model.Model) error
// AddPolicy adds a policy rule to the storage.
// This is part of the Auto-Save feature.
AddPolicy(sec string, ptype string, rule []string) error
// RemovePolicy removes a policy rule from the storage.
// This is part of the Auto-Save feature.
RemovePolicy(sec string, ptype string, rule []string) error
// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
// This is part of the Auto-Save feature.
RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error
}
*/
func NewPgCasbinAdapter() (persist.Adapter, error) {
dsn := os.Getenv("PG_DSN")
return pgadapter.NewAdapter(dsn)
}
Конечно же это простой пример того как проходит авторизация
Управление ролями и пользователями
Casbin позволяет легко управлять ролями и пользователями в вашей системе. С его помощью можно добавлять, удалять и изменять роли, а также назначать их пользователям. Давайте рассмотрим основные операции, которые можно выполнять для управления ролями и пользователями.
Добавление пользователей к ролям
Чтобы добавить пользователя к роли, можно использовать метод AddGroupingPolicy
. Это позволяет связывать пользователей с ролями.
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/persist/file-adapter"
)
func main() {
e, _ := casbin.NewEnforcer("rbac-model.conf", "policy.csv")
// Добавление пользователя к роли
e.AddGroupingPolicy("charlie", "user")
}
В этом примере мы добавляем пользователя charlie
к роли user
.
Удаление пользователей из ролей
Чтобы удалить пользователя из роли, можно использовать метод RemoveGroupingPolicy
.
// Удаление пользователя из роли
e.RemoveGroupingPolicy("charlie", "user")
Этот метод удалит связь между пользователем charlie
и ролью user
.
Добавление прав к ролям
Для добавления прав к роли можно использовать метод AddPolicy
.
// Добавление прав к роли
e.AddPolicy("editor", "data2", "write")
Этот метод добавляет правило, что роль editor имеет право write на ресурс data2.
Удаление прав у ролей
Для удаления прав у роли можно использовать метод RemovePolicy
.
// Удаление прав у роли
e.RemovePolicy("editor", "data2", "write")
Этот метод удаляет правило, что роль editor имеет право write на ресурс data2.
Проверка ролей и прав
Вы можете проверить, к каким ролям принадлежит пользователь и какие права у роли.
// Получение ролей пользователя
roles, _ := e.GetRolesForUser("alice")
// Получение прав роли
permissions, _ := e.GetPermissionsForUser("admin")
Эти методы позволяют узнать, какие роли назначены пользователю alice
и какие права имеет роль admin
.
Полный пример
Давайте соберем всё вместе в одном примере.
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/persist/file-adapter"
)
func main() {
// Загрузка модели и политик
e, _ := casbin.NewEnforcer("rbac-model.conf", "policy.csv")
// Добавление пользователя к роли
e.AddGroupingPolicy("charlie", "user")
// Удаление пользователя из роли
e.RemoveGroupingPolicy("charlie", "user")
// Добавление прав к роли
e.AddPolicy("editor", "data2", "write")
// Удаление прав у роли
e.RemovePolicy("editor", "data2", "write")
// Получение ролей пользователя
roles, _ := e.GetRolesForUser("alice")
// Получение прав роли
permissions, _ := e.GetPermissionsForUser("admin")
}
Заключение
Вот и подошел к концу небольшой экскурс по Casbin
, надеюсь, что теперь вы сможете упростить работу с доступами к ресурсам и, может быть, даже сможете принести эту идею в свою команду и получить за это некоторые плюшки.
В целом это весьма несложная тема и с ней достаточно просто разобраться. Надеюсь. что смог кому-то помочь.
Так же Casbin
поддерживает и другие языки, такие как:
php
nodejs
.net
python
rust
java
В ближайшие дни я планирую снять обучающий ролик на ютуб, там я сделаю небольшое API
и покажу на примерах как использовать ролевую модель RBAC
, поэтому если кому-то интересно такое, то предлагаю перейти в мой канал, там я в скором времени анонсирую всю информацию.
Комментарии (8)
atues
20.06.2024 17:58+1Приблуда отличная. На хабре даже была парочка статей лет этак 5 назад (пишу со смартфона, а на нем искать неудобно, так что сами поищите). Настраивал когда-то ABAC на несколько сотен пользователей. Не скажу, что с первого раза получилось, но получилось и в итоге остался доволен. Рекомендую
Ztare
Жуть.
1) я правильно понимаю что вы предлагаете ядро безопасности системы вынести из кода в нонейм разметку без подсветки и валидации в ide?
2) в коде библиотеки разнородные ключи сущностей имеют общий тип "строка". Это источник бесконечного числа ошибок и опечаток. В вашем случае это совсем печальные последствия создаст
blackmarllbor0 Автор
Если важна подсветка в
IDE
, то для этого есть плагины, в которых можно еще и тесты проводить -casbin
достаточно популярная библиотека, которая при том же имеет и онлайн редактор, где так же есть подсветка синтаксиса и тесты.Паранойя иногда бывает полезна, но если есть такие сильные переживания, то предлагаю сходить и посмотреть кто использует
casbin
и некоторые вопросы и страхи отпадут сами собой))Надеюсь, что смог ответить на все вопросы и Вы все же попробуете это решение, хотя бы в своих личных проектах.
LazyTechwork
Я соглашусь с комментарием. Недавно сам искал реализации иерархического RBAC + ABAC, наткнулся на Casbin, но декларация всего этого чуда в отдельных файлах будто не то, что подходит для оперирования 12 000 пользователями. Хочется чтобы отправил запрос в систему авторизации, а она дала либо ответ, что пользователю это можно, либо смогла добавить/удалить пользователя в нужную роль. Если есть хорошие open source решения, рассчитанные на чуть большее чем простая система авторизации, буду рад узнать!
blackmarllbor0 Автор
Не могу сказать точно, но могу предложить посмотреть `open-fga`.
LazyTechwork
OpenFGA это как раз то, с чего я начал, дальше начал смотреть альтернативные open source реализации Google Zanzibar, в том числе Ory Keto, но пока кажется, что это оверхед. Попробуем запустить и положить на это гибрид RBAC+ABAC, может и опубликую потом результаты. Большое спасибо за предложение!
blackmarllbor0 Автор
Понял Вас, будет очень интересно узнать Ваше решение, если будет не сложно, то отпишите, пожалуйста))
blackmarllbor0 Автор
Так же вопрос))) Вы все ядро безопасности отдаете на валидацию ide))))