Найти тему
Наука от Rezerford

Управление контролем доступа с помощью Redis Bitfields

Оглавление

Как создать высокопроизводительную, высокодоступную и гибкую систему контроля доступа с использованием двоичных данных и побитовых операторов в Redis

Одна из самых сложных частей при написании ориентированного на пользователя приложения или службы - это контроль доступа к ресурсам. Решения об управлении доступом являются одними из самых ранних, которые могут быть приняты, и могут привести к поломке всей платформы. Обычно это компромисс между гранулярностью и скоростью. Давайте рассмотрим, как использовать Redis, чтобы получить детальный контроль и скорость одновременно.

Один из подходов состоит в том, чтобы установить «пользовательские уровни», как правило, числа или роли, такие как «администратор», «обычный пользователь», «привилегированный пользователь» и т. д. Один только этот подход обычно не является очень жизнеспособным путем, поскольку вы сталкиваетесь с не завершающимся аддитивным процессом («супер-супер-администратор» или «отключенный-обычный пользователь» и т. д.) или создание путаницы с широко разнесенными уровнями пользователей и надеждой на лучшее.

Другой подход заключается в том, чтобы дать возможность выполнять конкретные действия (например, редактировать, просматривать, обновлять и т. д.) на уровне пользователя. Для наших целей мы будем называть это «способностью», но вы можете думать о ней как о чем-то похожем на GRANT в SQL или просто на предметно-ориентированное разрешение на основе действий. Контроль доступа, основанный на действии, является гибким, детализированным подходом к обеспечению безопасности ваших ресурсов. Каждому пользователю предоставляется список того, что он может сделать, и когда пользователь пытается выполнить какое-либо действие, вы проверяете его возможности на соответствие тому, что требуется от этого действия. Звучит достаточно просто, правда?

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

Суть этого подхода заключается в использовании двоичных данных, что может показаться странным. Redis, в отличие от многих баз данных, может манипулировать и хранить двоичные данные напрямую. Мы можем использовать эту функцию, чтобы переворачивать отдельные биты в битовой карте для представления возможностей. Каждая возможность представляет бит в индексе. Чтобы иметь доступ к странице или маршруту в вашем приложении, пользователь должен иметь биты, установленные страницей или маршрутом.

Мы будем хранить возможности каждого пользователя в определенном ключе с помощью SETBIT - что-то вроде этого:

> SETBIT user:kyle 0 1

> SETBIT user:kyle 3 1

> SETBIT user:kyle 4 1

В приведенном выше примере я устанавливаю биты 0, 3 и 4 в ключе пользователя: kyle. Если бит еще не установлен, Redis предполагает, что это 0. Каждый маршрут будет иметь похожее растровое изображение для своих требований:

> SETBIT route:/test/:thing 0 1

> SETBIT route:/test/:thing 4 1

Техника опирается на побитовые операторы с помощью команды Redis BITOP. Сначала побитовые операторы могут показаться пугающими, но просто помните, что они похожи на логические операторы. Однако, в то время как логические операторы применяются к одному биту (обычно представленному как истина или ложь), побитовые операторы сравнивают каждое значение побитно. 0-й бит значения 1 сравнивается с 0-м битом значения 2, и это возвращает 0-й бит результата. Мы делаем это для каждого бита в двух значениях. Для этого метода нам понадобится два оператора: побитовый XOR и побитовый AND.

Давайте рассмотрим, как это работает, на нескольких примерах.

Пользователь имеет больше возможностей, чем требуется

В этом случае пользователь имеет все возможности, необходимые для доступа к странице, плюс некоторые. На первом этапе (побитовое И) вы видите, что мы эффективно находим пересечение страницы и пользовательских битов. Мы могли бы остановиться здесь и просто сравнить страницу с результатом AND:

if (page === (page AND user)) { ... }

Тем не менее, для этого потребуется перенести биты обратно на уровень приложения, а не хранить их в Redis. Это не страшно, но есть осложнения. Redis хорошо работает с двоичными данными, но может быть сложнее с некоторыми языками (например, JavaScript, который хочет выполнять приведение типов; именно поэтому у нас есть return_buffers в node_redis). Кроме того, передача битов означает дополнительные издержки и сложность для вашего приложения. К счастью, мы можем сделать все это в Redis следующим шагом.

На этом шаге мы произведём побитовую операцию XOR результата первого шага с битовой маской страницы. В этой операции мы вернем все нули, если они совпадают. Наконец, мы можем использовать BITCOUNT, чтобы увидеть, сколько установлено бит на втором шаге.

-2

BITCOUNT = 0 означает, что пользователь обладает необходимыми возможностями. Если третий шаг вернет 0 к уровню приложения, то будет предоставлен доступ для выполнения действия. Теперь давайте посмотрим на другую ситуацию.

