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.
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
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-
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!