Skip to content
Snippets Groups Projects
authentication.rst 13.67 KiB

Authentication and authorization

Understanding the details of security is primarily an issue for administrators, but ordinary users should at least skim this section so that they will have an idea of 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 space where user names and password-hashes are stored. There are functions for saying that certain users are allowed to do certain things ("privileges"). There is a _priv 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").

Passwords

Each user may have a password. The password is any alphanumeric string. Administrators should advise users to choose long unobvious passwords, but it is ultimately up to the users to choose or change their own passwords.

Tarantool passwords are stored in the _user space with a `Cryptographic hash function`_ so that, if the password is 'x', the stored hashed-password is a long string like 'lL3OvhkIPOKh+Vn9Avlkx69M/Ck='. When a client connects to a Tarantool server, the server sends a random `Salt Value`_ which the client must mix with the hashed-password before sending to the server. Thus the original value 'x' is never stored anywhere except in the user's head, and the hashed value is never passed down a network wire except when mixed with a random salt. This system prevents malicious onlookers from finding passwords by snooping in the log files or snooping on the wire. It is the same system that `MySQL introduced several years ago`_ which has proved adequate for medium-security installations. Nevertheless administrators should warn users that no system is foolproof against determined long-term attacks, so passwords should be guarded and changed occasionally.

Users and the _user space

The fields in the _user space are: the numeric id of the tuple, the numeric id of the tuple's creator, the user name, the type, and the optional password.

There are three special tuples in the _user space: 'guest', 'admin', and 'public'.

Name ID Type Description
guest 0 user Default when connecting remotely. Usually an untrusted user with few privileges.
admin 1 user Default when using tarantool as a console. Usually an administrative user with all privileges.
public 2 role Not a user in the usual sense. Described later in section Roles.

To select a row from the _user space, use box.space._user:select. For example, here is what happens with a select for user id = 0, which is the 'guest' user, which by default has no password:

tarantool> box.space._user:select{0}
---
- - [0, 1, 'guest', 'user']
...

To change tuples in the _user space, do not use ordinary box.space functions for insert or update or delete - the _user space is special so there are special functions which have appropriate error checking.

To create a new user, say box.schema.user.create(user-name) or box.schema.user.create(user-name, {password=password}). The form box.schema.user.create(user-name, {password=password}) is better because in a `URI`_ (Uniform Resource Identifier) it is usually illegal to include a user-name without a password.

To change the current user's password, say box.schema.user.passwd(password).

To change a different user's password, say box.schema.user.passwd(user-name, password). (Only the admin user can perform this function.)

To drop a user, say box.schema.user.drop(user-name).

To check whether a user exists, say box.schema.user.exists(user-name), which returns true or false.

For example, here is a session which creates a new user with a strong password, selects a tuple in the _user space, and then drops the user.

tarantool> box.schema.user.create('JeanMartin', {password = 'Iwtso_6_os$$'})
---
...

tarantool> box.space._user.index.name:select{'JeanMartin'}
---
- - [17, 1, 'JeanMartin', 'user', {'chap-sha1': 't3xjUpQdrt857O+YRvGbMY5py8Q='}]
...

tarantool> box.schema.user.drop('JeanMartin')
---
...

Note

The maximum number of users is 32.

Priveleges and _priv space

The fields in the _priv space are: the numeric id of the user who gave the privilege ("grantor_id"), the numeric id of the user who received the privilege ("grantee_id"), the id of the object, the type of object - "space" or "function" or "universe", the type of operation - "read" or "write" or "execute" or a combination such as "read,write,execute".

The function for granting a privilege is: box.schema.user.grant(user-name-of-grantee, operation-type, object-type, object-name) or box.schema.user.grant(user-name-of-grantee, operation-type, 'universe').

The function for revoking a privilege is: box.schema.user.revoke(user-name-of-grantee, operation-type, object-type, object-name) or box.schema.user.revoke(user-name-of-grantee, operation-type, 'universe').

For example, here is a session where the admin user gave the guest user the privilege to read from a space named space55, and then took the privilege away:

