Database Security

OrientDB uses a security model based on the concepts of users and roles. That is, a database has its own users. Each User has one or more roles. Roles are a combination of the working mode and a set of permissions and security policies.

Security overview

For more information on security, see:

Users

A user is an actor on the database. When you open a database, you need to specify the user name and the password to use. Each user has its own credentials and permissions.

By convention, each time you create a new database OrientDB creates three default users. The passwords for these users are the same as the usernames. That is, by default the admin user has a password of admin.

  • admin This user has access to all functions on the database without limitation.
  • reader This user is a read-only user. The reader can query any records in the database, but can't modify or delete them. It has no access to internal information, such as the users and roles themselves.
  • writer This user is the same as the user reader, but it can also create, update and delete records.

The users themselves are records stored inside the cluster ouser. OrientDB stores passwords in hash. From version 2.2 on, OrientDB uses the PBKDF2 algorithm. Prior releases relied on SHA-256. For more information on passwords, see Password Management.

OrientDB stores the user status in the field status. It can either be SUSPENDED or ACTIVE. Only ACTIVE users can log in.

Working with Users

When you are connected to a database, you can query the current users on the database by using SELECT queries on the OUser class.

orientdb> SELECT RID, name, status FROM OUser

---+--------+--------+--------
#  | @CLASS | name   | status
---+--------+--------+--------
0  | null   | admin  | ACTIVE
1  | null   | reader | ACTIVE
2  | null   | writer | ACTIVE
---+--------+--------+--------
3 item(s) found. Query executed in 0.005 sec(s).

Creating a New User

To create a new user, use the INSERT command. Remember in doing so, that you must set the status to ACTIVE and give it a valid role.

orientdb> INSERT INTO OUser SET name = 'jay', password = 'JaY', status = 'ACTIVE',
          roles = (SELECT FROM ORole WHERE name = 'reader')

Updating Users

You can change the name for the user with the UPDATE statement:

orientdb> UPDATE OUser SET name = 'jay' WHERE name = 'reader'

In the same way, you can also change the password for the user:

orientdb> UPDATE OUser SET password = 'hello' WHERE name = 'reader'

OrientDB saves the password in a hash format. The trigger OUserTrigger encrypts the password transparently before it saves the record.

Disabling Users

To disable a user, use UPDATE to switch its status from ACTIVE to SUSPENDED. For instance, if you wanted to disable all users except for admin:

orientdb> UPDATE OUser SET status = 'SUSPENDED' WHERE name <> 'admin'

