Top.Mail.Ru
Управление доступом | Tarantool
Tarantool
Узнайте содержание релиза 2.8
Управление доступом

Управление доступом

Управление доступом

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

Вкратце:

  • Существует метод, который с помощью паролей проверяет, что пользователи являются теми, за кого себя выдают (“аутентификация”).
  • Существует системный спейс _user, где хранятся имена пользователей и хеши паролей.
  • Существуют функции, чтобы дать определенным пользователям право совершать определенные действия (“права”).
  • Существует системный спейс _priv, где хранятся права. Когда пользователь пытается выполнить операцию, проводится проверка на наличие у него прав на выполнение такой операции (“управление доступом”).

Подробная информация приводится ниже.

Для любой локальной или удаленной программы, работающей с Tarantool, есть текущий пользователь. Если удаленное соединение использует бинарный порт, то текущим пользователем, по умолчанию, будет „guest“ (гость). Если соединение использует порт для административной консоли, текущим пользователем будет „admin“ (администратор). При выполнении скрипта инициализации на Lua, текущим пользователем также будет ‘admin’.

Имя текущего пользователя можно узнать с помощью box.session.user().

Текущего пользователя можно изменить:

  • Для соединения по бинарному порту – с помощью команды протокола AUTH, которая поддерживается большинством клиентов;
  • Для соединения по порту для административной консоли и при выполнении скрипта инициализации на Lua – с помощью box.session.su();
  • Для соединения по бинарному порту, которое вызывает хранимую функцию с помощью команды CALL – если для функции включена настройка SETUID, Tarantool временно заменит текущего пользователя на создателя функции со всеми правами создателя во время выполнения функции.

У каждого пользователя (за исключением гостя „guest“) может быть пароль. Паролем является любая буквенно-цифровая строка.

Пароли Tarantool’а хранятся в системном спейсе _user с криптографической хеш-функцией, так что если паролем является ‘x’, хранится хеш-пароль в виде длинной строки, например ‘lL3OvhkIPOKh+Vn9Avlkx69M/Ck=‘. Когда клиент подключается к экземпляру Tarantool’а, экземпляр отправляет случайное значение соль, которое клиент должен сложить вместе с хеш-паролем перед отправкой на экземпляр. Таким образом, изначальное значение ‘x’ никогда не хранится нигде, кроме как в голове самого пользователя, а хешированное значение никогда не передается по сети, кроме как в смешанном с солью виде.

Примечание

Для получения дополнительной информации об алгоритме хеширования паролей (например, для написания нового клиентского приложения), прочтите файл заголовка scramble.h.

Система не дает злоумышленнику определить пароли путем просмотра файлов журнала или слежения за активностью. Это та же система, несколько лет назад внедренная в MySQL, которой оказалось достаточно для объектов со средней степенью безопасности. Тем не менее, администраторы должны предупреждать пользователей, что никакая система не защищена полностью от постоянных длительных атак, поэтому пароли следует охранять и периодически изменять. Администраторы также должны рекомендовать пользователям выбирать длинные неочевидные пароли, но сами пользователи выбирают свои пароли и изменяют их.

Для управления паролями в Tarantool’е есть две функции: box.schema.user.passwd() для изменения пароля пользователя и box.schema.user.password() для получения хеша пароля пользователя.

В Tarantool’е одна база данных. Она может называться «box.schema» или «universe». База данных содержит объекты базы данных, включая спейсы, индексы, пользователей, роли, последовательности и функции.

Владелец объекта базы данных – это пользователь, который создал его. Владельцем самой базы данных и объектов, которые изначально были созданы (системные спейсы и пользователи по умолчанию) является „admin“.

