Multi-tenancy

Many database systems provide multi-tenant capabilities. They can contain multiple databases, each with their own access controls. Fauna takes this much further by allowing any database to have multiple child databases. This enables a database owner to create a child database within their own database (the "parent" database), create an "admin" key for the child database, and provide the key to a team that needs a database for application development. The team then has full access to the child database and is free to create collections, documents, and indexes, as well as child databases within their database, and manage them as they need without requiring the parent database owner’s intervention. When connected to a child database, it is not possible to access the parent database, or even determine that there is a parent database. At any time, the parent database owner can revoke a team’s access key should that be required.

It is not possible to query databases outside of the current database. It is possible to perform read-only queries within child databases by specifying the optional database parameter when calling any of these functions: Collection, Collections, Database, Databases, Function, Functions, Index, Indexes, Keys, Role, Roles

To query another database, or perform queries involving writes within a child database, you must use a secret associated with that database. See Fauna key system for more information.

This tutorial demonstrates how to create multiple databases, create child databases, and provide access secrets.

This tutorial assumes that you have completed the Dashboard quick start.

Step 1: Start Fauna Shell

In a terminal, start Fauna Shell by running:

fauna shell my_db
Starting shell for database my_db
Connected to https://db.fauna.com
Type Ctrl+D or .exit to exit the shell
 

The secret used to setup and access the database my_db is an admin secret, which has permission to create and manage child databases.

Step 2: Create the top-level databases

First, create a set of top-level databases that can be handed over to individual teams. Run the following query to create two databases, named production and internal:

Map(
  ["production", "internal"],
  Lambda("name", CreateDatabase({ name: Var("name")}))
)

You should see output similar to:

[ { ref: Database("production"),
    ts: 1558574366150000,
    name: 'production' },
  { ref: Database("internal"),
    ts: 1558574366150000,
    name: 'internal' } ]

Step 3: Create admin secrets for each database

Here we create admin secrets for each database. These secrets can be handed to each team, and they give the teams full control over their respective databases (but not each other’s).

Map(
  [Database("production"), Database("internal")],
  Lambda("db", CreateKey({ role: "admin", database: Var("db") }))
)

You should see output similar to:

[ { ref: Ref(Keys(), "233115268679729664"),
    ts: 1558574894580000,
    role: 'admin',
    database: Database("production"),
    secret: 'fnADPDEaDWACAONo-I_v8FH9DEuWDZKCGQyborRY',
    hashed_secret:
     '$2a$05$ufSuq8vPlxU2KpX3qIxTMue59uz51D.VpXwfiZovyMAm.1lcY/IPK' },
  { ref: Ref(Keys(), "233115268694409728"),
    ts: 1558574894580000,
    role: 'admin',
    database: Database("internal"),
    secret: 'fnADQDExDkACAJxK4j3CwxjTWxB2CnqTHowktW4M',
    hashed_secret:
     '$2a$05$3XncjrjIPhkPAXX9DsV8B.46FVJZOmSAOF6dsqMz3/p5HiLt1aPDm' } ]
Be sure to save a copy of each secret. Secrets are only displayed when they are created. If you lose a secret, you would need to delete the associated key and make a new one.

Step 4: Create per-team child databases

Given the new keys, each team can create their own child databases that fit their needs. In this case, the production team does not need any child databases, but the internal team does.

Open a new terminal window, and start Fauna Shell with the secret for the internal database (replace the secret below with the secret generated in the previous command):

fauna shell --secret=fnADQDExDkACAJxK4j3CwxjTWxB2CnqTHowktW4M
Connected to https://db.fauna.com
Type Ctrl+D or .exit to exit the shell
 

Fauna knows which database to connect to when you provide a secret, but it doesn’t know the database’s name (the prompt is just >).

The internal team needs two child databases, personnel and bulletin-board. Run the following query:

Map(
  ["personnel", "bulletin-board"],
  Lambda("db", CreateDatabase({ name: Var("db") }))
)

You should see output similar to:

[ { ref: Database("personnel"),
    ts: 1558575584530000,
    name: 'personnel' },
  { ref: Database("bulletin-board"),
    ts: 1558575584530000,
    name: 'bulletin-board' } ]

Step 5: Create server secrets for the internal team’s child databases

The internal team has an application for each database, so we now need to create server secrets to permit the applications to connect to their respective databases. Server secrets do not have permission to create or manage child databases; they can only be used to connect to their associated database.

Run the following query:

Map(
  [Database("personnel"), Database("bulletin-board")],
  Lambda("db", CreateKey({ role: "server", database: Var("db") }))
)

You should see output similar to:

[ { ref: Ref(Keys(), "233116225856602624"),
    ts: 1558575807350000,
    role: 'server',
    database: Database("personnel"),
    secret: 'fnADPDH46ZACAIfnmFk883bVBkFVFodHhtVXXtBK',
    hashed_secret:
     '$2a$05$WWJkl8bydmoAo8D0KNN/w.4sglkxfDwKtSIYr3RU5jg36uT3l3atm' },
  { ref: Ref(Keys(), "233116225856603648"),
    ts: 1558575807350000,
    role: 'server',
    database: Database("bulletin-board"),
    secret: 'fnADPDH46ZAGAMjVDSoDHU89i0qsMq9uMArnLzoK',
    hashed_secret:
     '$2a$05$ZgftjR/OiajqV3qaDSDl0uHolpaO.HRfHQoM4NaWlinF18bgcIUli' } ]

These secrets should be copied into the respective application’s configuration now; you won’t see the secrets again.

Step 6: Verify the configuration

Now that the databases have been created, which databases can be seen?

In the terminal with Fauna Shell connected to the internal database, run the following query:

Paginate(Databases())

You should see output similar to:

{ data: [ Database("personnel"), Database("bulletin-board") ] }

In the terminal with the Fauna Shell connected to the my_db database, run the following query:

Paginate(Databases())

You should see output similar to:

{ data: [ Database("production"), Database("internal") ] }

Surprised? Fauna only lists the immediate child databases within the connected database. However, the operator can use her admin secret to connect to the child databases to investigate further.

Conclusion

In this tutorial, we demonstrated how to set up a hierarchy of databases, starting with two top-level, broadly-scoped databases and continuing down to individual databases for the internal team. And we saw how to create access secrets and learned how much access those secrets provide.

Is this article helpful? 

Tell Fauna how the article can be improved:
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!