six demon bag

Wind, fire, all that kind of thing!

2020-02-29

Non-interactive MongoDB Commandline

MongoDB provides an interactive command shell for working with the database. Which is all nice and dandy, but from an admin and automation perspective it's desirable to also be able to run commands non-interactively. The mongo commandline tool does have a parameter --eval that kind of allows you to do that:

--eval <javascript>
Evaluates a JavaScript expression that is specified as an argument. mongo does not load its own environment when evaluating code. As a result many options of the shell environment are not available.

except that it doesn't play nice when you also want to automatically authenticate via the config file .mongorc.js.


root@server:~# mongo --ssl --sslAllowInvalidHostnames --eval 'rs.status()'
MongoDB shell version v3.4.10
connecting to: mongodb://127.0.0.1:27017
2018-04-11T08:39:28.250+0000 W NETWORK  [thread1] The server certificate does not match the host name. Hostname: 127.0.0.1 does not match SAN(s): *.example.com example.com
MongoDB server version: 3.4.10
{
  "ok" : 0,
  "errmsg" : "not authorized on admin to execute command { replSetGetStatus: 1.0 }",
  "code" : 13,
  "codeName" : "Unauthorized"
}

Essentially, non-interactive invocation ignores .mongorc.js and requires explicit credentials:

mongo --username alice --password abc123 --authenticationDatabase admin ...

However, if you echo the statement into the mongo command it authenticates and executes just fine:

root@server:~# echo 'rs.status()' | mongo --ssl --sslAllowInvalidHostnames
MongoDB shell version v3.4.23
connecting to: mongodb://127.0.0.1:27017
2020-02-26T19:33:07.603+0000 W NETWORK  [thread1] The server certificate does not match the host name. Hostname: 127.0.0.1 does not match SAN(s): ...
{
  ...
}

To work around this issue create a script (e.g. mongocmd) with the following content:

#!/bin/bash

declare -a mongo_args=(
  '--ssl'
  '--sslAllowInvalidHostnames'
  '--quiet'
)

if [ "$#" -eq 0 ]; then
  mongo "${mongo_args[@]}" < /dev/stdin
else
  for cmd in "$@"; do
    echo "$cmd" | mongo "${mongo_args[@]}"
  done
fi

The script will take commands either as arguments:

mongocmd 'rs.status()' 'db.hostInfo()'

or (if no arguments were provided) read them from STDIN:

echo 'rs.status()' | mongocmd
mongocmd <<EOF
rs.status()
db.hostInfo()
EOF

The parameter --sslAllowInvalidHostnames is used because the host certificate didn't include a Subject Alternative Name for 127.0.0.1.

The .mongorc.js for automatic login should look like this:

function authRequired() {
  try {
    return (db.serverCmdLineOpts().code == 13);
  } catch (err) {
    return false;
  }
}

if (authRequired()) {
  try {
    rs.slaveOk()
    var prev_db = db
    db = db.getSiblingDB('admin')
    db.auth('admin', 'PASSWORD')
    db = db.getSiblingDB(prev_db)
  } catch (err) {
    abort('Unknown error')
  }
}

Posted 01:29 [permalink]