У пользователя есть некоторые возможности, но не все

В этом случае пользователь имеет только некоторые возможности, необходимые для просмотра страницы. На первом этапе объединение битов AND дает только общие биты между страницей и пользователем. На втором этапе мы видим, что XOR найдет биты, которые существуют только в одном. Когда мы используем BITCOUNT, мы увидим, что он больше нуля, что означает, что у пользователя нет прав перейти на данную страницу.

-3

BITCOUNT> 1 означает, что у пользователя нет необходимых возможностей.

Пользователь не имеет ни одной из необходимых возможностей

Когда у пользователя есть некоторые возможности, но не те, которые необходимы, AND будет иметь все биты 0. На этапе XOR конечный результат будет таким же, как страница. Наконец, BITCOUNT для конечного результата будет давать такое же количество битов, что и страница (например, больше нуля), поэтому пользователь не будет допущен на страницу.

-4

BITCOUNT> 1 означает, что у пользователя нет необходимых возможностей.

Страница не имеет требований

В этом случае страница открыта для просмотра и не требует ничего, но у пользователя есть некоторые возможности (которые на самом деле не нужны). Первый этап приведет к 0 битам. Внесение этого во второй этап и выполнение операции XOR одинаково приведет, конечно, к получению всех нулевых битов. BITCOUNT даст 0, что означает, что пользователь может быть пропущен на страницу.

-5

BITCOUNT = 0 означает, что пользователь обладает необходимыми возможностями.

Конечно, если у пользователя нет никаких возможностей, а страница не требует их, таблицы истинности будут почти идентичны этому случаю и предоставят пользователю доступ.

Реализация этого типа логики в Redis требует всего несколько команд:

> MULTI
OK
> BITOP AND cap-temp user:kyle route:/test/:thing
QUEUED
> BITOP XOR cap-temp route:/test/:thing cap-temp
QUEUED
> BITCOUNT cap-temp
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1
3) (integer) 0

Единственный действительно важный результат - третий, от BITCOUNT. В приведенном выше случае результат равен 0. Это здорово - это означает, что между страницей и замаскированной комбинацией пользователя и страницы нет конфликтующих битов. Если бы результат BITCOUNT был больше нуля, мы бы знали, что что-то не совпадает, и мы не позволили бы пользователю получить доступ к ресурсу.

Помимо отдельных битов

После того, как вы установили этот двоичный ключ для своих пользовательских возможностей, вы не ограничены только индивидуальным хранением битов - помните, это двоичный код: окончательная свободная форма.

У Redis есть команда BITFIELD. Эта команда похожа на GETBIT и SETBIT, поскольку она может манипулировать или извлекать данные побитно, но также имеет возможность делать немного больше. Сначала давайте рассмотрим команду:

-6

Приведенная выше команда показывает две подкоманды (GET, SET), но вы, безусловно, можете сделать столько, сколько захотите. Типы данных могут быть со знаком (ix) или без знака (ux). Это означает, например, что вы можете установить u8 в битовый индекс 0, а затем получить u4 в индексе 0 (это даст первые четыре бита u8).

Давайте рассмотрим еще один пример использования BITFIELD. На этот раз мы вернем двоичное представление байта.

> DEL a-key
(integer) 1

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

> bitfield a-key SET u8 0 127
1) (integer) 0

Теперь мы устанавливаем первый байт (u8) равным 127. Те, кто знает двоичные числа, увидят, что это самое высокое значение 7-разрядного числа.

> BITFIELD a-key GET u1 0 GET u1 1 GET u1 2 GET u1 3 GET u1 4 GET u1 5 GET u1 6 GET u1 7
1) (integer) 0
2) (integer) 1
3) (integer) 1
4) (integer) 1
5) (integer) 1
6) (integer) 1
7) (integer) 1
8) (integer) 1

Это выглядит сложно, но на самом деле это что-то довольно простое. Мы возвращаем 1 или 0 (u1) каждого бита в байте, который мы установили ранее. Как и ожидалось, мы видим один ноль и семь единиц.

> BITFIELD a-key GET u4 0 GET u4 4
1) (integer) 7
2) (integer) 15

С помощью приведенной выше команды мы получаем два без знаковых 4-битных значения с индексом 0 и индексом 4. Зная про двоичную систему счисления, мы знаем, почему мы получаем 7 (0111) и 15 (1111).

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

  • User А является общим администратором сайта.
  • User B является администратором региона.
  • User C является администратором раздела.
  • User D является администратором страницы.

