sahat / hackathon-starter

A boilerplate for Node.js web applications
MIT License
34.87k stars 8.18k forks source link

Are there tricks to enabling MongoDB authentication? #783

Closed erskine closed 6 years ago

erskine commented 6 years ago

First, thank you for this awesome project, it has helped me immensely.

Now, I'm wanting to enable authentication on MongoDB for security reasons.

I've followed all the instructions on Mongo and established all the users with the appropriate roles (I think). When I log in as the user admin (db_user), I can get into the admin db and view all the users roles:

> use admin
> db.getUsers()
[
    {
        "_id" : "admin.db_user",
        "user" : "db_user",
        "db" : "admin",
        "roles" : [
            {
                "role" : "userAdminAnyDatabase",
                "db" : "admin"
            }
        ]
    },
    {
        "_id" : "admin.erskine",
        "user" : "erskine",
        "db" : "admin",
        "roles" : [
            {
                "role" : "readWriteAnyDatabase",
                "db" : "admin"
            }
        ]
    },
    {
        "_id" : "admin.root",
        "user" : "root",
        "db" : "admin",
        "roles" : [
            {
                "role" : "root",
                "db" : "admin"
            }
        ]
    }
]
>

I can also login as the application user (erskine) and select from the collections in the application db (test).

But when I start the composite container, I get this error from the app:

web_1    | Unhandled rejection MongoError: not authorized on test to execute command { listIndexes: "sessions", cursor: {} }
web_1    |     at Function.MongoError.create (/starter/node_modules/mongodb-core/lib/error.js:31:11)
web_1    |     at queryCallback (/starter/node_modules/mongodb-core/lib/cursor.js:212:36)
web_1    |     at /starter/node_modules/mongodb-core/lib/connection/pool.js:469:18
web_1    |     at _combinedTickCallback (internal/process/next_tick.js:73:7)
web_1    |     at process._tickCallback (internal/process/next_tick.js:104:9)
web_1    | Mongoose: users.ensureIndex({ email: 1 }, { unique: true, background: true }

Yet, when I view the logs from KiteMatic on the mongo container, it suggests the user was able to connect no problem, but it does show the not authorized to execute listIndexes command error:

2018-02-02T16:45:55.856+0000 I ACCESS   [conn6] Unauthorized: not authorized on test to execute command { listIndexes: "sessions", cursor: {} }
2018-02-02T16:45:55.875+0000 I ACCESS   [conn5] Successfully authenticated as principal erskine on admin

And here is my mongoose connect code:

mongoose.Promise = global.Promise;
var options = {
  useMongoClient: true,
  user: process.env.MONGO_USER,
  pass: process.env.MONGO_PASS,
  auth: {
    authdb: 'admin'
  }
}
mongoose.set('debug', true);
mongoose.connect(process.env.MONGODB_URI, options);
mongoose.connection.on('error', (err) => {
  console.error(err);
  console.log('%s MongoDB connection error. Please make sure MongoDB is running.', chalk.red('✗'));
  process.exit();
});

My user and password are in the .env.example file.

No amount of googling the above error has yielded a solution. I'm wondering if it's related to the composite nature of the containers and the startup dynamics? The readWriteAnyDatabase should be (more than) sufficient for the application user. I've also tried with the root user, with no better results.

YasharF commented 6 years ago

When you say you want to enable authentication on MongoDB for security reasons, what do you mean by that?

Right now:

erskine commented 6 years ago

Thanks Yashar, my choice of db user names is confusing. Let me clarify: I'm referring to securing MongoDB from unauthorized read/write access coming from outside the app itself. The default for this project is to launch mongod without the --auth flag, meaning anyone can launch the mongo CLI and interact with the dB without restrictions, including reading from the user collection which contains API access tokens for users.

When I attempt to enable dB authentication via the --auth flag in the docker-compose.yml file, the app is no longer able to connect to the dB via mongoose using the credentials I supply for the users I created.

I'm following the docs from around the internet, but in particular the "Authentication & Authorization" section here: https://hub.docker.com/_/mongo/

YasharF commented 6 years ago

I don't know about configuring docker, but here is the simple case where you have a standalone MongoDB setup (like in mLab.com):

The app accesses the DB (if not local) based on MONGODB_URI or MONGOLAB_URI environment variables and the DB username and password gets folded in the URI. That is based on line https://github.com/sahat/hackathon-starter/blob/master/app.js#L52 So you can set MONGODB_URI=mongodb://[dbUsername]:[dbUser'sPassword]@[dbServer]:[dbPort]/[dbname] in your environment variables and you are done without having to make any code changes.

As an example the URI would end up something like: MONGODB_URI=mongodb://erskdb:blahblahpassword837sd@server345.mongolab.com:51843/myawesomeprojectdb

YasharF commented 6 years ago

Is the problem that you are having, the defaults in https://github.com/sahat/hackathon-starter/blob/master/docker-compose.yml ?

YasharF commented 6 years ago

When I type db.getUsers() for a secured instance on mlab, I get something like the following, and heroku_lj2sd1v0 and its password are what are in the URI env variable. In this case the users were created through the UI at mlab and heroku in the first place though.

[
        {
                "_id" : "heroku_lj2sd1v0.my_DB_Access",
                "user" : "my_DB_Access",
                "db" : "heroku_lj2sd1v0",
                "roles" : [
                        {
                                "role" : "dbOwner",
                                "db" : "heroku_lj2sd1v0"
                        }
                ]
        },
        {
                "_id" : "heroku_lj2sd1v0.backup",
                "user" : "backup",
                "db" : "heroku_lj2sd1v0",
                "roles" : [
                        {
                                "role" : "read",
                                "db" : "heroku_lj2sd1v0"
                        }
                ]
        },
        {
                "_id" : "heroku_lj2sd1v0.app1_beta_signup",
                "user" : "app1_beta_signup",
                "db" : "heroku_lj2sd1v0",
                "roles" : [
                        {
                                "role" : "dbOwner",
                                "db" : "heroku_lj2sd1v0"
                        }
                ]
        },
        {
                "_id" : "heroku_lj2sd1v0.heroku_lj2sd1v0",
                "user" : "heroku_lj2sd1v0",
                "db" : "heroku_lj2sd1v0",
                "roles" : [
                        {
                                "role" : "dbOwner",
                                "db" : "heroku_lj2sd1v0"
                        }
                ]
        }
]
erskine commented 6 years ago

I have tried supplying the username/password in the MONGODB_URI, to no avail. I was also experimenting with the environment variables MONGO_USER & MONGO_PASS via .env.example as you can see in my code above. I'm using a local MongoDB instance and not mlab. But I will try again with the MONGODB_URI.

erskine commented 6 years ago

Still no joy - I get " Unhandled rejection MongoError: not authorized on test to execute command { listIndexes: "sessions", cursor: {} }" even when connecting as my root db user. However, when I connect to mongodb via the mongo CLI, I can authenticate as root and read/write to my hearts content. Very strange.

YasharF commented 6 years ago

Can you connect to the DB with a tool like MongoDB Compass? Are you using the DB related code in hackathon-starter without modification at this point, or are you using your own mongoose connect code? You can probably add some temporary debug code to console.log to see what username and password and URI mongoose is trying and how they might be different than what is manually working in MongoDB Compass or CLI.