Neo4j 4: Multi tenancy

February 6, 2020 · 5 min read

Up until version 4.0, Neo4j has supported only one active database per server instance. As such, achieving multi tenancy meant that either a Neo4j instance had to be deployed per tenant, or all tenant graphs co-existed in the same database.

The first option meant a lot of extra infrastructure and maintenance, and the second implied some custom partitioning strategy usually achieved by differentiating tenants by labels or properties- a mechanism fraught with risk and mostly never preferred.

Neo4j 4 allows you to use more than one active database at the same time, where each database defines a transaction domain and execution context, thus preventing transactions from spanning across databases by default.

In this article, we describe how Neo4j 4 can be used in a multi tenant environment.

The Acme Corporation

In 2020, Acme decided to use Neo4j to manage its supply chain and picked Neo4j 4 to support its franchise business with multi tenancy. This was a good choice, because in earlier versions of Neo4j, their set up would have to account for multiple Neo4j servers, one for each franchise.

Separate Neo4j databases

Neo4j 4 was installed and the database administrator found that it contained two databases. One was the system database called system, and the other was called neo4j– the default database for user data. The system database is new in Neo4j 4 and contains metadata about the DBMS and security configuration.

The first franchise to be provisioned was WidgetsPlus. The goal was to create a database for WidgetsPlus, and the following users:

  • An admin
  • An application user

Here is what the Acme database admin did.

Setting up a tenant

First, she switched to the system database to be able to perform administrative functions such as creating databases and users.

:use system

Then, she created a database for the WidgetsPlus tenant. Note that database names are case-insensitive and normalised to lower case.

CREATE DATABASE widgetsplus

WidgetsPlus Neo4j database

Listing all databases shows the new one, and it was already online:

SHOW DATABASES

+-----------------------------------------------------------------------------------------------------+ | name          | address          | role         | requestedStatus | currentStatus | error | default | +-----------------------------------------------------------------------------------------------------+ | "neo4j"       | "localhost:7687" | "standalone" | "online"        | "online"      | ""    | TRUE    | | "system"      | "localhost:7687" | "standalone" | "online"        | "online"      | ""    | FALSE   | | "widgetsplus" | "localhost:7687" | "standalone" | "online"        | "online"      | ""    | FALSE   | +-----------------------------------------------------------------------------------------------------+ 

Creating Tenant Users

A separate admin role had to be created for every tenant.

Since privileges are bound to databases, and then assigned to roles, she could not create a global admin role and restrict it to different databases depending on the user.

So, to keep each tenant admin within his or her own space, she took the approach of creating a specific admin role for each tenant database.

CREATE ROLE widgetsPlusAdmin

GRANT ALL ON DATABASE widgetsplus TO widgetsPlusAdmin

This granted all database administrative privileges including access to the widgetsplus database and the ability to manage indexes and constraints, as well as start and stop it.

SHOW ROLE widgetsPlusAdmin PRIVILEGES

+-------------------------------------------------------------------------------------------------+ | access    | action               | resource   | graph         | segment    | role               | +-------------------------------------------------------------------------------------------------+ | "GRANTED" | "access"             | "database" | "widgetsplus" | "database" | "widgetsPlusAdmin" | | "GRANTED" | "create_constraint"  | "database" | "widgetsplus" | "database" | "widgetsPlusAdmin" | | "GRANTED" | "create_index"       | "database" | "widgetsplus" | "database" | "widgetsPlusAdmin" | | "GRANTED" | "create_label"       | "database" | "widgetsplus" | "database" | "widgetsPlusAdmin" | | "GRANTED" | "create_propertykey" | "database" | "widgetsplus" | "database" | "widgetsPlusAdmin" | | "GRANTED" | "create_reltype"     | "database" | "widgetsplus" | "database" | "widgetsPlusAdmin" | | "GRANTED" | "drop_constraint"    | "database" | "widgetsplus" | "database" | "widgetsPlusAdmin" | | "GRANTED" | "drop_index"         | "database" | "widgetsplus" | "database" | "widgetsPlusAdmin" | | "GRANTED" | "start_database"     | "database" | "widgetsplus" | "database" | "widgetsPlusAdmin" | | "GRANTED" | "stop_database"      | "database" | "widgetsplus" | "database" | "widgetsPlusAdmin" | +-------------------------------------------------------------------------------------------------+  

Next, the database admin created a user and assigned him this new role.

CREATE USER wpadmin SET PASSWORD "admin" CHANGE NOT REQUIRED

GRANT ROLE widgetsPlusAdmin TO wpadmin

