Введение

Всем привет!

Сегодня мы поговорим о том, как сделать вашу систему авторизации надежной, гибкой и простой в управлении с помощью библиотеки 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

На диаграмме показаны:

  1. Роли:

    • Роль представляет собой набор прав доступа.

    • Например, роль user, которая может выполнять CRUD с своими ресурсами и читать ресурсы других пользователей.

  2. Пользователи:

    • Пользователь — это любой субъект (человек или процесс), которому нужно иметь доступ к ресурсам.

    • Пользователям назначаются одна или несколько ролей. Например, пользователь Bob может иметь роль user.

  3. Права:

    • Права определяют, какие действия могут выполняться над какими ресурсами.

    • Например, право read может разрешать чтение данных своих или чужих постов.

  4. Связи:

    • Связи между ролями и правами определяют, какие права принадлежат каким ролям.

    • Связи между пользователями и ролями определяют, какие роли назначены каким пользователям.

Создание 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, поэтому если кому-то интересно такое, то предлагаю перейти в мой канал, там я в скором времени анонсирую всю информацию.

Github

Telegram

Instagram

Комментарии (8)


  1. Ztare
    20.06.2024 17:58
    +3

    Жуть.

    1) я правильно понимаю что вы предлагаете ядро безопасности системы вынести из кода в нонейм разметку без подсветки и валидации в ide?

    2) в коде библиотеки разнородные ключи сущностей имеют общий тип "строка". Это источник бесконечного числа ошибок и опечаток. В вашем случае это совсем печальные последствия создаст


    1. blackmarllbor0 Автор
      20.06.2024 17:58

      1. Если важна подсветка в IDE, то для этого есть плагины, в которых можно еще и тесты проводить - casbin достаточно популярная библиотека, которая при том же имеет и онлайн редактор, где так же есть подсветка синтаксиса и тесты.

      2. Паранойя иногда бывает полезна, но если есть такие сильные переживания, то предлагаю сходить и посмотреть кто использует casbin и некоторые вопросы и страхи отпадут сами собой))

      Надеюсь, что смог ответить на все вопросы и Вы все же попробуете это решение, хотя бы в своих личных проектах.


    1. LazyTechwork
      20.06.2024 17:58
      +1

      Я соглашусь с комментарием. Недавно сам искал реализации иерархического RBAC + ABAC, наткнулся на Casbin, но декларация всего этого чуда в отдельных файлах будто не то, что подходит для оперирования 12 000 пользователями. Хочется чтобы отправил запрос в систему авторизации, а она дала либо ответ, что пользователю это можно, либо смогла добавить/удалить пользователя в нужную роль. Если есть хорошие open source решения, рассчитанные на чуть большее чем простая система авторизации, буду рад узнать!


      1. blackmarllbor0 Автор
        20.06.2024 17:58
        +1

        Не могу сказать точно, но могу предложить посмотреть `open-fga`.


        1. LazyTechwork
          20.06.2024 17:58
          +1

          OpenFGA это как раз то, с чего я начал, дальше начал смотреть альтернативные open source реализации Google Zanzibar, в том числе Ory Keto, но пока кажется, что это оверхед. Попробуем запустить и положить на это гибрид RBAC+ABAC, может и опубликую потом результаты. Большое спасибо за предложение!


          1. blackmarllbor0 Автор
            20.06.2024 17:58
            +1

            Понял Вас, будет очень интересно узнать Ваше решение, если будет не сложно, то отпишите, пожалуйста))


    1. blackmarllbor0 Автор
      20.06.2024 17:58

      Так же вопрос))) Вы все ядро безопасности отдаете на валидацию ide))))


  1. atues
    20.06.2024 17:58
    +1

    Приблуда отличная. На хабре даже была парочка статей лет этак 5 назад (пишу со смартфона, а на нем искать неудобно, так что сами поищите). Настраивал когда-то ABAC на несколько сотен пользователей. Не скажу, что с первого раза получилось, но получилось и в итоге остался доволен. Рекомендую