Access control
Note
Starting from crud 1.6.0 (crud-ee 1.7.3), the user on behalf of whom CRUD operations are performed through the router must be granted privileges to execute CRUD read and write operations. See the list of minimum required privileges for reading and writing through CRUD in the Minimum set of privileges in typical Tarantool scenarios section.
Without these privileges, the user will get an access error when executing CRUD operations.
The error can occur either on the router side or on the storage side, depending on which component lacks the required permissions.
For example, the error may be caused by missing access to routing metadata,
missing read/ write privileges for the target user space, or missing execute privileges to call the required crud.* methods on the router.
This section explains how Tarantool makes it possible for administrators to prevent unauthorized access to the database and to certain functions.
Briefly:
- There is a method to guarantee with password checks that users really are who they say they are (“authentication”).
- There is a _user system space, where usernames and password-hashes are stored.
- There are functions for saying that certain users are allowed to do certain things (“privileges”).
- There is a _priv system space, where privileges are stored. Whenever a user tries to do an operation, there is a check whether the user has the privilege to do the operation (“access control”).
Details follow.
There is a current user for any program working with Tarantool, local or remote. If a remote connection is using a binary port, the current user, by default, is ‘guest’. If the connection is using an admin-console port, the current user is ‘admin’. When executing a Lua initialization script, the current user is also ‘admin’.
The current user name can be found with box.session.user().
The current user can be changed:
- For a binary port connection – with the AUTH protocol command, supported by most clients;
- For an admin-console connection and in a Lua initialization script – with box.session.su();
- For a binary-port connection invoking a stored function with the CALL command – if the SETUID property is enabled for the function, Tarantool temporarily replaces the current user with the function’s creator, with all the creator’s privileges, during function execution.
Each user (except ‘guest’) may have a password. The password is any alphanumeric string.
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 has one database. It may be called “box.schema” or “universe”. The database contains database objects, including spaces, indexes, users, roles, sequences, and functions.
The owner of a database object is the user who created it. The owner of the database itself, and the owner of objects that are created initially (the system spaces and the default users) is ‘admin’.
Owners automatically have privileges for what they create. They can share these privileges with other users or with roles, using box.schema.user.grant() requests. The following privileges can be granted:
- ‘read’, e.g. allow select from a space
- ‘write’, e.g. allow update on a space
- ‘execute’, e.g. allow call of a function, or (less commonly) allow use of a role
- ‘create’, e.g. allow box.schema.space.create (access to certain system spaces is also necessary)
- ‘alter’, e.g. allow box.space.x.index.y:alter (access to certain system spaces is also necessary)
- ‘drop’, e.g. allow box.sequence.x:drop (access to certain system spaces is also necessary)
- ‘usage’, e.g. whether any action is allowable regardless of other privileges (sometimes revoking ‘usage’ is a convenient way to block a user temporarily without dropping the user)
- ‘session’, e.g. whether the user can ‘connect’.
To create objects, users need the ‘create’ privilege and at least ‘read’ and ‘write’ privileges on the system space with a similar name (for example, on the _space if the user needs to create spaces).
To access objects, users need an appropriate privilege on the object (for example, the ‘execute’ privilege on function F if the users need to execute function F). See below some examples for granting specific privileges that a grantor – that is, ‘admin’ or the object creator – can make.
To drop an object, a user must be an ‘admin’ or have the ‘super’ role. Some objects may also be dropped by their creators. As the owner of the entire database, any ‘admin’ can drop any object, including other users.
To grant privileges to a user, the object owner says box.schema.user.grant(). To revoke privileges from a user, the object owner says box.schema.user.revoke(). In either case, there are up to five parameters:
(user-name, privilege, object-type [, object-name [, options]])
user-nameis the user (or role) that will receive or lose the privilege;privilegeis any of ‘read’, ‘write’, ‘execute’, ‘create’, ‘alter’, ‘drop’, ‘usage’, or ‘session’ (or a comma-separated list);object-typeis any of ‘space’, ‘index’, ‘sequence’, ‘function’, ‘user’, ‘role’, or ‘universe’;object-nameis what the privilege is for (omitted ifobject-typeis ‘universe’) (may be omitted ornilif the intent is to grant for all objects of the same type);optionsis a list inside braces, for example{if_not_exists=true|false}(usually omitted because the default is acceptable).All updates of user privileges are reflected immediately in the existing sessions and objects, e.g. functions.
Example for granting many privileges at once
In this example an ‘admin’ user grants many privileges on many objects to user ‘U’, using a single request.
box.schema.user.grant('U','read,write,execute,create,drop','universe')
Examples for granting privileges for specific operations
In these examples an administrator grants strictly the minimal privileges necessary for particular operations, to user ‘U’.
-- So that 'U' can create spaces:
box.schema.user.grant('U','create','space')
box.schema.user.grant('U','write', 'space', '_schema')
box.schema.user.grant('U','write', 'space', '_space')
-- So that 'U' can create indexes on space 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')
-- So that 'U' can alter indexes on space T (assuming 'U' did not create the index)
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')
-- So that 'U' can alter indexes on space T (assuming 'U' created the index)
box.schema.user.grant('U','read','space','_space_sequence')
box.schema.user.grant('U','read,write','space','_index')
-- So that 'U' can create users:
box.schema.user.grant('U','create','user')
box.schema.user.grant('U', 'read,write', 'space', '_user')
box.schema.user.grant('U', 'write', 'space', '_priv')
-- So that 'U' can create roles:
box.schema.user.grant('U','create','role')
box.schema.user.grant('U', 'read,write', 'space', '_user')
box.schema.user.grant('U', 'write', 'space', '_priv')
-- So that 'U' can create sequence generators:
box.schema.user.grant('U','create','sequence')
box.schema.user.grant('U', 'read,write', 'space', '_sequence')
-- So that 'U' can create functions:
box.schema.user.grant('U','create','function')
box.schema.user.grant('U','read,write','space','_func')
-- So that 'U' can create any object of any type
box.schema.user.grant('U','read,write,create','universe')
-- So that 'U' can grant access on objects that 'U' created
box.schema.user.grant('U','write','space','_priv')
-- So that 'U' can select or get from a space named 'T'
box.schema.user.grant('U','read','space','T')
-- So that 'U' can update or insert or delete or truncate a space named 'T'
box.schema.user.grant('U','write','space','T')
-- So that 'U' can execute a function named 'F'
box.schema.user.grant('U','execute','function','F')
-- So that 'U' can use the "S:next()" function with a sequence named S
box.schema.user.grant('U','read,write','sequence','S')
-- So that 'U' can use the "S:set()" or "S:reset() function with a sequence named S
box.schema.user.grant('U','write','sequence','S')
-- So that 'U' can drop a sequence (assuming 'U' did not create it)
box.schema.user.grant('U','drop','sequence')
box.schema.user.grant('U','write','space','_sequence_data')
box.schema.user.grant('U','write','space','_sequence')
-- So that 'U' can drop a function (assuming 'U' did not create it)
box.schema.user.grant('U','drop','function')
box.schema.user.grant('U','write','space','_func')
-- So that 'U' can drop a space that has some associated objects
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')
-- So that 'U' can drop any space (ignore if the privilege exists already)
box.schema.user.grant('U','drop','space',nil,{if_not_exists=true})
Example for creating users and objects then granting privileges
Here a Lua function is created that will be executed under the user ID of its creator, even if called by another user.
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')
A role is a container for privileges which can be granted to regular users. Instead of granting or revoking individual privileges, you can put all the privileges in a role and then grant or revoke the role.
Role information is stored in the _user space, but the third field in the tuple – the type field – is ‘role’ rather than ‘user’.
An important feature in role management is that roles can be nested. For example, role R1 can be granted a privileged “role R2”, so users with the role R1 will subsequently get all privileges from both roles R1 and R2. In other words, a user gets all the privileges granted to a user’s roles, directly or indirectly.
There are actually two ways to grant or revoke a role:
box.schema.user.grant-or-revoke(user-name-or-role-name,'execute', 'role',role-name...)
or
box.schema.user.grant-or-revoke(user-name-or-role-name,role-name...).
The second way is preferable.
The ‘usage’ and ‘session’ privileges cannot be granted to roles.
Example
-- This example will work for a user with many privileges, such as 'admin'
-- or a user with the pre-defined 'super' role
-- Create space T with a primary index
box.schema.space.create('T')
box.space.T:create_index('primary', {})
-- Create the user U1 so that later the current user can be changed to U1
box.schema.user.create('U1')
-- Create two roles, R1 and R2
box.schema.role.create('R1')
box.schema.role.create('R2')
-- Grant role R2 to role R1 and role R1 to user U1 (order doesn't matter)
-- There are two ways to grant a role; here the shorter way is used
box.schema.role.grant('R1', 'R2')
box.schema.user.grant('U1', 'R1')
-- Grant read/write privileges for space T to role R2
-- (but not to role R1, and not to user U1)
box.schema.role.grant('R2', 'read,write', 'space', 'T')
-- Change the current user to user U1
box.session.su('U1')
-- An insertion to space T will now succeed because (due to nested roles)
-- user U1 has write privilege on space T
box.space.T:insert{1}
More details are to be found in box.schema.user.grant() and box.schema.role.grant() in the built-in modules reference.
A session is the state of a connection to Tarantool. It contains:
- An integer ID identifying the connection,
- the current user associated with the connection,
- text description of the connected peer, and
- session local state, such as Lua variables and functions.
In Tarantool, a single session can execute multiple concurrent transactions. Each transaction is identified by a unique integer ID, which can be queried at start of the transaction using box.session.sync().
Note
To track all connects and disconnects, you can use connection and authentication triggers.
This section provides a list of the minimum required privileges for the following typical Tarantool usage scenarios.
Additional limitations and recommendations related to upgrading the crud module are described in the Upgrading the crud module section.
CRUD passes the name of the user who initiated the operation on the router to the storage side. The internal call is performed via the vshard service user, after which CRUD switches to the forwarded user and executes the operation with that user’s privileges.
For the correct operation of CRUD methods, the user on behalf of whom requests are made to the cluster through the router must be granted a minimum set of privileges on each storage instance.
Starting from crud 1.6.0, grant the user the required privileges on the user spaces and, if applicable, on the DDL sharding metadata spaces.
Starting from crud 1.7.0, also grant the user the read privilege
on the _bucket space.
It is not recommended to grant an application user the execute privilege on universe,
because it allows executing arbitrary Lua code and significantly broadens the user’s permissions.
On the router, register only the required CRUD functions and grant the application user permission to execute them:
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
The minimum set of privileges on a storage instance depends on the enabled functionality and the CRUD version.
To read and write data, the user must have read and write access to the target user spaces, for example, the bands space:
box.schema.user.grant('db_user', 'read,write', 'space', 'bands')
Access to routing-related system spaces
Read access to
_bucket— starting from CRUD 1.7.0, storage-side operationsbucket_ref/bucket_unrefrequire it to verify the bucket state and ownership.If DDL-based routing metadata is used,
readaccess to_ddl_sharding_keyand_ddl_sharding_funcis required.Note
Privileges for DDL sharding metadata are required only in configurations where CRUD routing relies on metadata stored in DDL.
CRUD loads sharding metadata from the system spaces
_ddl_sharding_keyand_ddl_sharding_func:_ddl_sharding_key— sharding key metadata;_ddl_sharding_func— user-defined sharding function metadata.
If the cluster uses a user-defined sharding function, you need to grant read access to both spaces.
box.schema.user.grant('db_user', 'read', 'space', '_ddl_sharding_key') box.schema.user.grant('db_user', 'read', 'space', '_ddl_sharding_func')
Example:
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')
In some configurations (in particular, on Tarantool 2.x or when privileges are configured manually), internal calls on
storage may require execute privileges for a set of service functions vshard.storage.*.
Note
These execute privileges are required for the vshard service user (or for the ``sharding`` role, if it is
used in the cluster), not for the application user (for example, db_user).
The application user must have:
- router-side privileges to call the required
crud.*methods; - storage-side privileges for the required spaces (user spaces and, if applicable, routing metadata spaces such as
_bucketand DDL metadata spaces).
Granting the execute privilege on universe to the application user is not recommended.
Note
Without these privileges, the user will get an access error when executing CRUD operations.
The error can occur either on the router side or on the storage side, depending on which component lacks the required permissions.
For example, the error may be caused by missing access to routing metadata,
missing read / write privileges for the target user space, or missing execute privileges to call the required crud.* methods on the router.