tarantool> box.schema.user.grant('guest', 'read', 'space', 'space55')
---
...
tarantool> box.schema.user.revoke('guest', 'read', 'space', 'space55')
---
...

Note

Generally privileges are granted or revoked by the owner of the object (the user who created it), or by the 'admin' user. Before dropping any objects or users, steps should be taken to ensure that all their associated privileges have been revoked. Only the 'admin' user can grant privileges for the 'universe'.

Functions and _func space

The fields in the _func space are: the numeric function id, a number, and the function name.

The _func space does not include the function's body. One continues to create Lua functions in the usual way, by saying "function function_name () ... end", without adding anything in the _func space. The _func space only exists for storing function tuples so that their names can be used within grant/revoke functions.

The function for creating a _func tuple is: box.schema.func.create(function-name).

The function for dropping a _func tuple is: box.schema.func.drop(function-name).

The function for checking whether a _func tuple exists is: box.schema.func.exists(function-name).

In the following example, a function named 'f7' is created, then it is put in the _func space, then it is used in a box.schema.user.grant function, then it is dropped:

tarantool> function f7() box.session.uid() end
---
...
tarantool> box.schema.func.create('f7')
---
...
tarantool> box.schema.user.grant('guest', 'execute', 'function', 'f7')
---
...
tarantool> box.schema.user.revoke('guest', 'execute', 'function', 'f7')
---
...
tarantool> box.schema.func.drop('f7')
---
...

box.session and security

After a connection has taken place, the user has access to a "session" object which has several functions. The ones which are of interest for security purposes are:

box.session.uid()         -- returns the id of the current user
box.session.user()        -- returns the name of the current user
box.session.su(user-name) -- allows changing current user to 'user-name'

If a user types requests directly on the Tarantool server in its interactive mode, or if a user connects via telnet to the administrative port (using `admin`_ instead of listen), then the user by default is 'admin' and has many privileges. If a user connects from an application program via one of the `connectors`_, then the user by default is 'guest' and has few privileges. Typically an admin user will set up and configure objects, then grant privileges to appropriate non-admin users. Typically a guest user will use box.session.su() to change into a non-generic user to whom admin has granted more than the default privileges. For example, admin might say:

box.space._user:insert{123456,0,'manager'}
box.schema.user.grant('manager', 'read', 'space', '_space')
box.schema.user.grant('manager', 'read', 'space', 'payroll')

and later a guest user, who wishes to see the payroll, might say:

box.session.su('manager')
box.space.payroll:select{'Jones'}

Roles

A role is a container for privileges which can be granted to regular users. Instead of granting and revoking individual privileges, one can put all the privileges in a role and then grant or revoke the role. Role information is in the _user space but the third field - the type field - is 'role' rather than 'user'.

If a role R1 is granted a privilege X, and user U1 is granted a privilege "role R1", then user U1 in effect has privilege X. Then if a role R2 is granted a privilege Y, and role R1 is granted a privilege "role R2", then user U1 in effect has both privilege X and privilege Y. In other words, a user gets all the privileges that are granted to a user's roles, directly or indirectly.

There is one predefined role, named 'public', which is automatically assigned to new users when they are created with box.schema.user.create(user-name). Therefore a convenient way to grant 'read' on space 't' to every user that will ever exist is:

box.schema.role.grant('public','read','space','t').

Example

In this example, a new user named U1 will insert a new tuple into a new space named T, and will succeed even though user U1 has no direct privilege to do such an insert -- that privilege is inherited from role R1, which in turn inherits from role R2.

-- This example will work for a user with many privileges, such as 'admin'
box.schema.space.create('T')
box.space.T:create_index('primary',{})
-- Create a user U1 so that later it's possible to say box.session.su('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 U1 (order doesn't matter)
box.schema.role.grant('R1','execute','role','R2')
box.schema.role.grant('U1','execute','role','R1')
-- Grant read and execute privileges to R2 (but not to R1 and not to U1)
box.schema.role.grant('R2','read,write','space','T')
box.schema.role.grant('R2','execute','universe')
-- Use box.session.su to say "now become user U1"
box.session.su('U1')
-- The following insert succeeds because U1 in effect has write privilege on T
box.space.T:insert{1}