Access control
Tarantool enables flexible management of access to various database resources. The main concepts of Tarantool access control system are as follows:
- A user is a person or program that interacts with a Tarantool instance.
- An object is an entity to which access can be granted, for example, a space, an index, or a function.
- A privilege allows a user to perform certain operations on specific objects, for example, creating spaces, reading or updating data.
- A role is a named collection of privileges that can be granted to a user.
A user identifies a person or program that interacts with a Tarantool instance. There might be different types of users, for example:
- A database administrator responsible for the overall management and administration of a database. An administrator can create other users and grant them specified privileges.
- A user with limited access to certain data and stored functions. Such users can get their privileges from the database administrator.
- Users used in communications between Tarantool instances. For example, such users can be created to maintain replication and sharding in a Tarantool cluster.
There are two built-in users in Tarantool:
adminis a user with all available administrative privileges. If the connection uses an admin-console port, the current user isadmin. For example,adminis used when connecting to an instance using tt connect locally using the instance name:$ tt connect app:instance001
To allow remote binary port connections using the
adminuser, you need to set a password.guestis a user with minimum privileges used by default for remote binary port connections. For example,guestis used when connecting to an instance using tt connect using the IP address and port without specifying the name of a user:$ tt connect 192.168.10.10:3301
Warning
Given that the
guestuser allows unauthenticated access to Tarantool instances, it is not recommended to grant additional privileges to this user. For example, granting theexecuteaccess to universe allows remote code execution on instances.
Note
Information about users is stored in the _user space.
Any user (except guest) may have a password.
If a password is not set, a user cannot connect to Tarantool instances.
Tarantool password hashes are stored in the _user system space.
By default, Tarantool uses the CHAP protocol to authenticate users and applies SHA-1 hashing to
passwords.
So, if the password is ‘123456’, the stored hash is a string like ‘a7SDfrdDKRBe5FaN2n3GftLKKtk=’.
In the Enterprise Edition, you can enable PAP authentication with the SHA256 hashing algorithm.
Tarantool Enterprise Edition 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 Authentication topic.
An object is a securable entity to which access can be granted. Tarantool has a number of objects that enable flexible management of access to data, stored functions, specific actions, and so on.
Below are a few examples of objects:
universerepresents a database (box.schema) that contains database objects, including spaces, indexes, users, roles, sequences, and functions. Granting privileges touniversegives a user access to any object in a database.spaceenables granting privileges to user-created or system spaces.functionenables granting privileges to functions.
Note
The full list of object types is available in the Object types section.
The privileges granted to a user determine which operations the user can perform, for example:
- The
readandwritepermissions granted to thespaceobject allow a user to read or modify data in the specified space. - The
createpermission granted to thespaceobject allows a user to create new spaces. - The
executepermission granted to thefunctionobject allows a user to execute the specified function. - The
sessionpermission granted to a user allows connecting to an instance over IPROTO.
Note that some privileges might require read and write access to certain system spaces.
For example, the create permission granted to the space object requires read and write permissions to the _space system space.
Similarly, granting the ability to create functions requires read and write access to the _func space.
Note
Information about privileges is stored in the _priv space.
A role is a container for privileges that can be granted to users. Roles can also be assigned to other roles, creating a role hierarchy.
There are the following built-in roles in Tarantool:
superhas all available administrative permissions.publichas certain read permissions. This role is automatically granted to new users when they are created.replicationcan be granted to a user used to maintain replication in a cluster.shardingcan be granted to a user used to maintain sharding in a cluster.Note
The
shardingrole is created only if an instance is managed using YAML configuration.
Below are a few diagrams that demonstrate how privileges can be granted to a user without and with using roles.
In this example, a user gets privileges directly without using roles.
user1 ── privilege1 ├─── privilege2 └─── privilege3In this example, a user gets all privileges provided by
role1and specific privileges assigned directly.user1 ── role1 ── privilege1 │ └─── privilege2 ├─── privilege3 └─── privilege4In this example,
role2is granted torole1. This means that a user withrole1subsequently gets all privileges from both rolesrole1androle2.user1 ── role1 ── privilege1 │ ├─── privilege2 │ └─── role2 │ ├─── privilege3 │ └─── privilege4 ├─── privilege5 └─── privilege6
Note
Information about roles is stored in the _user space.
An owner of a database object is the user who created it.
The owner of the database and the owner of objects that are created initially (the system spaces and the default users) is the admin user.
Owners automatically have privileges for objects they create.
They can share these privileges with other users or roles using box.schema.user.grant() and box.schema.role.grant().
Note
Information about users who gave the specified privileges is stored in the _priv space.
A session is the state of a connection to Tarantool. The session contains:
- An integer ID identifying the connection.
- The current user associated with the connection.
- The text description of the connected peer.
- A session’s 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 the start of the transaction using box.session.sync().
Note
To track all connects and disconnects, you can use connection and authentication triggers.
To create a new user, call box.schema.user.create(). In the example below, a user is created without a password:
box.schema.user.create('testuser')
In this example, the password is specified in the options parameter:
box.schema.user.create('testuser', { password = 'foobar' })
To set or change a user’s password, use box.schema.user.passwd(). In the example below, a user password is set for a currently logged-in user:
box.schema.user.passwd('foobar')
To set the password for the specified user, pass a username and password as shown below:
box.schema.user.passwd('testuser', 'foobar')
Note
box.schema.user.password() returns a hash of the specified password.
To grant the specified privileges to a user, use the box.schema.user.grant() function.
In the example below, testuser gets read permissions to the writers space and read/write permissions to the books space:
box.schema.user.grant('testuser', 'read', 'space', 'writers')
box.schema.user.grant('testuser', 'read,write', 'space', 'books')
Learn more about granting privileges to different types of objects from Granting privileges.
To check whether the specified user exists, call box.schema.user.exists():
box.schema.user.exists('testuser')
--[[
- true
--]]
To get information about privileges granted to a user, call box.schema.user.info():
box.schema.user.info('testuser')
--[[
- - - execute
- role
- public
- - read
- space
- writers
- - read,write
- space
- books
- - session,usage
- universe
-
- - alter
- user
- testuser
--]]
In the example above, testuser has the following privileges:
- The
executepermission to thepublicrole means that this role is assigned to the user. - The
readpermission to thewritersspace means that the user can read data from this space. - The
read,writepermissions to thebooksspace mean that the user can read and modify data in this space. - The
session,usagepermissions touniversemean the following:session: the user can authenticate over an IPROTO connection.usage: lets the user use their privileges on database objects (for example, read and modify data in a space).
- The
alterpermission letstestusermodify its own settings, for example, a password.
To revoke the specified privileges, use the box.schema.user.revoke() function.
In the example below, write access to the books space is revoked:
box.schema.user.revoke('testuser', 'write', 'space', 'books')
Revoking the session permission to universe can be used to disallow a user to connect to a Tarantool instance:
box.schema.user.revoke('testuser', 'session', 'universe')
The current user name can be found using box.session.user().
box.session.user()
--[[
- admin
--]]
The current user can be changed:
For an admin-console connection: using box.session.su():
box.session.su('testuser') box.session.user() --[[ - testuser --]]
For a binary port connection: using the AUTH protocol command, supported by most clients.
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.
To create a new role, call box.schema.role.create(). In the example below, two roles are created:
box.schema.role.create('books_space_manager')
box.schema.role.create('writers_space_reader')
To grant the specified privileges to a role, use the box.schema.role.grant() function.
In the example below, the books_space_manager role gets read and write permissions to the books space:
box.schema.role.grant('books_space_manager', 'read,write', 'space', 'books')
The writers_space_reader role gets read permissions to the writers space:
box.schema.role.grant('writers_space_reader', 'read', 'space', 'writers')
Learn more about granting privileges to different types of objects from Granting privileges.
Note
Not all privileges can be granted to roles. Learn more from Permissions.
Roles can be assigned to other roles.
In the example below, the newly created all_spaces_manager role gets all privileges granted to books_space_manager and writers_space_reader:
box.schema.role.create('all_spaces_manager')
box.schema.role.grant('all_spaces_manager', 'books_space_manager')
box.schema.role.grant('all_spaces_manager', 'writers_space_reader')
To grant the specified role to a user, use the box.schema.user.grant() function.
In the example below, testuser gets privileges granted to the books_space_manager and writers_space_reader roles:
box.schema.user.grant('testuser', 'books_space_manager')
box.schema.user.grant('testuser', 'writers_space_reader')
To check whether the specified role exists, call box.schema.role.exists():
box.schema.role.exists('books_space_manager')
--[[
- true
--]]
To get information about privileges granted to a role, call box.schema.role.info():
box.schema.role.info('books_space_manager')
--[[
- - - read,write
- space
- books
--]]
If a role has the execute permission to other roles, this means that these roles are granted to this parent role:
box.schema.role.info('all_spaces_manager')
--[[
- - - execute
- role
- books_space_manager
- - execute
- role
- writers_space_reader
--]]
To revoke the specified role from a user, revoke the execute privilege for this role using the box.schema.user.revoke() function.
In the example below, the books_space_reader role is revoked from testuser:
box.schema.user.revoke('testuser', 'execute', 'role', 'writers_space_reader')
To revoke role’s privileges, use box.schema.role.revoke().
To drop the specified role, call box.schema.role.drop():
box.schema.role.drop('writers_space_reader')
To grant the specified privileges to a user or role, use the box.schema.user.grant() and box.schema.role.grant() functions,
which have similar signatures and accept the same set of arguments.
For example, the box.schema.user.grant() signature looks as follows:
box.schema.user.grant(username, permissions, object-type, object-name[, {options}])
username: the name of the user that gets the specified privileges.permissions: a string value that represents permissions granted to the user. If there are several permissions, they should be separated by commas without a space.object-type: a type of object to which permissions are granted.object-name: the name of the object to which permissions are granted. An empty string ("") ornilprovided instead ofobject-namegrants the specified permissions to all objects of the specified type.Note
object-nameis ignored for the following combinations of permissions and object types:- Any permission granted to
universe. - The
createanddroppermissions for the following object types:user,role,space,function,sequence. - The
executepermission for the following object types:lua_eval,lua_call,sql.
- Any permission granted to
In the example below, testuser gets privileges allowing them to create any object of any type:
box.schema.user.grant('testuser','read,write,create','universe')
In this example, testuser can grant access to objects that testuser created:
box.schema.user.grant('testuser','write','space','_priv')
In the example below, testuser gets privileges allowing them to create spaces:
box.schema.user.grant('testuser','create','space')
box.schema.user.grant('testuser','write', 'space', '_schema')
box.schema.user.grant('testuser','write', 'space', '_space')
As you can see, the ability to create spaces also requires write access to certain system spaces.
To allow testuser to drop a space that has associated objects, add the following privileges:
box.schema.user.grant('testuser','create,drop','space')
box.schema.user.grant('testuser','write','space','_schema')
box.schema.user.grant('testuser','write','space','_space')
box.schema.user.grant('testuser','write','space','_space_sequence')
box.schema.user.grant('testuser','read','space','_trigger')
box.schema.user.grant('testuser','read','space','_fk_constraint')
box.schema.user.grant('testuser','read','space','_ck_constraint')
box.schema.user.grant('testuser','read','space','_func_index')
In the example below, testuser gets privileges allowing them to create indexes in the ‘writers’ space:
box.schema.user.grant('testuser','create,read','space','writers')
box.schema.user.grant('testuser','read,write','space','_space_sequence')
box.schema.user.grant('testuser','write', 'space', '_index')
To allow testuser to alter indexes in the writers space, grant the privileges below.
This example assumes that indexes in the writers space are not created by testuser.
box.schema.user.grant('testuser','alter','space','writers')
box.schema.user.grant('testuser','read','space','_space')
box.schema.user.grant('testuser','read','space','_index')
box.schema.user.grant('testuser','read','space','_space_sequence')
box.schema.user.grant('testuser','write','space','_index')
If testuser created indexes in the writers space, granting the following privileges is enough to alter indexes:
box.schema.user.grant('testuser','read','space','_space_sequence')
box.schema.user.grant('testuser','read,write','space','_index')
In this example, testuser gets privileges allowing them to select data from the ‘writers’ space:
box.schema.user.grant('testuser','read','space','writers')
In this example, testuser is allowed to read and modify data in the ‘books’ space:
box.schema.user.grant('testuser','read,write','space','books')
In this example, testuser gets privileges to create sequence generators:
box.schema.user.grant('testuser','create','sequence')
box.schema.user.grant('testuser', 'read,write', 'space', '_sequence')
To let testuser drop a sequence, grant them the following privileges:
box.schema.user.grant('testuser','drop','sequence')
box.schema.user.grant('testuser','write','space','_sequence_data')
box.schema.user.grant('testuser','write','space','_sequence')
In this example, testuser is allowed to use the id_seq:next() function with a sequence named ‘id_seq’:
box.schema.user.grant('testuser','read,write','sequence','id_seq')
In the next example, testuser is allowed to use the id_seq:set() or id_seq:reset() functions with a sequence named ‘id_seq’:
box.schema.user.grant('testuser','write','sequence','id_seq')
In this example, testuser gets privileges to create functions:
box.schema.user.grant('testuser','create','function')
box.schema.user.grant('testuser','read,write','space','_func')
To let testuser drop a function, grant them the following privileges:
box.schema.user.grant('testuser','drop','function')
box.schema.user.grant('testuser','write','space','_func')
To give the ability to execute a function named ‘sum’, grant the following privileges:
box.schema.user.grant('testuser','execute','function','sum')
Granting the ‘execute’ privilege on lua_call permits the user to call any global (accessible via the _G Lua table)
user-defined Lua function with the IPROTO_CALL request. To grant permission to any non-persistent function, you need to
specify its name when granting the lua_call privilege.
Note
The function doesn’t need to be defined at the time privileges are granted, meaning that the access to the function will be provided for the user once this function is defined.
function my_func_1() end
function my_func_2() end
box.cfg({listen = 3301})
box.schema.user.create('alice', {password = 'secret'})
conn = require('net.box').connect(box.cfg.listen, {user = 'alice', password = 'secret'})
box.schema.user.grant('alice', 'execute', 'lua_call', 'my_func_1')
conn:call('my_func_1') -- ok
conn:call('my_func_2') -- access denied
box.schema.user.grant('alice', 'execute', 'lua_call', 'box.session.su')
conn:call('box.session.su', {'admin'}) -- ok
In this example, testuser gets privileges to create other users:
box.schema.user.grant('testuser','create','user')
box.schema.user.grant('testuser', 'read,write', 'space', '_user')
box.schema.user.grant('testuser', 'write', 'space', '_priv')
To let testuser create new roles, grant the following privileges:
box.schema.user.grant('testuser','create','role')
box.schema.user.grant('testuser', 'read,write', 'space', '_user')
box.schema.user.grant('testuser', 'write', 'space', '_priv')
To let testuser execute Lua code, grant the execute privilege to the lua_eval object:
box.schema.user.grant('testuser','execute','lua_eval')
Similarly, executing an arbitrary SQL expression requires the execute privilege to the sql object:
box.schema.user.grant('testuser','execute','sql')
In the example below, the created Lua function is executed on behalf of its creator, even if called by another user.
First, the two spaces (space1 and space2) are created, and a no-password user (private_user)
is granted full access to them. Then read_and_modify is defined and private_user becomes this function’s creator.
Finally, another user (public_user) is granted access to execute Lua functions created by private_user.
box.schema.space.create('space1')
box.schema.space.create('space2')
box.space.space1:create_index('pk')
box.space.space2:create_index('pk')
box.schema.user.create('private_user')
box.schema.user.grant('private_user', 'read,write', 'space', 'space1')
box.schema.user.grant('private_user', 'read,write', 'space', 'space2')
box.schema.user.grant('private_user', 'create', 'universe')
box.schema.user.grant('private_user', 'read,write', 'space', '_func')
function read_and_modify(key)
local space1 = box.space.space1
local space2 = box.space.space2
local fiber = require('fiber')
local t = space1:get{key}
if t ~= nil then
space1:put{key, box.session.uid()}
space2:put{key, fiber.time()}
end
end
box.session.su('private_user')
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')
Whenever public_user calls the function, it is executed on behalf of its creator, private_user.
| Object type | Description |
|---|---|
universe |
A database (box.schema) that contains database objects, including spaces, indexes, users, roles, sequences, and functions. Granting privileges to universe gives a user access to any object in the database. |
user |
A user. |
role |
A role. |
space |
A space. |
function |
A function. |
sequence |
A sequence. |
lua_eval |
Executing arbitrary Lua code. |
lua_call |
Calling any global user-defined Lua function. |
sql |
Executing an arbitrary SQL expression. |
| Permission | Object type | Granted to roles | Description |
|---|---|---|---|
read |
All | Yes | Allows reading data of the specified object. For example, this permission can be used to allow a user to select data from the specified space. |
write |
All | Yes | Allows updating data of the specified object. For example, this permission can be used to allow a user to modify data in the specified space. |
create |
All | Yes | Allows creating objects of the specified type. For example, this permission can be used to allow a user to create new spaces. Note that this permission requires read and write access to certain system spaces. |
alter |
All | Yes | Allows altering objects of the specified type. Note that this permission requires read and write access to certain system spaces. |
drop |
All | Yes | Allows dropping objects of the specified type. Note that this permission requires read and write access to certain system spaces. |
execute |
role, universe, function, lua_eval, lua_call, sql |
Yes | For role, allows using the specified role.
For other object types, allows calling a function. |
session |
universe |
No | Allows a user to connect to an instance over IPROTO. |
usage |
universe |
No | Allows a user to use their privileges on database objects (for example, read, write, and alter spaces). |
| Object type | Details |
|---|---|
universe |
|
user |
|
role |
|
space |
|
function |
|
sequence |
|
lua_eval |
|
lua_call |
|
sql |
|