Search code examples
node.jsmongodbdocker-compose

MongoDB Authentication failes for any user


I'm using docker compose to pull up a MongoDB container and a simple Node CRUD app, really nothing fancy. The containers come up and seem to work so far, but when I try to enable authentication and log in using Compass, it always says "Authentication failed", no matter what user or connection settings I use. Inspecting the logs inside the Mongo container, I can see that the creation of the users apparently fails, but I have no clue why. Note that the same error message I posted in the logs section below also shows for the technical user in the same manner.

EDIT: just for clarification, I'm deploying this on my own dev server inside my local network, and the whole stuff is not intended to be pushed to some public server at any time. For personal use only, you could say ;-)

Here's my docker compose file:

services:
  mongodb:
    image: mongo:latest
    restart: unless-stopped
    ports:
      - "1335:27017"
    volumes:
      - database:/database
      - ./config/UserInit.js:/docker-entrypoint-initdb.d/UserInit.js:ro
    environment:
      - "MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}"
      - "MONGO_INITDB_ROOT_PASSWORD=${MONGO_PW}"
      - "MONGO_INITDB_DATABASE=banking"
    stdin_open: true
    tty: true

  bankingbackend:
    depends_on:
      - mongodb
    build: .
    restart: unless-stopped
    stdin_open: true
    tty: true
    ports:
      - "1336:1336"

volumes:
  database:

Dockerfile:

FROM node:latest

WORKDIR "/bankingBackend"
COPY . .
RUN npm install

CMD ["npm", "start"]

UserInit.js which should create another (technical) user:

db.createUser(
    {
        user: process.env.MONGO_TEC_USER,
        pwd: process.env.MONGO_TEC_USER_PASS,
        roles: [
            {
                role: "readWrite",
                db: "banking"
            }
        ]
    }
);
use("banking");
db.createCollection("accounts");
db.createCollection("transactions");

Jenkinsfile triggering the build and providing the credentials as env vars:

pipeline {
    agent any
    stages {
        stage('checkout') {
            steps {
                checkout scm
            }
        }
        stage('docker-compose-up') {
            steps {
            withCredentials([
                usernamePassword(credentialsId: 'Mongo tec user', passwordVariable: 'MONGO_PW', usernameVariable: 'MONGO_USER'),
                usernamePassword(credentialsId: 'Mongo tec user', passwordVariable: 'MONGO_TEC_USER_PASS', usernameVariable: 'MONGO_TEC_USER')]) {
                    sh 'MONGO_USER=MONGO_USER MONGO_PW=MONGO_PW MONGO_TEC_USER_PASS=MONGO_TEC_USER_PASS MONGO_TEC_USER=MONGO_TEC_USER docker compose up -d --build --force-recreate'
                }
            }
        }
    }
}

Logs:

"c":"ACCESS",   "id":5286307, "ctx":"conn2","msg":"Failed to authenticate","attr":{"client":"192.166.124.101:53804","isSpeculative":false,"isClusterMember":false,"mechanism":"SCRAM-SHA-1","user":"adminUser","db":"admin","error":"UserNotFound: Could not find user \"adminUser\" for db \"admin\"","result":11,"metrics":{"conversation_duration":{"micros":451,"summary":{"0":{"step":1,"step_total":2,"duration_micros":421

Solution

  • When you run docker-compose with environment variables like this:

    MONGO_USER=MONGO_USER MONGO_PW=MONGO_PW MONGO_TEC_USER_PASS=MONGO_TEC_USER_PASS MONGO_TEC_USER=MONGO_TEC_USER docker compose up -d --build --force-recreate
    

    The environment variables are not being passed to the docker-compose process, but rather to the shell that's running the command.

    To pass environment variables to the docker-compose process, you can use the -e flag followed by the variable name and value, like this:

    docker compose -e MONGO_USER=MONGO_USER -e MONGO_PW=MONGO_PW -e MONGO_TEC_USER_PASS=MONGO_TEC_USER_PASS -e MONGO_TEC_USER=MONGO_TEC_USER up -d --build --force-recreate
    

    However, this can become cumbersome if you have many environment variables.

    A better approach is to define the environment variables in your docker-compose.yml file, like this:

    services:
      mongodb:
        ...
        environment:
          - MONGO_INITDB_ROOT_USERNAME=${MONGO_USER}
          - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PW}
          - MONGO_INITDB_DATABASE=banking
          - MONGO_TEC_USER=${MONGO_TEC_USER}
          - MONGO_TEC_USER_PASS=${MONGO_TEC_USER_PASS}
        ...
    

    Then, in your Jenkinsfile, you can pass the environment variables like this:

    withCredentials([
        usernamePassword(credentialsId: 'Mongo tec user', passwordVariable: 'MONGO_PW', usernameVariable: 'MONGO_USER'),
        usernamePassword(credentialsId: 'Mongo tec user', passwordVariable: 'MONGO_TEC_USER_PASS', usernameVariable: 'MONGO_TEC_USER')]) {
        sh 'docker compose -f docker-compose.yml up -d --build --force-recreate'
    }
    

    This way, the environment variables will be passed to the docker-compose process and will be available to your containers.