NOTE: In the event that, due to accident or database corruption, you lose the user admin and need to restore it on the database, see Restoring the admin User`.

Roles

A role determines what operations a user can perform against a resource.

Working with Roles

When you are connected to a database, you can query the current roles on the database using SELECT queries on the ORole class.

orientdb> SELECT RID, mode, name, policies, rules FROM ORole

+----+----+----+------+------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------+
|#   |RID |mode|name  |rules                                                                                     |policies                                                                                                             |
+----+----+----+------+------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------+
|0   |    |    |admin |{database=31, database.function=31, database.schema=31, database.command.gremlin=31, *=...|{*:#4:0}                                                                                                             |
|1   |    |    |reader|{database.cluster.internal=2, database.cluster.orole=0, database=2, database.function=2...|{database.class.*.*:#4:0, database:#4:1, database.schema:#4:1, database.cluster.internal:#4:1, database.cluster.or...|
|2   |    |    |writer|{database.cluster.internal=2, database.class.oschedule=2, database.class.osequence=2, d...|{database.class.*.*:#4:0, database:#4:1, database.schema:#4:3, database.cluster.internal:#4:1, database.cluster.or...|
+----+----+----+------+------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------+
3 item(s) found.  Query executed in 0.002 sec(s).

Creating New Roles

To create a new role, use the INSERT statement.

orientdb> INSERT INTO ORole SET name = 'developer'

Role Inheritance

Roles can inherit permissions from other roles in an object-oriented fashion. To let a role extend another, add the parent role in the inheritedRole attribute. For instance, say you want users with the role appuser to inherit settings from the role writer.

orientdb> UPDATE ORole SET inheritedRole = (SELECT FROM ORole WHERE name = 'writer')
          WHERE name = 'appuser'

Working with Modes (DEPRECATED in v 3.1)

IMPORTANT: in v 3.1 working modes do not apply anymore. The standard behaviour is Deny All But. Even explicitly configuring the role for Allow All, it won't change the behaviour.

Where rules determine what users belonging to certain roles can do on the databases, working modes determine how OrientDB interprets these rules. There are two types of working modes, designating by 1 and 0.

  • Allow All But (Rules) By default is the super user mode. Specify exceptions to this using the rules. If OrientDB finds no rules for a requested resource, then it allows the user to execute the operation. Use this mode mainly for power users and administrators. The default role admin uses this mode by default and has no exception rules. It is written as 1 in the database.

  • Deny All But (Rules) By default this mode allows nothing. Specify exceptions to this using the rules. If OrientDB finds rules for a requested resource, then it allows the user to execute the operation. Use this mode as the default for all classic users. The default roles reader and writer use this mode. It is written as 0 in the database.

Operations

The supported operations are the classic CRUD operations. That is, Create, Read, Update, Delete. Roles can have none of these permissions or all of them. OrientDB represents each permission internally by a 4-digit bitmask flag.

NONE:   #0000 - 0
CREATE: #0001 - 1
READ:   #0010 - 2
UPDATE: #0100 - 4
DELETE: #1000 - 8
ALL:    #1111 - 15

In addition to these base permissions, you can also combine them to create new permissions. For instance, say you want to allow only the Read and Update permissions:

READ:               #0010 - 2
UPDATE:             #0100 - 4
Permission to use:  #0110 - 6

Security Policies

Security policies are an enhanced version of a the Operations bitmap.

A security policy is a map made of operation-predicate pairs.

The "operation" (the map key) can be one of the following:

  • "CREATE"
  • "READ"
  • "BEFORE UPDATE"
  • "AFTER UPDATE"
  • "DELETE"
  • "EXECUTE"

The "predicate" is a normal SQL predicate, that can be a very simple one (eg. TRUE or FALSE) or a more structured condition (eg. "owner = $currentUser), that can also include subqueries and graph traversal.

Security policies are intended to be applied to single records in each phase of its lifecycle (ie. when it's created, read, before and after its update and so on) to define if that particular operation is allowed or not

Resources

Resources are strings bound to OrientDB concepts.

NOTE: Resource entries are case-sensitive.

  • database, checked on accessing to the database
  • database.class.<class-name>, checked on accessing on specific class
  • database.class.<class-name>.<property-name>, checked on accessing on specific property
  • database.cluster.<cluster-name>, checked on accessing on specific cluster
  • database.query, checked on query execution
  • database.command, checked on command execution
  • database.schema, checked to access to the schema
  • database.function, checked on function execution
  • database.config, checked on accessing at database configuration
  • database.hook.record
  • server.admin, checked on accessing to remote server administration

For instance, say you have a role motorcyclist that you want to have access to all classes except for the class Car.

orientdb> UPDATE ORole PUT rules = "database.class.*", 15 WHERE name = "motorcyclist"

orientdb> UPDATE ORole PUT rules = "database.class.Car", 0 WHERE name = "motorcyclist"

Granting and Revoking Permissions

To grant and revoke permissions from a role, use the GRANT and REVOKE commands.

Until v 3.0, the only way to manage permissions for a particular role, was to grant or revoke "cluster" permissions as follows

orientdb> GRANT UPDATE ON database.cluster.Car TO motorcyclist

In v 3.1, role permissions can be managed with security policies, eg.

orientdb> CREATE SECURITY POLICY readOnlyPolicy SET CREATE = (FALSE), READ = (TRUE), BEFORE UPDATE = (FALSE), DELETE = (FALSE)

orientdb> GRANT POLICY readOnlyPolicy ON database.class.Car TO aRoleName

Security policies can also be applied to single properties, eg. to hide a property to a particular role

orientdb> CREATE SECURITY POLICY cannotRead SET  READ = (FALSE)

orientdb> GRANT POLICY cannotRead ON database.class.Employee.salary TO basicRole

Record-level Security (DEPRECATED in v 3.1)

IMPORTANT: In v 3.1 the record level security is replaced by Predicate Security (see below).

The sections above manage security in a vertical fashion at the schema-level, but in OrientDB you can also manage security in a horizontal fashion, that is: per record. This allows you to completely separate database records as sandboxes, where only authorized users can access restricted records.

To active record-level security, create classes that extend the ORestricted super class. In the event that you are working with a Graph Database, set the V and E classes (that is, the vertex and edge classes) themselves to extend ORestricted.

orientdb> ALTER CLASS V SUPERCLASS ORestricted

orientdb> ALTER CLASS E SUPERCLASS ORestricted

This causes all vertices and edges to inherit the record-level security. Beginning with version 2.1, OrientDB allows you to use multiple inheritances, to cause only certain vertex or edge calsses to be restricted.

orientdb> CREATE CLASS Order EXTENDS V, ORestricted

Whenever a class extends the class ORestricted, OrientDB uses special fields to type-set _<OIdentifiable> to store authorization on each record.

  • _allow Contains the users that have full access to the record, (that is, all CRUD operations).
  • _allowRead Contains the users that can read the record.
  • _allowUpdate Contains the users that can update the record.
  • _allowDelete Contains the users that can delete the record.

To allow full control over a record to a user, add the user's RID to the _allow set. To provide only read permissions, use _allowRead. In the example below, you allow the user with the RID #5:10 to read record #43:22:

orientdb> UPDATE #43:22 ADD _allowRead #5:10

If you want to remove read permissions, use the following command:

orientdb> UPDATE #43:22 REMOVE _allowRead #5:10

Predicate Security

The sections above manage security in a vertical fashion at the schema-level, but in OrientDB you can also manage security in a horizontal fashion, that is: per record or even per single property in a record. This allows you to completely separate database records as sandboxes, where only authorized users can access restricted records.

To active Predicate Security, you have to define Security Policies and assign them to roles.

orientdb> CREATE SECURITY POLICY readYourRecords SET READ = (owner = $currentUser)
orientdb> GRANT readYourRecords ON database.class.Person TO aRoleName
orientdb> INSERT INTO Person SET name = 'John', owner = #6:15

In this case, when records of Person class are loaded (eg. with a SQL query), the predicate owner = $currentUser is evaluated on each record; if the result of the evaluation is TRUE, then the record will be made available to the user, otherwise it will be just hidden (it's completely transparent on the user level, the user will just not see the record).

Predicates can be applied to all the record lifecycle.

In case the policy is applied to a class resource (eg. database.class.ClassName):

  • CREATE predicate: is evaluated when a record is created, in case it returns FALSE, the user will have a security exception
  • READ predicate: is evaluated when the record is loaded, in case it returns FALSE, the record is not returned to the user
  • BEFORE UPDATE predicate: is evaluated during an UPDATE operation, passing the record in its original status (ie. before the modifications); in case it returns FALSE, the user will have a security exception
  • AFTER UPDATE predicate: is evaluated during an UPDATE operation, passing the record in its new status (ie. after the modifications); in case it returns FALSE, the user will have a security exception
  • DELETE predicate: is evaluated during a DELETE operation, passing the record in its status before it's deleted; in case it returns FALSE, the user will have a security exception

In case the policy is applied to a property resource (eg. database.class.ClassName.propertyName):

  • CREATE predicate: is evaluated when a record is created, in case it returns FALSE, the user will have a security exception
  • READ predicate: is evaluated when the record is loaded, in case it returns FALSE, the record is returned to the user, but WITHOUT the property. The user won't be allowed to overwrite the property either.
  • BEFORE UPDATE predicate: is evaluated during an UPDATE operation, passing the record in its original status (ie. before the modifications); in case it returns FALSE, the user will have a security exception
  • AFTER UPDATE predicate: is evaluated during an UPDATE operation, passing the record in its new status (ie. after the modifications); in case it returns FALSE, the user will have a security exception
  • DELETE predicate: is evaluated during a DELETE operation, passing the record in its status before it's deleted; in case it returns FALSE, the user will have a security exception

If you want to remove read permissions, use the following command:

orientdb> REVOKE POLICY ON database.class.Person FROM aRoleName

Run-time Checks

OrientDB checks record-level security using a hook that injects the check before each CRUD operation:

  • Create Documents: Sets the current database's user in the _allow field. To change this behavior, see Customize on Creation.
  • Read Documents: Checks if the current user, or its roles, are listed in the _allow or _allowRead fields. If not, OrientDB skips the record. This allows each query to work per user.
  • Update Documents: Checks if the current user, or its roles, are listed in the _allow or _allowUpdate field. If not, OrientDB raises an OSecurityException exception.
  • Delete Documents: Checks if the current user, or its roles, are listed in the _allow or _allowDelete field. If not, OrientDB raises an OSecurityException exception.

The allow fields, (that is, _allow, _allowRead, _allowUpdate, and _allowDelete) can contain instances of OUser and ORole records, as both classes extend OIdentity. Use the class OUser to allow single users and use the class ORole to allow all users that are a part of that role.

Using the API

In addition to managing record-level security features through the OrientDB console, you can also configure it through the Graph and Document API's.

  • Graph API

    OrientVertex v = graph.addVertex("class:Invoice");
    v.setProperty("amount", 1234567);
    graph.getRawGraph().getMetadata().getSecurity().allowUser(
          v.getRecord(), ORestrictedOperation.ALLOW_READ, "report");
    v.save();
    
  • Document API

    ODocument invoice = new ODocument("Invoice").field("amount", 1234567);
    database.getMetadata().getSecurity().allowUser(
          invoice, ORestrictedOperation.ALLOW_READ, "report");
    invoice.save();
    

Customize on Creation

By default, whenever you create a restricted record, (that is, create a class that extends the class ORestricted), OrientDB inserts the current user into the _allow field. You can change this using custom properties in the class schema:

  • onCreate.fields Specifies the names of the fields it sets. By default, these are _allow, but you can also specify _allowRead, _allowUpdate, _allowDelete or a combination of them as an alternative. Use commas to separate multiple fields.
  • onCreate.identityType Specifies whether to insert the user's object or its role (the first one). By default, it is set to user, but you can also set it to use its role.

For instance, say you wanted to prevent a user from deleting new posts:

orientdb> ALTER CLASS Post CUSTOM onCreate.fields=_allowRead,_allowUpdate

Consider another example, where you want to assign a role instead of a user to new instances of Post.

orientdb> ALTER CLASS Post CUSTOM onCreate.identityType=role

Bypassing Security Constraints

On occasion, you may need a role that can bypass restrictions, such as for backup or administrative operations. You can manage this through the special permission database.bypassRestricted, by changing its value to READ. By default, the role admin has this permission.

For security reasons, this permission is not inheritable. In the event that you need to assign it to other roles in your database, you need to set it on each role.

Using Security

Now that you have some familiarity with how security works in OrientDB, consider the use case of OrientDB serving as the database for a blog-like application. The blog is accessible through the web and you need to implement various security features to ensure that it works properly and does not grant its users access to restricted content.

To begin, the administrator connects to the database and creates the document class Post, which extends ORestricted. This ensures that users can only see their own entries in the blog and entries that are shared with them.

orientdb> CONNECT REMOTE:localhost/blog admin admin
orientdb> CREATE CLASS Post EXTENDS ORestricted

Class 'Post' created successfully.

The user Luke is registered in OUser as luke, with an RID of #5:5. He logs into the database and creates a new blog, which is an instance of the class Post.

orientdb> CONNECT REMOTE:localhost/blog luke lukepassword
orientdb> INSERT INTO Post SET title = "Yesterday in Italy"

Created document #18:0

orientdb> SELECT FROM Post

-------+--------+--------------------
 RID   | _allow | title 
-------+--------+--------------------
 #18:0 | [#5:5] | Yesterday in Italy
-------+--------+--------------------

Independent of the users admin and luke, there is the user Steve. Steve is registers with OUser as steve, he has an RID of #5:6. Steve logs into OrientDB and also creates a new entry on the class Post:

orientdb> CONNECT REMOTE:localhost/blog steve steve
orientdb> INSERT INTO Post SET title = "My Nutella Cake!"

Created document #18:1

orientdb> SELECT FROM Post

-------+--------+------------------
 RID   | _allow | title
-------+--------+------------------
 #18:1 | [#5:6] | My Nutella Cake!
-------+--------+------------------

As you can see, the users Steve and Luke can only see the records that they have access to. Now, after some editorial work, Luke is satisfied with the state of his blog entry Yesterday in Italy. He is now ready to share it with others. From the database console, he can do so by adding the user Steve's RID to the _allow field.

orientdb> UPDATE #18:0 ADD _allow = #5:6

Now, when Steve logs in, the same query from before gives him different results, since he can now see the content Luke shared with him.

orientdb> SELECT FROM Post

-------+--------+---------------------
 RID   | _allow | title  
-------+--------+---------------------
 #18:0 | [#5:5] | Yesterday in Italy
 #18:1 | [#5:6] | My Nutella Cake!
-------+--------+---------------------

While this is an effective solution, it does have one minor flaw for Luke. By adding Steve to the _allow list, Steve can not only read posts Luke makes, but he can also modify them. While Luke may find Steve a reasonable person, he begins to have second thoughts about this blanket permission and decides to remove Steve from the _allow field and instead add him to the _allowRead field:

orientdb> UPDATE #18:0 REMOVE _allow = 5:6
orientdb> UPDATE #18:0 ADD _allowRead = #5:6

For the sake of argument, assume that Luke's misgivings about Steve have some foundation. Steve decides that he does not like Luke's entry Yesterday in Italy and would like to remove it from the database. He logs into OrientDB, runs SELECT to find its RID, and attempts to DELETE the record:

orientdb> SELECT FROM Post

-------+--------+---------------------
 RID   | _allow | title
-------+--------+---------------------
 #18:0 | [#5:5] | Yesterday in Italy
 #18:1 | [#5:6] | My Nutella Cake!
-------+--------+---------------------

orientdb> DELETE FROM #18:0

!Error: Cannot delete record #18:0 because the access to the resource is restricted.

As you can see, OrientDB blocks the DELETE operation, given that the current user, Steve, does not have permission to do so on this resource.

Password Management

OrientDB stores user passwords in the OUser records using the PBKDF2 HASH algorithm with a 24-bit length Salt per user for a configurable number of iterations. By default, this number is 65,536 iterations. You can change this account through the security.userPasswordSaltIterations global configuration. Note that while a higher iteration count can slow down attacks, it also slows down the authentication process on legitimate OrientDB use.

In order to speed up password hashing, OrientDB uses a password cache, which it implements as an LRU with a maximum of five hundred entries. You can change this setting through the security.userPasswordSaltCacheSize global configuration. Giving this global configuration the value of 0 disables the cache.

NOTE: In the event that attackers gain access to the Java virtual machine memory dump, he could access this map, which would give them access to all passwords. You can protect your database from this attack by disabling the in memory password cache.

results matching ""

    No results matching ""