Управление доступом
Примечание
Начиная с версии crud 1.6.0 (crud-ee 1.7.3), перед началом работы для пользователя, от имени которого выполняются CRUD-операции через роутер, необходимо задать права на выполнение CRUD-операций чтения и записи данных. Список минимально необходимых разрешений для чтения и записи через CRUD приведен в разделе Минимальный набор разрешений в типовых сценариях Tarantool.
Без этих прав пользователь получит ошибку доступа при выполнении CRUD-операций. Ошибка может возникать как на стороне роутера, так и на стороне хранилища — в зависимости от того, какому компоненту не хватает необходимых разрешений. Например, ошибка может быть вызвана отсутствием доступа к метаданным маршрутизации, отсутствием прав read/write на целевой пользовательский спейс или отсутствием права execute для вызова необходимых методов crud.* на роутере.
В этом разделе объясняется, как Tarantool позволяет администраторам не допустить неавторизованный доступ к базе данных и некоторым функциям.
Вкратце:
- Существует метод, который с помощью паролей проверяет, что пользователи являются теми, за кого себя выдают (“аутентификация”).
- Существует системный спейс _user, где хранятся имена пользователей и хеши паролей.
- Существуют функции, чтобы дать определенным пользователям право совершать определенные действия (“права”).
- Существует системный спейс _priv, где хранятся права. Когда пользователь пытается выполнить операцию, проводится проверка на наличие у него прав на выполнение такой операции (“управление доступом”).
Подробная информация приводится ниже.
Для любой локальной или удаленной программы, работающей с Tarantool, есть текущий пользователь. Если удаленное соединение использует бинарный порт, то текущим пользователем, по умолчанию, будет „guest“ (гость). Если соединение использует порт для административной консоли, текущим пользователем будет „admin“ (администратор). При выполнении скрипта инициализации на Lua, текущим пользователем также будет ‘admin’.
Имя текущего пользователя можно узнать с помощью box.session.user().
Текущего пользователя можно изменить:
- Для соединения по бинарному порту – с помощью команды протокола AUTH, которая поддерживается большинством клиентов;
- Для соединения по порту для административной консоли и при выполнении скрипта инициализации на Lua – с помощью box.session.su();
- Для соединения по бинарному порту, которое вызывает хранимую функцию с помощью команды CALL – если для функции включена настройка SETUID, Tarantool временно заменит текущего пользователя на создателя функции со всеми правами создателя во время выполнения функции.
У каждого пользователя (за исключением гостя „guest“) может быть пароль. Паролем является любая буквенно-цифровая строка.
Tarantool passwords are stored in the _user system space with a cryptographic hash function so that, if the password is ‘x’, the stored hash-password is a long string like ‘lL3OvhkIPOKh+Vn9Avlkx69M/Ck=‘. Tarantool supports two protocols for authenticating users:
CHAP with
SHA-1hashingIn this case, password hashes are stored in the
_userspace unsalted. If an attacker gains access to the database, they may crack a password using, for example, a rainbow table.PAP with
SHA256hashing (Enterprise Edition)For PAP, a password is salted with a user-unique salt before saving it in the
_userspace. This keeps the database protected from cracking using a rainbow table. Note that PAP sends a password as plain text, so you need to configure SSL/TLS for a connection.
There are two functions for managing passwords in Tarantool:
- box.schema.user.passwd() allows you to change a user’s password.
- box.schema.user.password() returns a hash of a user’s password.
Tarantool Enterprise Edition also allows you to improve database security by enforcing the use of strong passwords, setting up a maximum password age, and so on. Learn more from the Access control section.
В 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('U','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().
Примечание
Чтобы отследить все подключения и отключения, можно использовать триггеры соединений и аутентификации.
В этом разделе приведен список минимально необходимых разрешений для следующих типовых сценариев работы с Tarantool.
Дополнительные ограничения и рекомендации, связанные с обновлением модуля crud, описаны в разделе Обновление модуля crud.
CRUD передает на сторону хранилища имя пользователя, который инициировал операцию на роутере. Внутренний вызов выполняется от имени служебного пользователя vshard, после чего CRUD переключается на переданного пользователя и выполняет операцию с его правами.
Для корректной работы методов CRUD пользователю, от имени которого выполняются обращения к кластеру через роутер, необходимо предоставить минимальный набор прав на каждом экземпляре хранилища.
Начиная с версии crud 1.6.0, выдайте пользователю необходимые права на пользовательские спейсы и, если применимо, на спейсы метаданных шардирования DDL.
Начиная с версии crud 1.7.0, также выдайте пользователю право read на спейс _bucket.
Выдавать прикладному пользователю право execute на universe не рекомендуется, поскольку оно позволяет выполнять произвольный Lua-код и существенно расширяет полномочия пользователя.
На роутере зарегистрируйте только необходимые функции CRUD и выдайте прикладному пользователю право на их вызов:
local crud_functions = {
'crud.select',
'crud.get',
'crud.insert',
'crud.replace',
'crud.update',
'crud.upsert',
'crud.delete',
}
for _, name in ipairs(crud_functions) do
box.schema.func.create(name, {
setuid = false,
if_not_exists = true,
})
box.schema.user.grant(
'db_user', 'execute', 'function', name,
{if_not_exists = true}
)
end
Минимальный набор прав на экземпляре хранилища зависит от включенной функциональности и версии CRUD. Для чтения и записи данных у пользователя должен быть доступ на чтение и запись к целевым пользовательским спейсам, например к спейсу bands:
box.schema.user.grant('db_user', 'read,write', 'space', 'bands')
Доступ к системным спейсам, связанным с маршрутизацией
Доступ на чтение к
_bucket— начиная с CRUD 1.7.0, операцииbucket_ref/bucket_unrefна стороне хранилища требуют его для проверки состояния и принадлежности сегмента.Если используются метаданные маршрутизации на основе DDL, требуется доступ
readк_ddl_sharding_keyи_ddl_sharding_func.Примечание
Права на метаданные шардирования DDL требуются только в конфигурациях, где маршрутизация CRUD опирается на метаданные, хранящиеся в DDL.
CRUD загружает метаданные шардирования из системных спейсов
_ddl_sharding_keyи_ddl_sharding_func:_ddl_sharding_key— метаданные ключа шардирования;_ddl_sharding_func— метаданные пользовательской функции шардирования.
Если в кластере используется пользовательская функция шардирования, необходимо выдать доступ на чтение к обоим спейсам.
box.schema.user.grant('db_user', 'read', 'space', '_ddl_sharding_key') box.schema.user.grant('db_user', 'read', 'space', '_ddl_sharding_func')
Пример:
box.schema.user.grant('db_user', 'read', 'space', '_bucket')
box.schema.user.grant('db_user', 'read', 'space', '_ddl_sharding_key')
box.schema.user.grant('db_user', 'read', 'space', '_ddl_sharding_func')
box.schema.user.grant('db_user', 'read,write', 'space', 'bands')
В некоторых конфигурациях (в частности, на Tarantool 2.x или при настройке прав вручную) внутренние вызовы на хранилище могут требовать права execute на набор служебных функций vshard.storage.*.
Примечание
Эти права execute требуются служебному пользователю vshard (или роли ``sharding``, если она используется в кластере), а не прикладному пользователю (например, db_user).
Прикладной пользователь должен иметь:
- права на стороне роутера для вызова необходимых методов
crud.*; - storage-side privileges for the required spaces (user spaces and, if applicable, routing metadata spaces such as
_bucketand DDL metadata spaces).
Выдавать прикладному пользователю право execute на universe не рекомендуется.
Примечание
Без этих прав пользователь получит ошибку доступа при выполнении CRUD- операций. Ошибка может возникать как на стороне роутера, так и на стороне хранилища — в зависимости от того, какому компоненту не хватает необходимых разрешений. Например, ошибка может быть вызвана отсутствием доступа к метаданным маршрутизации, отсутствием прав read / write на целевой пользовательский спейс или отсутствием права execute для вызова необходимых методов crud.* на роутере.