Safeguarding destructive functions on the MongoDB shell

The Gist

You can redefine the behaviour of the mongodb shell. In this post, we use this to safeguard against accidental calls of destructive functions.

The same principle can also be used to remove access to any functionality of the mongodb shell.

The Story

A couple of months ago, in one of our projects, during a live deployment, the deployer accidentally dropped all indexes on a central collection. I can’t blame him - this can happen to (almost) anyone given enough time, and he’s very experienced with a history of splendid work.

We do post mortem analyses for incidents, so we tried to figure out some causes for this one. We came up with:

  • the wonderful tab completion the mongodb-shell has
  • fingers being quicker than the brain
  • working manually on the live database

There’s already some things to easily take away from this one:

  • don’t perform manual work on the live database, write scripts that are tested on a test database
  • if you need to manually apply migrations, do this in pairs, be attentive

Today, I want to present to you another quickwin we came up with to protect us from this kind of thing happening again.

I love the mongodb shell. I happen to have a good grasp on javascript, so I can apply this knowledge 1:1 when querying the database. In contrast to SQL shells, I have a context and state, so I can use the output of a query for mangling the data or even as input for another query or even multiple queries. Overall, I’m a happy camper and am not looking back to SQL shells. And you can configure it via an rc file on a system (/etc/mongorc.js) and user (~/.mongorc.js) basis.

Javascript is a dynamic language. This allows for Monkey Patching - replacing functions at runtime. We decided to implement a safeguard for destructive functions with the goal of reducing errors due to tab completion and fingers quicker than the brain.

In order to achieve that, we wanted to replace a function with a safeguard that stopped the normal execution of the destructive function, but kept it available in a convenient manner. We decided to republish the destructive function under the same name, but with a random string appended. This way, tab completion will always stop at the safeguard. Let’s see it in action:

Note that the name under which the function is republished (in this case “dropIndexeskgk”) changes for each new instance of the mongo shell.

The code actually is quite simple:

Take it apart

So, let’s dissect this snippet.

In line 1, the whole functionality is wrapped in a so called IIFE. This is a standard Javascript pattern to prevent namespace pollution.

Since we want to append random strings to our function names, we define a function for generating random strings on lines 3-5 (in a javascript specific, concise manner).

The main action happens in the obfuscate function (lines 7-24). First, we safeguard against invalid input (objects without prototype, line 8, and functions that don’t exist, line 13), so that if any object or function names change in future MongoDB versions, the script will at least notify the user. Then we publish the original function under the obfuscated name (line 17) and then put the safeguard function in place of the original function (lines 18-20).

On lines 26-49, the destructive calls are defined and passed to the obfuscate function.

Wrap it up

We roll this out to all our servers using chef. Our experience has been positive. It’s not too intrusive, but achieves its goal.

If you want to give your users the ability to skip the safeguarding, roll it out to ~/.mongorc.js, sourcing of which can be prevented by calling the mongo shell with the –norc parameter. Otherwise, roll it out to /etc/mongorc.js, which will always be sourced.