Creating SQL Databases
In a multi-tenant setup, each tenant should have its own isolated database. This separation is crucial to ensure data security and integrity, allowing each tenant to access only its own data.
The case-engine scheduler and process-engine basic scheduler are exceptions. The scheduler can only be initialised once and is tenant-aware of itself.
To create the necessary databases, connect to your PostgreSQL server and execute the following SQL statements:
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "postgres" <<-EOSQL CREATE DATABASE google_audits; CREATE DATABASE google_cds; CREATE DATABASE google_comments; CREATE DATABASE google_process; CREATE DATABASE google_timeline; CREATE DATABASE google_trace; CREATE DATABASE google_reports; CREATE DATABASE apple_audits; CREATE DATABASE apple_cds; CREATE DATABASE apple_comments; CREATE DATABASE apple_process; CREATE DATABASE apple_timeline; CREATE DATABASE apple_trace; CREATE DATABASE apple_reports; CREATE DATABASE scheduler; EOSQL
Next, execute the SQL scripts for each tenant's database to set up the necessary tables and data structures. NOTE: use the regular creation scripts and transfer them to your postgresql server.
psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_audits" -f /db_scripts/audit.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_cds" -f /db_scripts/cds.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_comments" -f /db_scripts/comments.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_process" -f /db_scripts/process.sql -f /db_scripts/process_outbox.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_timeline" -f /db_scripts/timeline.sql -f /db_scripts/timeline_inbox.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_trace" -f /db_scripts/trace.sql -f /db_scripts/trace_inbox.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "google_reports" -f /db_scripts/reports.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_audits" -f /db_scripts/audit.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_cds" -f /db_scripts/cds.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_comments" -f /db_scripts/comments.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_process" -f /db_scripts/process.sql -f /db_scripts/process_outbox.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_timeline" -f /db_scripts/timeline.sql -f /db_scripts/timeline_inbox.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_trace" -f /db_scripts/trace.sql -f /db_scripts/trace_inbox.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "apple_reports" -f /db_scripts/reports.sql psql -v ON_ERROR_STOP=1 --username "postgres" --dbname "scheduler" -f /db_scripts/scheduler.sql
Defining Queues & Exchanges
Next we change the definitions.json of RabbitMQ to add two vHosts: Google and Apple. Afterwards we change the current vHosts of all declarations and bindings to Google. Finally we will duplicate the bindings an declarations and change on those the vHost to Apple.
In the least complex situation, the RabbitMQ is shared between tenants. Each tenant uses the same queues, but in its own virtual host. This means that data is not mixed between tenants.
An example script for RabbitMQ initialisation can be found here, it must be adjusted to create all exchanges and queues for each tenant, with a different virtual host, like the description below.
Google Tenant
{ "bindings": [ { "arguments": {}, "destination": "dcmEventsQueue", "destination_type": "queue", "routing_key": "", "source": "dcmEvents", "vhost": "Google" }, ... ], "exchanges": [ { "arguments": {}, "auto_delete": false, "durable": true, "internal": false, "name": "dlx.dcmMaintenanceApp", "type": "fanout", "vhost": "Google" }, ... ], "permissions": [ { "configure": ".*", "read": ".*", "user": "guest", "vhost": "Google", "write": ".*" } ], "policies": [], "queues": [ { "arguments": {}, "auto_delete": false, "durable": true, "name": "dcmMaintenanceAppDlq", "vhost": "Google" }, ... ], "vhosts": [ { "name": "Google" } ] }
Apple Tenant
{ "bindings": [ { "arguments": {}, "destination": "dcmEventsQueue", "destination_type": "queue", "routing_key": "", "source": "dcmEvents", "vhost": "Apple" }, ... ], "exchanges": [ { "arguments": {}, "auto_delete": false, "durable": true, "internal": false, "name": "dlx.dcmMaintenanceApp", "type": "fanout", "vhost": "Apple" }, ... ], "permissions": [ { "configure": ".*", "read": ".*", "user": "guest", "vhost": "Apple", "write": ".*" } ], "policies": [], "queues": [ { "arguments": {}, "auto_delete": false, "durable": true, "name": "dcmMaintenanceAppDlq", "vhost": "Apple" }, ... ], "vhosts": [ { "name": "Apple" } ] }
OAuth2 - Keycloak
To ensure that Keycloak adds the correct tenant name in the JWT token during user authentication, and to map the tenant attribute appropriately, you'll need to configure Keycloak accordingly.
A way to achieve this is to add a user-attribute to the users in Keycloak. For example using the steps below:
1. Add a "TenantID" Attribute to Users
- Navigate to the Keycloak Admin Console:
- Go to
Users
in the left-hand menu. - Select the specific user or create a new user or select an existing user.
- Go to the
Attributes
tab. - Add a new attribute with the name
TenantID
and set its value to the appropriate tenant name. - Save the changes.
- Go to
2. Create a Custom Mapper to Add the "tenant" Claim
- Create a Client Mapper:
- In the Keycloak Admin Console, go to the
Clients
section and select the client for which you want to add the claim. - Go to the
Mappers
tab and clickCreate
. - Configure the new mapper with the following settings:
- Name:
Tenant Mapper
(or any suitable name) - Mapper Type:
User Attribute
- User Attribute:
TenantID
- Token Claim Name:
tenant
(or your preferred claim path name) - Claim JSON Type:
String
- Add to ID token:
ON
- Add to access token:
ON
- Add to userinfo:
ON
- Name:
- Save the mapper.
- In the Keycloak Admin Console, go to the
Alternative Claim Path Name
If you decide to use a different claim path name, ensure that your application is configured to recognize this custom claim. For example, if you use custom_tenant_claim_path
instead of tenant
, you need to set the following configuration in the Runtime and DCM maintenance app:
blueriq: jwt: tenant-path: $.custom_tenant_claim_path
This configuration allows your application to correctly parse the tenant information from the JWT token.
Configuring the components
Runtime
To transition your Blueriq Runtime environment from single tenancy to multi-tenancy, you need to configure the property files to handle connections on a per-tenant basis. Below is described how to achieve this for two tenants, Google
and Apple
.
1. Enable Multi-Tenancy
First, enable multi-tenancy by adding the following properties to your application.properties
file:
blueriq.multi-tenancy.enabled=true blueriq.multi-tenancy.allowedTenants=google,apple
This setting tells the Blueriq Runtime that it should operate in multi-tenant mode and allows only the specified tenants (google
and apple
).
2. Configure JDBC Connections for Tenants
For each tenant, configure the JDBC connection settings. Use default properties for common configurations like the driver, username, and password, and then specify the JDBC URL for each tenant's specific databases.
Google Tenant
blueriq.default.datasource.tenants.google.driverClassName=org.postgresql.Driver blueriq.default.datasource.tenants.google.username=<your database username> blueriq.default.datasource.tenants.google.password=<your database password> blueriq.datasource.comments-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_comments blueriq.datasource.process-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_process blueriq.datasource.trace-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_trace blueriq.datasource.timeline-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_timeline blueriq.datasource.report-service.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_reports
Apple Tenant
blueriq.default.datasource.tenants.apple.driverClassName=org.postgresql.Driver blueriq.default.datasource.tenants.apple.username=<your database username> blueriq.default.datasource.tenants.apple.password=<your database password> blueriq.datasource.comments-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_comments blueriq.datasource.process-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_process blueriq.datasource.trace-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_trace blueriq.datasource.timeline-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_timeline blueriq.datasource.report-service.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_reports
3. Configure RabbitMQ Connections for Tenants
For each tenant, configure the RabbitMQ connection settings. Use default properties for common settings like host, port, username, and password. You dont need to set the queue and exchange names as they are picked up by default.
Defaults to the RabbitMQ instance
blueriq.default.rabbitmq.host=<rabbitmq host> blueriq.default.rabbitmq.port=<rabbitmq port>
Google Tenant
blueriq.default.rabbitmq.tenants.google.username=<rabbitmq username> blueriq.default.rabbitmq.tenants.google.password=<rabbitmq password> blueriq.default.rabbitmq.tenants.google.virtualHost=Google
Apple Tenant
blueriq.default.rabbitmq.tenants.apple.username=<rabbitmq username> blueriq.default.rabbitmq.tenants.apple.password=<rabbitmq password> blueriq.default.rabbitmq.tenants.apple.virtualHost=Apple
4. Additional Connections (SOAP, REST, etc.)
For other connections like SOAP services, REST calls, etc., follow the same pattern. Use the format:
<connection-prefix>.tenants.<tenantName>.<specific-property>=<value>
blueriq.soap.service.tenants.google.endpoint=<google soap endpoint> blueriq.soap.service.tenants.apple.endpoint=<apple soap endpoint>
Case Engine
To set up the Case Engine with multi-tenancy support, you will need to configure SQL connections (for the process sql stoe, trace sql store, timeline sql store, and scheduler-quartz) and RabbitMQ connections for each tenant.
1. Enable Multi-Tenancy
Add the following settings to enable multi-tenancy:
blueriq.multi-tenancy.enabled=true blueriq.multi-tenancy.allowedTenants=google,apple
2. Configure JDBC Connections for Tenants
Google Tenant
# Google SQL Data Source blueriq.default.datasource.tenants.google.driverClassName=org.postgresql.Driver blueriq.default.datasource.tenants.google.username=<your database username> blueriq.default.datasource.tenants.google.password=<your database password> # Google JDBC URLs blueriq.datasource.process-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_process blueriq.datasource.trace-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_trace blueriq.datasource.timeline-sql-store.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_timeline blueriq.datasource.scheduler-quartz.tenants.google.url=jdbc:postgresql://<database host>:<database port>/google_reports
Apple Tenant
# Apple SQL Data Source blueriq.default.datasource.tenants.apple.driverClassName=org.postgresql.Driver blueriq.default.datasource.tenants.apple.username=<your database username> blueriq.default.datasource.tenants.apple.password=<your database password> # Apple JDBC URLs blueriq.datasource.process-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_process blueriq.datasource.trace-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_trace blueriq.datasource.timeline-sql-store.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_timeline blueriq.datasource.scheduler-quartz.tenants.apple.url=jdbc:postgresql://<database host>:<database port>/apple_reports
3. Configure RabbitMQ Properties for Each Tenant
Defaults to the RabbitMQ instance
blueriq.default.rabbitmq.host=<rabbitmq host> blueriq.default.rabbitmq.port=<rabbitmq port>
Google Tenant
blueriq.default.rabbitmq.tenants.google.username=<rabbitmq username> blueriq.default.rabbitmq.tenants.google.password=<rabbitmq password> blueriq.default.rabbitmq.tenants.google.virtualHost=Google
Apple Tenant
blueriq.default.rabbitmq.tenants.apple.username=<rabbitmq username> blueriq.default.rabbitmq.tenants.apple.password=<rabbitmq password> blueriq.default.rabbitmq.tenants.apple.virtualHost=Apple
4. Add MongoDB Connections for Each Tenant
Lastly, add the MongoDB connection details for each tenant to the application.properties
file:
Google Tenant
# Google MongoDB Configuration blueriq.default.mongodb.tenants.google.host=<mongodb host> blueriq.default.mongodb.tenants.google.port=<mongodb port> blueriq.default.mongodb.tenants.google.username=<mongodb username> blueriq.default.mongodb.tenants.google.password=<mongodb password> blueriq.case.engine.data.mongodb.tenants.google.database=google_caseEngine blueriq.locking.mongodb.tenants.google.database=google_locks
Apple Tenant
# Apple MongoDB Configuration blueriq.default.mongodb.tenants.apple.host=<mongodb host> blueriq.default.mongodb.tenants.apple.port=<mongodb port> blueriq.default.mongodb.tenants.apple.username=<mongodb username> blueriq.default.mongodb.tenants.apple.password=<mongodb password> blueriq.case.engine.data.mongodb.tenants.apple.database=apple_caseEngine blueriq.locking.mongodb.tenants.apple.database=apple_locks
CDS
To enable multi-tenancy for the CDS and adjust the customer data SQL store connection properties, enable multi-tenancy and adjust the customer data sql store datasource as follows
blueriq: multi-tenancy: enabled: true allowed-tenants: google,apple customerdata: datasource: customer-data-sql-store: driver-class-name: org.postgresql.Driver tenants: google: url: jdbc:postgresql://<database host>:<database port>/google_cds username: <your database username> password: <your database password> apple: url: jdbc:postgresql://<database host>:<database port>/apple_cds username: <your database username> password: <your database password>
DCM Lists Service
To enable multi-tenancy for the DCM Lists Service and configure the MongoDB and RabbitMQ connections, update your configuration as follows:
blueriq: multi-tenancy: enabled: true allowed-tenants: - google - apple default: mongodb: tenants: google: host: <database host> port: <database port> username: <database username> password: <database password> apple: host: <database host> port: <database port> username: <database username> password: <database password> rabbitmq: tenants: google: host: <database host> port: <database port> username: <database username> password: <database password> virtualHost: Google apple: host: <database host> port: <database port> username: <database username> password: <database password> virtualHost: Apple dcm: lists: mongodb: tenants: google: database: google_tasks apple: database: apple_tasks
DCM Maintenance App
To enable multi-tenancy for the DCM Mainteance App and configure the MongoDB and RabbitMQ connections, update your configuration as follows:
blueriq: multi-tenancy: enabled: true allowed-tenants: - google - apple default: mongodb: tenants: google: host: <database host> port: <database port> username: <database username> password: <database password> apple: host: <database host> port: <database port> username: <database username> password: <database password> rabbitmq: tenants: google: host: <database host> port: <database port> username: <database username> password: <database password> virtualHost: Google apple: host: <database host> port: <database port> username: <database username> password: <database password> virtualHost: Apple dcm: maitnenance: app: mongodb: tenants: google: database: google_messages apple: database: apple_apple
Adding the X-Tenant-ID
Header
The final step to ensure that your multi-tenant stack functions correctly is to include the X-Tenant-ID
header in each request. This header should carry the appropriate tenant name as its value.
We recommend using NGINX as a proxy to automate this process. NGINX should be configured to add the X-Tenant-ID
header to every incoming request.
One way of achieveing this is to add the tenant to your domain (google.mydomain.com or apple.mydomain.com). This can be rewritten in NGINX to add "apple" or "google" as a header to the request. Make sure the proxy always overwrites the header, otherwise someone might set it himself in the browser.