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}