Допустим, только администраторы раздела и администраторы сайта должны иметь возможность изменять параметры конфигурации раздела. В этом примере было бы довольно просто перевернуть биты, если бы у вас было небольшое количество пользователей, но что, если вместо одного администратора страницы есть 1000 или 10000 (или даже 1 миллион) пользователей? В этой ситуации было бы лучше иметь пользовательские уровни для этой конкретной области. Любой, кто находится выше определенного уровня, может изменять параметры. Для этого мы можем хранить полные числа в растровом изображении пользователя. Вернёмся к примеру:

  • User A: 127 (u7 Бинарный код: 1111111)
  • User B: 60 (u7 Бинарный код : 0111100)
  • User C: 40 (u7 Бинарный код : 0101000)
  • User D: 20 (u7 Бинарный код : 0010100)

В этих битах нет реального шаблона, но это место, где вы можете ввести слово без знака u7. Что здорово в этом, так это то, что он может сосуществовать с возможностями флаговых битов. Вот как бы вы выложили это:

-7

В первом и третьем байтах есть что-то еще (не важно). Второй байт относится к нашему контролю доступа. 0-й бит - это битовый флаг, который, скажем, «администратор», а биты с 1 по 7 образуют слово u7. Наши стандартные возможности работы с битами работают одинаково. Итак, маршрут защищен растровым изображением, которое не имеет возможностей в области, где живет u7:

-8

Используя нашу технологию битовой маски сверху (И затем XOR), мы фактически игнорируем u7. Затем мы используем BITFIELD отдельно, чтобы включить u7 в качестве нашего дополнительного уровня пользователя.

Все вместе

Во-первых, настройте страницу / маршрут, требуя установки 0-го и 8-го бита и вставив u7 в биты 9–16 со значением 0. Затем мы будем использовать BITFIELD, чтобы также задать требования к странице, но на другом ключ и до желаемого уровня (60):

> BITFIELD a-page SET u1 0 1 SET u1 8 1 SET u7 9 0
> BITFIELD a-page:level SET u7 9 60

Затем мы настроим пользователя, который может получить к нему доступ (User B), пользователя, который не может из-за своего пользовательского уровня ( User C), и, наконец, пользователя, который соответствует требованиям уровня пользователя, но не соответствует битам возможностей ( User D):

> BITFIELD user-b SET u1 0 1 SET u1 8 1 SET u7 9 60
> BITFIELD user-c SET u1 0 1 SET u1 8 1 SET u7 9 40
> BITFIELD user-d SET u1 8 1 SET u7 9 60

Теперь давайте оценим это по сравнению с нашим ресурсом.

User B

> MULTI
OK
> BITOP AND cap-temp a-page user-b
QUEUED
> BITOP XOR cap-temp a-page cap-temp
QUEUED
> BITCOUNT cap-temp
QUEUED
> BITFIELD a-page:level GET u7 9
QUEUED
> BITFIELD user-b GET u7 9
QUEUED
> EXEC
1) (integer) 2
2) (integer) 2
3) (integer) 0
4) 1) (integer) 60
5) 1) (integer) 60

Первые несколько команд похожи на те, которые мы использовали в первой части. На уровне приложения мы проверим следующее:

  • Третий результат 0? Если так, продолжаем; в противном случае отклонить доступ.
  • Пятый результат больше, чем четвертый? Если так, продолжаем; в противном случае отклонить доступ.

Как видите, третий результат (BITCOUNT) равен нулю (что является нашей проверкой способности «вы в порядке»). Четвертый результат - требуемый уровень пользователя страницы. И пятый результат - уровень пользователя.

User C

> MULTI
OK
> BITOP AND cap-temp a-page user-c
QUEUED
> BITOP XOR cap-temp a-page cap-temp
QUEUED
> BITCOUNT cap-temp
QUEUED
> BITFIELD a-page:level GET u7 9
QUEUED
> BITFIELD user-c GET u7 9
QUEUED
> EXEC
1) (integer) 2
2) (integer) 2
3) (integer) 0
4) 1) (integer) 60
5) 1) (integer) 40

Оценивая третий результат, мы в порядке, так как результат равен 0. Однако четвертый (требуемый уровень ресурса) выше уровня пользователя (пятый результат), поэтому приложение отклонит запрос.

User D

> MULTI
OK
> BITOP AND cap-temp a-page user-d
QUEUED
> BITOP XOR cap-temp a-page cap-temp
QUEUED
> BITCOUNT cap-temp
QUEUED
> BITFIELD a-page:level GET u7 9
QUEUED
> BITFIELD user-d GET u7 9
QUEUED
> EXEC
1) (integer) 2
2) (integer) 2
3) (integer) 1
4) 1) (integer) 60
5) 1) (integer) 60

Это немного другой случай - третий результат возвращает число больше нуля, поэтому этот запрос будет отклонен. Однако важно отметить, что он пройдет вторую проверку - мы видим, что уровень пользователя приемлем.

Переведённый источник https://www.infoworld.com/article/3226768/manage-access-control-using-redis-bitfields.html