PRODUCTS

KEYWORDS

DumboDB: Announcing RBAC Support

DumboDB Logo

DumboDB is DoltHub’s NoSQL, version-controlled database. It’s MongoDB’s API combined with Git-style revision control. Last week, we released DumboDB 0.4.0, which had several big features, including authentication and authorization. We also added durable views and validators, but we’ll talk about those next week. This week, let’s lock down your DumboDB server!

Setup Workflow#

DumboDB followed MongoDB’s lead in setting up authentication for your first user.

Many server applications have a chicken-and-egg problem when starting up for the first time. You want to have a secure server, but you don’t have any users registered yet who can administer it. In Dolt, we have the root user when you start, but on first user creation, the root user is deleted. Other servers require you to start with an interactive prompt to create your first user.

MongoDB, and therefore DumboDB, takes a different approach. The first time you start the server, you can connect to it as an unauthenticated user from localhost. When you connect, you can’t do anything except create your first user. Once you create that user, you are forced to log in to do anything else. This is a nice balance between security and usability.

Let’s show the steps. First start the server with authentication enabled using the --auth flag:

$ dumbodb --data-dir /tmp/mydb --auth

Then connect to the server from the localhost using a different terminal:

$ mongosh mongodb://localhost
Current Mongosh Log ID: 6a7a1d14cbb17329e082805e
Connecting to:          mongodb://localhost/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.9.2
Using MongoDB:          8.0.28
Using Mongosh:          2.9.2

For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/

test>

The very observant reader will notice that when connecting to a DumboDB server without authentication, you will see a warning from the server indicating that you are connected to DumboDB. You can’t see that warning in this scenario because seeing warnings from the server requires the getActionLog RBAC privilege, which no one has yet because there are no users. Remember how I said that this temporary connection has no privileges other than creating the first user? Well, that includes seeing warnings from the server!

test> use admin
switched to db admin
admin> db.createUser({
    user: "neil",
    pwd: "my_s00per_secret",
    // Very important to create the first user with the root role
    roles: [ { role: "root", db: "admin" } ]   
})

There is a dangerous footgun to avoid here. The first user you create must have sufficiently high privileges to create additional users and control their permissions. Remember that the connection you are on right now is treated with special logic that allows you to create a user if, and only if, there are no users at all. Once you create that first user, your current connection basically becomes useless until you authenticate.

admin> db.createUser({ user: "joe", pwd: "secret", roles: [ { role: "root", db: "admin" } ] })
MongoServerError[Unauthorized]: Command createUser requires authentication

This is all to say that if your first user doesn’t have root, you’ll turn your server into a brick. You can’t create any more users, and you can’t do anything else. The only way to fix this is to stop the server process, delete the data directory, and start over.

When you have your first user created, you can promote your current connection to an authenticated connection by running the db.auth command:

admin> db.auth("neil", "my_s00per_secret")
{"ok": 1}

And just to double-check, you can look at the privileges of your current connection by looking at your connectionStatus:

admin> db.runCommand({ connectionStatus: 1, showPrivileges: true })
{
  authInfo: {
    authenticatedUsers: [ { user: 'neil', db: 'admin' } ],
    authenticatedUserRoles: [ { db: 'admin', role: 'root' } ],
    authenticatedUserPrivileges: [
      {
        resource: { db: '', collection: '' },
        actions: [
          'find',
          [...snip 41 lines...]
          'viewUser'
        ]
      },
      {
        resource: { cluster: true },
        actions: [
             [...snip 8 lines...]
        ]
      },
      {
        resource: { db: 'admin', collection: 'system.users' },
        actions: [
          [...snip 7 lines...]
        ]
      },
      {
        resource: { db: 'admin', collection: 'system.roles' },
        actions: [
          [...snip 7 lines...]
        ]
      }
    ]
  },
  ok: 1
}

RBAC#

Now that you have your administrator user created, you can create additional users and assign them roles. MongoDB has a role-based access control (RBAC) system that allows for very fine-grained control over what users can do. DumboDB has implemented the same RBAC system, so you can use the same roles and privileges as you would in MongoDB.

For small deployments, I confess that RBAC is a big hammer. For example, you can give a user the ability to insert documents into one specific collection, but they can’t do anything else. They can’t even read from the collection to determine if their insert was successful. This can be really useful for some applications, especially if you need to constrain system users (or agents) that can only do one narrowly defined task. Powerful, yes. RBAC is how MongoDB rolls, and so that’s DumboDB rolls too (roles pun in there somewhere).