У владельцев автоматически есть права на то, что они создают. Владельцы могут поделиться этими правами с другими пользователями или ролями с помощью запросов box.schema.user.grant(). Можно предоставить следующие права:

  • „read“ (чтение), например, разрешить выборку из спейса
  • „write“ (запись), например, разрешить обновление спейса
  • „execute“ (выполнение), например, разрешить вызов функции, или (реже) разрешить использование роли
  • „create“ (создание), например, разрешить выполнение box.schema.space.create (также необходим доступ к определенным системным спейсам)
  • „alter“ (изменение), например, разрешить выполнение box.space.x.index.y:alter (также необходим доступ к определенным системным спейсам
  • „drop“ (удаление), например, разрешить выполнение box.sequence.x:drop (также необходим доступ к определенным системным спейсам)
  • „usage“ (использование), например, допустимо ли любое действие, несмотря на другие права (иногда удобно отменить право на использование, чтобы временно заблокировать пользователя, не удаляя ег
  • „session“ (сессия), например, может ли пользователь выполнить подключение „connect“.

Чтобы создавать объекты, у пользователей должны быть права на создание „create“ и хотя бы права на чтение „read“ и запись „write“ в системный спейс с похожим именем (например, на спейс _space, если пользователю необходимо создавать спейсы.

Чтобы получать доступ к объектам, у пользователей должны быть соответствующие права на объект (например, права на выполнение „execute“ на функцию F, если пользователям необходимо выполнить функцию F). См. ниже некоторые примеры предоставления определенных прав, которые может выдать „admin“ или создатель объекта.

Чтобы удалить объект, у пользователя должны быть права уровня „admin“ или роль „super“. Некоторые объекты также может удалить их создатель. Как владелец всей базы данных, любой „admin“ может удалить любой объект, включая других пользователей.

Чтобы предоставить права пользователю, владелец объекта выполняет команду box.schema.user.grant(). Чтобы отменить права пользователя, владелец объекта выполняет команду box.schema.user.revoke(). В любом случае можно использовать до пяти параметров:

(user-name, privilege, object-type [, object-name [, options]])
  • user-name – это пользователь (или роль), который получит или потеряет права;

  • privilege – это тип прав: „read“, „write“, „execute“, „create“, „alter“, „drop“, „usage“ или „session“ (или список прав, разделенных запятыми);

  • object-type — это любой тип объекта: „space“, „index“, „sequence“, „function“, „user“, „role“ или „universe“;

  • object-name — это имя объекта, на который выдаются права: не указывается, если object-type = „universe“; не указывается или равно nil, если права предоставляются на все объекты одного типа);

  • options — это список параметров, приведенный в скобках: например, {if_not_exists=true|false} (обычно не указан, поскольку допустимы значения по умолчанию).

    Все изменения прав пользователя сразу же применяются в текущих сеансах и на объектах, например, функциях.

Пример предоставления нескольких типов прав одновременно

В данном примере пользователь „admin“ выдает много типов прав на множество объектов пользователю „U“ в едином запросе.

box.schema.user.grant('U','read,write,execute,create,drop','universe')

Примеры предоставления прав на определенные действия

В этих примерах администратор выдает пользователю „U“ минимально необходимые права на определенные действия.

-- Чтобы 'U' мог создавать спейсы:
  box.schema.user.grant('U','create','space')
  box.schema.user.grant('U','write', 'space', '_schema')
  box.schema.user.grant('U','write', 'space', '_space')
-- Чтобы 'U' мог создавать индексы для спейса T
  box.schema.user.grant('U','create,read','space','T')
  box.schema.user.grant('U','read,write','space','_space_sequence')
  box.schema.user.grant('U','write', 'space', '_index')
-- Чтобы 'U' мог изменять индексы для спейса T (подразумевается, что 'U' не создавал индекс)
  box.schema.user.grant('U','alter','space','T')
  box.schema.user.grant('U','read','space','_space')
  box.schema.user.grant('U','read','space','_index')
  box.schema.user.grant('U','read','space','_space_sequence')
  box.schema.user.grant('U','write','space','_index')
-- Чтобы 'U' мог изменять индексы для спейса T (подразумевается, что 'U' создал индекс)
  box.schema.user.grant('U','read','space','_space_sequence')
  box.schema.user.grant('U','read,write','space','_index')
-- Чтобы 'U' мог создавать пользователей:
  box.schema.user.grant('U','create','user')
  box.schema.user.grant('U', 'read,write', 'space', '_user')
  box.schema.user.grant('U', 'write', 'space', '_priv')
-- Чтобы 'U' мог создавать роли:
  box.schema.user.grant('U','create','role')
  box.schema.user.grant('U', 'read,write', 'space', '_user')
  box.schema.user.grant('U', 'write', 'space', '_priv')
-- Чтобы 'U' мог создавать генераторы последовательностей:
  box.schema.user.grant('U','create','sequence')
  box.schema.user.grant('U', 'read,write', 'space', '_sequence')
-- Чтобы 'U' мог создавать функции:
  box.schema.user.grant('U','create','function')
  box.schema.user.grant('U','read,write','space','_func')
-- Чтобы 'U' мог создавать любой объект любого типа
  box.schema.user.grant('guest','read,write,create','universe')
-- Чтобы 'U' могу выдавать права на созданные им объекты
  box.schema.user.grant('U','write','space','_priv')
-- Чтобы 'U' мог производить выборку или получать данные из спейса 'T'
  box.schema.user.grant('U','read','space','T')
-- Чтобы 'U' мог выполнять обновление, вставку, удаление или очистку спейса 'T'
  box.schema.user.grant('U','write','space','T')
-- Чтобы 'U' мог выполнять функцию 'F'
  box.schema.user.grant('U','execute','function','F')
-- Чтобы 'U' мог использовать функцию "S:next()" для последовательности S
  box.schema.user.grant('U','read,write','sequence','S')
-- Чтобы 'U' мог использовать функцию "S:set()" или "S:reset()" для последовательности S
  box.schema.user.grant('U','write','sequence','S')
-- Чтобы 'U' мог удалить последовательность (подразумевается, что 'U' не создавал ее)
  box.schema.user.grant('U','drop','sequence')
  box.schema.user.grant('U','write','space','_sequence_data')
  box.schema.user.grant('U','write','space','_sequence')
-- Чтобы 'U' мог удалить функцию (подразумевается, что 'U' не создавал ее)
  box.schema.user.grant('U','drop','function')
  box.schema.user.grant('U','write','space','_func')
-- Чтобы 'U' мог удалить спейс и объекты, которые с ним связаны
  box.schema.user.grant('U','create,drop','space')
  box.schema.user.grant('U','write','space','_schema')
  box.schema.user.grant('U','write','space','_space')
  box.schema.user.grant('U','write','space','_space_sequence')
  box.schema.user.grant('U','read','space','_trigger')
  box.schema.user.grant('U','read','space','_fk_constraint')
  box.schema.user.grant('U','read','space','_ck_constraint')
  box.schema.user.grant('U','read','space','_func_index')
-- Чтобы 'U' мог удалить любой спейс (игнорировать, если у пользователя уже есть такое право)
  box.schema.user.grant('U','drop','space',nil,{if_not_exists=true})

Пример создания пользователей и объектов и последующей выдачи прав

Здесь создана Lua-функция, которая будет выполняться от ID создавшего функцию пользователя, даже если ее вызывает другой пользователь.

First, the two spaces („u“ and „i“) are created, and a no-password user („internal“) is granted full access to them. Then a („read_and_modify“) is defined and the no-password user becomes this function’s creator. Finally, another user („public_user“) is granted access to execute Lua functions created by the no-password user.

box.schema.space.create('u')
box.schema.space.create('i')
box.space.u:create_index('pk')
box.space.i:create_index('pk')

box.schema.user.create('internal')

box.schema.user.grant('internal', 'read,write', 'space', 'u')
box.schema.user.grant('internal', 'read,write', 'space', 'i')
box.schema.user.grant('internal', 'create', 'universe')
box.schema.user.grant('internal', 'read,write', 'space', '_func')

function read_and_modify(key)
  local u = box.space.u
  local i = box.space.i
  local fiber = require('fiber')
  local t = u:get{key}
  if t ~= nil then
    u:put{key, box.session.uid()}
    i:put{key, fiber.time()}
  end
end

box.session.su('internal')
box.schema.func.create('read_and_modify', {setuid= true})
box.session.su('admin')
box.schema.user.create('public_user', {password = 'secret'})
box.schema.user.grant('public_user', 'execute', 'function', 'read_and_modify')

Роль представляет собой контейнер для прав, которые можно предоставить обычным пользователям. Вместо того, чтобы предоставлять или отменять индивидуальные права, можно поместить все права в роль, а затем назначить или отменить роль.

Информация о роли хранится в спейсе _user, но третье поле кортежа – поле типа – это ‘роль’, а не ‘пользователь’.

В управлении доступом на основе ролей важно то, что роли могут быть вложенными. Например, роли R1 можно предоставить право типа «роль R2», то есть пользователи с ролью R1 тогда получат все права роли R1 и роли R2. Другими словами, пользователь получает все права, которые предоставляются ролям пользователя напрямую и опосредованно.

Фактически есть два способа предоставить или отменить роль: box.schema.user.grant-or-revoke(имя-пользователя-или-имя-роли,'execute', 'role',имя-роли...) или box.schema.user.grant-or-revoke(имя-пользователя-или-имя-роли,имя-роли...). Рекомендуется использовать второй способ.

Права типов „usage“ и „session“ нельзя предоставить для роли.

Пример

-- Этот пример сработает для пользователя, у которого множество прав, например, 'admin',
-- или для пользователя с заданной ролью 'super'
-- Создать спейс T с первичным индексом
box.schema.space.create('T')
box.space.T:create_index('primary', {})
-- Создать пользователя U1, чтобы затем можно было заменить текущего пользователя на U1
box.schema.user.create('U1')
-- Создать две роли, R1 и R2
box.schema.role.create('R1')
box.schema.role.create('R2')
-- Предоставить роль R2 для роли R1, а роль R1 пользователю U1 (порядок не имеет значения)
-- Есть два способа предоставить роль, здесь используется более короткий способ
box.schema.role.grant('R1', 'R2')
box.schema.user.grant('U1', 'R1')
-- Предоставить права на чтение/запись на спейс T для роли R2
-- (но не для роли R1 и не пользователю U1)
box.schema.role.grant('R2', 'read,write', 'space', 'T')
-- Изменить текущего пользователя на пользователя U1
box.session.su('U1')
-- Теперь вставка в спейс T сработает, потому что благодаря вложенным ролям,
-- у пользователя U1 есть права на запись в спейс T
box.space.T:insert{1}

Более подробную информацию см. в справочнике по встроенным модулям: box.schema.user.grant() и box.schema.role.grant().

Сессия – это состояние подключения к Tarantool’у. Она содержит:

  • Идентификатор (ID) в виде целого числа, определяющий соединение,
  • текущий пользователь, использующий соединение,
  • текстовое описание подключенного узла и
  • локальное состояние сессии, например, переменные и функции на Lua.

В Tarantool за один сеанс могут выполняться несколько транзакций одновременно. Каждую транзакцию можно определить по уникальному идентификатору в виде целого числа, который можно запросить в начале транзакции с помощью box.session.sync().

Примечание

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