This new user was now able to connect to the widgetsplus database. Access to the system database is also permitted, as it is from system that wpadmin can start and stop the widgetsplus database.

Next, the database admin created a role for the application to connect with.

Note that user and role management privileges exist only at the DBMS level. This means, we cannot restrict wpadmin to modify or grant privileges to only users within the widgetsplus tenant.

So instead, we have the global database administrator manage the role and privileges of the application user.

CREATE ROLE widgetsPlusApp

GRANT CONSTRAINT MANAGEMENT ON DATABASE widgetsplus TO widgetsPlusApp

GRANT NAME MANAGEMENT ON DATABASE widgetsplus TO widgetsPlusApp

GRANT ACCESS ON DATABASE widgetsplus TO widgetsPlusApp

GRANT TRAVERSE ON GRAPH widgetsplus TO widgetsPlusApp

GRANT MATCH {*} ON GRAPH widgetsplus TO widgetsPlusApp

GRANT READ {*} ON GRAPH widgetsplus TO widgetsPlusApp

GRANT WRITE ON GRAPH widgetsplus TO widgetsPlusApp

She created an application user and assigned this new role.

CREATE USER wpapp SET PASSWORD "app" CHANGE NOT REQUIRED

GRANT ROLE widgetsPlusApp TO wpapp

These were the resulting privileges granted to wpapp:

+----------------------------------------------------------------------------------------------------------------------+ | access    | action               | resource         | graph         | segment           | role             | user    | +----------------------------------------------------------------------------------------------------------------------+ | "GRANTED" | "read"               | "all_properties" | "widgetsplus" | "NODE(*)"         | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "write"              | "all_properties" | "widgetsplus" | "NODE(*)"         | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "traverse"           | "graph"          | "widgetsplus" | "NODE(*)"         | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "read"               | "all_properties" | "widgetsplus" | "RELATIONSHIP(*)" | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "write"              | "all_properties" | "widgetsplus" | "RELATIONSHIP(*)" | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "traverse"           | "graph"          | "widgetsplus" | "RELATIONSHIP(*)" | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "access"             | "database"       | "widgetsplus" | "database"        | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "create_constraint"  | "database"       | "widgetsplus" | "database"        | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "create_label"       | "database"       | "widgetsplus" | "database"        | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "create_propertykey" | "database"       | "widgetsplus" | "database"        | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "create_reltype"     | "database"       | "widgetsplus" | "database"        | "widgetsPlusApp" | "wpapp" | | "GRANTED" | "drop_constraint"    | "database"       | "widgetsplus" | "database"        | "widgetsPlusApp" | "wpapp" | +----------------------------------------------------------------------------------------------------------------------+  

wpapp is now capable of creating and maintaining nodes, relationships and indexes in the graph for database widgetsPlus but cannot start or stop the database- this privilege belongs to wpadmin.

Another Tenant

Acme then expanded and had its second franchise to set up- AssembleIt. The same process was followed to set up the new database with its own admin and application users. Now, the new database is created and managed within the same Neo4j DBMS server instance-

AssembleIT

The users and roles are set up in similar fashion.

CREATE DATABASE assembleit

CREATE ROLE assembleItAdmin

GRANT ALL ON DATABASE assembleit TO assembleItAdmin

CREATE USER aitadmin SET PASSWORD "admin" CHANGE NOT REQUIRED

GRANT ROLE assembleItAdmin TO aitadmin

CREATE ROLE assembleItApp

GRANT CONSTRAINT MANAGEMENT ON DATABASE assembleit TO assembleItApp

GRANT NAME MANAGEMENT ON DATABASE assembleit TO assembleItApp

GRANT ACCESS ON DATABASE assembleit TO assembleItApp

GRANT TRAVERSE ON GRAPH assembleit TO assembleItApp

GRANT MATCH {*} ON GRAPH assembleit TO assembleItApp

GRANT READ {*} ON GRAPH assembleit TO assembleItApp

GRANT WRITE ON GRAPH assembleit TO assembleItApp

CREATE USER aitApp SET PASSWORD "app" CHANGE NOT REQUIRED

GRANT ROLE assembleItApp TO aitApp

Conclusion

Both tenant spaces are now separated, and neither set of admin and application users can access databases on which they do not have privileges.

Apart from multi tenancy, the approach discussed here is also suited for maintaining multiple graphs and having them accessed securely.

We expect many enterprises to start to shift to this model with the release of Neo4j 4- don’t hesitate to contact us to assist you!


Meet the authors