For most applications, you just want to give a user read/write access to a database, and you don’t care about the specific collections in that database. MongoDB does have roles which are unions of fine-grained roles, though. The best example is the readWrite role, which gives a user the ability to read and write to all collections in a database. This is the most common role that you will assign to users. If you dig in a little deeper, as the administrator, you can specifically list everything the readWrite role enables:

admin> db.runCommand({ rolesInfo: "readWrite", showPrivileges: true })
{
  roles: [
    {
      role: 'readWrite',
      db: 'admin',
      isBuiltin: true,
      roles: [],
      inheritedRoles: [],
      privileges: [
        {
          resource: { db: 'admin', collection: '' },
          actions: [
            'find',                   'collStats',
            'dbStats',                'killCursors',
            'listCollections',        'listIndexes',
            'listSearchIndexes',      'insert',
            'update',                 'remove',
            'createCollection',       'createIndex',
            'createSearchIndexes',    'dropCollection',
            'dropIndex',              'dropSearchIndex',
            'renameCollectionSameDB', 'updateSearchIndex',
            'convertToCapped'
          ]
        }
      ],
    }
  ],
  ok: 1
}

I say you can do that as an administrator, but that’s not strictly true. You can do that as any user that has the viewRole privilege, which an administrator has and can grant to others. It’s a powerful system!

RBAC even supports custom roles, so you can create your own roles with specific privileges. Let’s say, for example, you have a common task in your company that requires database users to be able to read all of the collections in the store database but only update documents in the orders collection. You can create a custom role for that:

admin> use store
switched to db store
store> db.createRole({
    role: "orderWriter",
    privileges: [
      {
        resource: { db: "store", collection: "orders" },   // scoped to ONE collection
        actions:  [ "update" ]                             // UPDATE only!
      }
    ],
    roles: [ { role: "read", db: "store" } ]                // inherit full read of store
})

You can create a user with only that one role, and they will be able to do their job:

store> db.createUser({
    user: "customerService54",
    pwd: "secret",
    roles: [ { role: "orderWriter", db: "store" } ]
})

RBAC is powerful stuff. There are books written about it, so I’m obviously just scratching the surface here. The point worth driving home is that MongoDB’s RBAC system is fully replicated in DumboDB. Existing systems built to depend on it should work with DumboDB without any changes.

DumboDB tries to match MongoDB’s RBAC system as closely as possible. We added more than 200 tests to ensure we got it right. Let us know if you find any missing roles or role unions. We want to be as compatible as possible with MongoDB’s RBAC system.

The admin Database#

Every DumboDB server has a special database called admin. Until the latest release, the admin database was empty. We didn’t initialize it. Now, the admin database is initialized with a system.users collection and a system.roles collection. The system.users collection contains all of the users that have been created on the server. These are keyed by database. The system.roles collection contains a document for each role. These collections are not directly writable; they are manipulated by the createUser, createRole, and other commands. You can read from them, but you can’t write to them directly. Under the hood, they are fully materialized collections like any other database.

For our use case, this is actually ideal. This means that when we add push and pull support to DumboDB, we can consider the data of the database as a standalone unit that can be moved between servers. The access control to that database is not encoded in the data itself. The DumboDB unDrop feature benefits from this as well. If you drop a database, the users and their permissions are still stored in the admin database. If you then unDrop the database, the users and their permissions are still intact.

There is an added benefit: the admin database is fully version controlled. It has the --auto-commit flag enabled. On every update, the admin database is automatically committed. This means that you can use the dumboLog command to see the history of user and role creation. You can see the last time a user updated their password. You have an audit log showing when your roles were modified. You can even revert to a previous version of the admin database if you need to roll back a change.

To give you an idea of what this looks like, from the example code above, the last thing that changed on the admin database was the creation of the customerService54 user. You can see that as the most recent commit in the log:

admin> db.runCommand({dumboLog:1, patch:1, limit: 1})
{
  commits: [
    {
      commitId: 'odpeu3da4gqs67tt35d9rg2i2t5ls6at',
      refs: [ 'HEAD', 'main' ],
      parent1: '5qlcta001okmna6qjhnnq8lk0mdaadgi',
      message: 'auto: insert 1 docs into system.users',        // New document in system.users
      timestamp: ISODate('2026-08-10T22:26:34.604Z'),          // means a new user was created.
      author: 'dumbodb <dumbodb@localhost>',
      committer: 'dumbodb <dumbodb@localhost>',
      committerTimestamp: ISODate('2026-08-10T22:26:34.604Z'),
      changes: [
        {
          type: 'collection',
          name: 'system.users',
          status: 'modified',
          documents: {
            added: [
              {
                _id: 'store.customerService54',                // Document is keyed as <db>.<user>
                credentials: {
                  'SCRAM-SHA-1': {
                    iterationCount: 10000,
                    salt: 'wza5XM+tl8T22YkieapheA==',
                    serverKey: 'EgYNHqiIitiD98kfDnJVvx6p6Yk=',
                    storedKey: '5uMeDMAjRaKWpX3qpmpD07FFqhs='
                  },
                  'SCRAM-SHA-256': {
                    iterationCount: 15000,
                    salt: 'hvq/eiahl4dCUJmLrUZSRLdMyOXPF8j+3f5F5w==',
                    serverKey: 'wPA+l5IJ6/M3/uVfzAvCTu/rT67IqjlYOqLIQSTBJLk=',
                    storedKey: 'T23wYCPK5nVCJquoj+0mGsMbdVVu1b6HGb324qiLwQo='
                  }
                },
                db: 'store',
                roles: [ { db: 'store', role: 'orderWriter' } ],  // Using our custom role from above.
                user: 'customerService54',
                userId: UUID('cb2533e3-efa5-404a-9ce5-d3b685b4105c')
              }
            ],
            removed: [],
            modified: []
          },
          indexes: { added: [], removed: [], modified: [] },
          metadata: {}
        }
      ]
    }
  ],
  next: [ '5qlcta001okmna6qjhnnq8lk0mdaadgi' ],
  ok: 1
}

You can see the data of the user creation in your history! It is probably worth calling out that MongoDB, and therefore DumboDB, does not store the password in the database. They both use SCRAM to store a salted hash of the password. This means that you can’t see the password in the history, but you can see when a user was created and when their password was changed.

Or, if you want to see more about how the sausage is made, we can look at the system.roles collection with a log filter. This is where the orderWriter role was created:

admin> db.runCommand({dumboLog:1, patch:1, filters: ['system.roles']})
{
  commits: [
    {
      commitId: '5qlcta001okmna6qjhnnq8lk0mdaadgi',
      parent1: 'bonvo2d8sb0ir42olkqcg96hvcd7crca',
      message: 'auto: insert 1 docs into system.roles',
      timestamp: ISODate('2026-08-10T22:26:26.614Z'),
      author: 'dumbodb <dumbodb@localhost>',
      committer: 'dumbodb <dumbodb@localhost>',
      committerTimestamp: ISODate('2026-08-10T22:26:26.614Z'),
      changes: [
        {
          type: 'collection',
          name: 'system.roles',
          status: 'added',
          documents: {
            added: [
              {
                _id: 'store.orderWriter',
                db: 'store',
                privileges: [
                  {
                    actions: [ 'update' ],
                    resource: { collection: 'orders', db: 'store' }
                  }
                ],
                role: 'orderWriter',
                roles: [ { db: 'store', role: 'read' } ]
              }
            ],
            removed: [],
            modified: []
          },
          indexes: { added: [], removed: [], modified: [] },
          metadata: {}
        }
      ]
    }
  ],
  ok: 1
}

Having a strong audit log of user and role creation is a powerful tool for administrators. Having the data is step one. We could add commands to leverage this in creative ways. For example, we could add a feature to ensure you never re-use a username. Check it out, and let us know if you have any other ideas for how to leverage the version control of the admin database.

What’s Next?#

Branch-level permissions would be the next logical thing to implement in the space of permissions. This will allow you to have a dev branch of your database that is open to all users, but a prod branch that is locked down to only a few users. This ability is particularly useful for agents developing on an isolated branch. We’ll let you know more when the feature is ready.

In parallel, we are continuing to work on testing against 3rd-party MongoDB applications. Each one we test shines a light on what we are building. Currently, collation support and TTL Indexes are the two biggest gaps in our MongoDB parity story. We’d love to hear from you about what applications you are trying to run against DumboDB. We want to make sure that we are testing the right things.

User authentication and authorization are table stakes for any database. We are happy to have this feature in DumboDB, and we are excited to see what you build with it! Hop on our Discord to ask questions and nerd out about version-controlled databases!