docker-library / mongo

Docker Official Image packaging for MongoDB

Home Page:https://www.mongodb.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

replica set with auth

adalga opened this issue · comments

When I try to run mongodb with --replSet and passed env variables for auth it gives error like

Error: Couldn't add user: not master

#179 I tried kalpakrg's solution too. However, it doesn't help either.

Same mistake

Ah, this is an interesting edge case of those new variables. As a workaround, you'll probably need to specify just --auth instead of supplying the environment variables, and then set up the users yourself after setting up the replica set (since that needs to be set up first, IIRC).

I'm not sure if there's a good way for us to deal with --replSet + auth automatically. 😞

It can only be corrected by overwriting the entrypoint

#!/usr/bin/env sh
# /custom-entrypoint.sh
if [ ! -f /data/db/.metadata/.replicaset ]; then
  mongod --fork --dbpath /data/db --port 27017 --logpath /var/log/mongod.log
  RET=1
  while [ $RET -ne 0 ]
  do
    echo "=> Waiting for confirmation of MongoDB service startup"
    sleep 5
    mongo admin --eval "help" >/dev/null 2>&1
    RET=$?
  done
  mongo admin --eval "db.createUser({user:'$MONGO_INITDB_ROOT_USERNAME',pwd:'$MONGO_INITDB_ROOT_PASSWORD',roles:[{role:'root',db:'admin'}]})"
  mongod --shutdown && mongod --fork --logpath /var/log/mongod.log --keyFile /run/secrets/MONGODB_KEYFILE --replSet $RS_NAME --shardsvr --dbpath /data/db --port 27017
  MYIP=192.168.1.10
  CONFIG="{_id:\"$RS_NAME\",version:1,members:[{_id:0,host:\"$MYIP:27017\"}]}"
  mongo -u $MONGO_INITDB_ROOT_USERNAME -p $MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "printjson(rs.initiate($CONFIG))"
  # members="192.168.1.11 192.168.1.12"
  # for member in $members; do
  #   if [ "$member" != "$MYIP" ]; then
  #     mongo -u $MONGO_INITDB_ROOT_USERNAME -p $MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "printjson(rs.add('$member:27017'))"
  #     sleep 5
  #   fi
  # done
  mkdir -p /data/db/.metadata
  touch /data/db/.metadata/.replicaset
  mongod --shutdown && mongod --keyFile /run/secrets/MONGODB_KEYFILE --replSet $RS_NAME --shardsvr --dbpath /data/db --port 27017
else
  mongod --keyFile /run/secrets/MONGODB_KEYFILE --replSet $RS_NAME --shardsvr --dbpath /data/db --port 27017
fi
version: '2'
services:
  mongod:
    image: mongo:3.4.10
    environment:
      RS_NAME: $RS_NAME
      MONGO_INITDB_ROOT_USERNAME: $MONGO_INITDB_ROOT_USERNAME
      MONGO_INITDB_ROOT_PASSWORD: $MONGO_INITDB_ROOT_PASSWORD
    entrypoint: /custom-entrypoint.sh

Right, the initial user must be configured without --replSet, but then the daemon must be restarted with --replSet in order to run rs.initiate().

Here's the simple one-liner I've been using to test with:

$ docker run -it --rm -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=example mongo --replSet test

The following patch fixes the issue (and allows the above command to complete successfully), but at the cost of removing --replSet for that initial mongod instance, which may or may not be acceptable:

diff --git a/3.4/docker-entrypoint.sh b/3.4/docker-entrypoint.sh
index e1d5642..f4fd398 100755
--- a/3.4/docker-entrypoint.sh
+++ b/3.4/docker-entrypoint.sh
@@ -76,26 +76,45 @@ _mongod_hack_ensure_arg() {
 		mongodHackedArgs+=( "$ensureArg" )
 	fi
 }
-# _mongod_hack_ensure_arg_val '--some-arg' 'some-val' "$@"
+# _mongod_hack_ensure_no_arg '--some-arg' "$@"
 # set -- "${mongodHackedArgs[@]}"
-_mongod_hack_ensure_arg_val() {
-	local ensureArg="$1"; shift
-	local ensureVal="$1"; shift
+_mongod_hack_ensure_no_arg() {
+	local ensureNoArg="$1"; shift
+	mongodHackedArgs=()
+	while [ "$#" -gt 0 ]; do
+		local arg="$1"; shift
+		if [ "$arg" = "$ensureNoArg" ]; then
+			continue
+		fi
+		mongodHackedArgs+=( "$arg" )
+	done
+}
+# _mongod_hack_ensure_no_arg '--some-arg' "$@"
+# set -- "${mongodHackedArgs[@]}"
+_mongod_hack_ensure_no_arg_val() {
+	local ensureNoArg="$1"; shift
 	mongodHackedArgs=()
 	while [ "$#" -gt 0 ]; do
 		local arg="$1"; shift
 		case "$arg" in
-			"$ensureArg")
+			"$ensureNoArg")
 				shift # also skip the value
 				continue
 				;;
-			"$ensureArg"=*)
+			"$ensureNoArg"=*)
 				# value is already included
 				continue
 				;;
 		esac
 		mongodHackedArgs+=( "$arg" )
 	done
+}
+# _mongod_hack_ensure_arg_val '--some-arg' 'some-val' "$@"
+# set -- "${mongodHackedArgs[@]}"
+_mongod_hack_ensure_arg_val() {
+	local ensureArg="$1"; shift
+	local ensureVal="$1"; shift
+	_mongod_hack_ensure_no_arg_val "$ensureArg" "$@"
 	mongodHackedArgs+=( "$ensureArg" "$ensureVal" )
 }
 # TODO what do to about "--config" ? :(
@@ -158,7 +177,11 @@ if [ "$originalArgOne" = 'mongod' ]; then
 		pidfile="$(mktemp)"
 		trap "rm -f '$pidfile'" EXIT
 
-		_mongod_hack_ensure_arg_val --bind_ip 127.0.0.1 "$@"
+		# remove "--auth" and "--replSet" for our initial startup (see https://docs.mongodb.com/manual/tutorial/enable-authentication/#start-mongodb-without-access-control)
+		_mongod_hack_ensure_no_arg --auth "$@"
+		_mongod_hack_ensure_no_arg_val --replSet "${mongodHackedArgs[@]}"
+
+		_mongod_hack_ensure_arg_val --bind_ip 127.0.0.1 "${mongodHackedArgs[@]}"
 		_mongod_hack_ensure_arg_val --port 27017 "${mongodHackedArgs[@]}"
 
 		sslMode="$(_mongod_hack_have_arg '--sslPEMKeyFile' "$@" && echo 'allowSSL' || echo 'disabled')" # "BadValue: need sslPEMKeyFile when SSL is enabled" vs "BadValue: need to enable SSL via the sslMode flag when using SSL configuration parameters"

One concrete side-effect is that if anyone was using rs.initiate() from within /docker-entrypoint-initdb.d, then this would break that (since we've stripped --replSet, and thus cannot initialize a replica set). Perhaps the fix would be to only remove --replSet if we've got MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD set (similar to other bits of this entrypoint).

For reference, here's that version:

diff --git a/3.4/docker-entrypoint.sh b/3.4/docker-entrypoint.sh
index e1d5642..871afa6 100755
--- a/3.4/docker-entrypoint.sh
+++ b/3.4/docker-entrypoint.sh
@@ -76,26 +76,45 @@ _mongod_hack_ensure_arg() {
 		mongodHackedArgs+=( "$ensureArg" )
 	fi
 }
-# _mongod_hack_ensure_arg_val '--some-arg' 'some-val' "$@"
+# _mongod_hack_ensure_no_arg '--some-arg' "$@"
 # set -- "${mongodHackedArgs[@]}"
-_mongod_hack_ensure_arg_val() {
-	local ensureArg="$1"; shift
-	local ensureVal="$1"; shift
+_mongod_hack_ensure_no_arg() {
+	local ensureNoArg="$1"; shift
+	mongodHackedArgs=()
+	while [ "$#" -gt 0 ]; do
+		local arg="$1"; shift
+		if [ "$arg" = "$ensureNoArg" ]; then
+			continue
+		fi
+		mongodHackedArgs+=( "$arg" )
+	done
+}
+# _mongod_hack_ensure_no_arg '--some-arg' "$@"
+# set -- "${mongodHackedArgs[@]}"
+_mongod_hack_ensure_no_arg_val() {
+	local ensureNoArg="$1"; shift
 	mongodHackedArgs=()
 	while [ "$#" -gt 0 ]; do
 		local arg="$1"; shift
 		case "$arg" in
-			"$ensureArg")
+			"$ensureNoArg")
 				shift # also skip the value
 				continue
 				;;
-			"$ensureArg"=*)
+			"$ensureNoArg"=*)
 				# value is already included
 				continue
 				;;
 		esac
 		mongodHackedArgs+=( "$arg" )
 	done
+}
+# _mongod_hack_ensure_arg_val '--some-arg' 'some-val' "$@"
+# set -- "${mongodHackedArgs[@]}"
+_mongod_hack_ensure_arg_val() {
+	local ensureArg="$1"; shift
+	local ensureVal="$1"; shift
+	_mongod_hack_ensure_no_arg_val "$ensureArg" "$@"
 	mongodHackedArgs+=( "$ensureArg" "$ensureVal" )
 }
 # TODO what do to about "--config" ? :(
@@ -158,7 +177,13 @@ if [ "$originalArgOne" = 'mongod' ]; then
 		pidfile="$(mktemp)"
 		trap "rm -f '$pidfile'" EXIT
 
-		_mongod_hack_ensure_arg_val --bind_ip 127.0.0.1 "$@"
+		# remove "--auth" and "--replSet" for our initial startup (see https://docs.mongodb.com/manual/tutorial/enable-authentication/#start-mongodb-without-access-control)
+		_mongod_hack_ensure_no_arg --auth "$@"
+		if [ "$MONGO_INITDB_ROOT_USERNAME" ] && [ "$MONGO_INITDB_ROOT_PASSWORD" ]; then
+			_mongod_hack_ensure_no_arg_val --replSet "${mongodHackedArgs[@]}"
+		fi
+
+		_mongod_hack_ensure_arg_val --bind_ip 127.0.0.1 "${mongodHackedArgs[@]}"
 		_mongod_hack_ensure_arg_val --port 27017 "${mongodHackedArgs[@]}"
 
 		sslMode="$(_mongod_hack_have_arg '--sslPEMKeyFile' "$@" && echo 'allowSSL' || echo 'disabled')" # "BadValue: need sslPEMKeyFile when SSL is enabled" vs "BadValue: need to enable SSL via the sslMode flag when using SSL configuration parameters"
@@ -214,12 +239,6 @@ if [ "$originalArgOne" = 'mongod' ]; then
 					roles: [ { role: 'root', db: $(jq --arg 'db' "$rootAuthDatabase" --null-input '$db') } ]
 				})
 			EOJS
-
-			mongo+=(
-				--username="$MONGO_INITDB_ROOT_USERNAME"
-				--password="$MONGO_INITDB_ROOT_PASSWORD"
-				--authenticationDatabase="$rootAuthDatabase"
-			)
 		fi
 
 		export MONGO_INITDB_DATABASE="${MONGO_INITDB_DATABASE:-test}"

(very interested in identifying more edge cases in this version, since it's something I'd be willing to accept as a PR if we can verify that most other currently-working use cases still work properly)

Hi, i think i have the same issues here, still very new to both docker and mongodb, i have a replica set all up and running, but i was going to enable auth mode on the replica set, so was just looking for the config file so i could add the line in to set the Key but there and switch it on, but could not find the conf file and i'm kinda stuck. not sure how to turn this on. any help / ideas ?

@adalga @yosifkit
Using dockerfile to execute a JS script successfully enables Mongo copy set permission settings.
The following code can be executed correctly

The contents of docker-compose.ym are as follows
content-mongo:
build:
context: ./content/docker/mongo
dockerfile: Dockerfile
volumes:
- /docker/content-mongo/db:/data/db
ports:
- 27018:27017

The contents of dockerfile are as follows
FROM mongo:3.4
COPY ./setup.js /etc/
RUN chmod +x /etc/setup.js
CMD ["/etc/setup.js"]

The contents of /etc/setup.js are as follows
#!/bin/bash
mongod --replSet replset0 --auth &
sleep 5
mongo admin<<EOF
var config = {
"_id": "replset0",
"members": [
{
"_id": 0,
"host": "192.168.1.233:27018"
}
]
};
rs.initiate(config)
EOF
sleep 3
mongo admin <<EOF
db.createUser({
user: "test",
pwd: "test",
roles: [ { role: "root", db: "admin" } ]
})
EOF
tail -f /dev/null

Where there is a problem, I hope to correct it. Thanks

@tianon - So, just to confirm, was the merge that closed this issue supposed to support replica sets with auth, or is the solution proposed by @ft0907 the recommended approach?

I have been able to get this to work when brewing my own similarly to the comment immediately above, but have not been able to do it with just additions to /docker-entrypoint-initdb.d as per the documentation. See https://github.com/PeterParker/replica-set-mongo-in-docker-with-auth as an example of this.

@PeterParker

the initial user must be configured without --replSet, but then the daemon must be restarted with --replSet in order to run rs.initiate().

The PR that closed this issue was not to let /docker-entrypoint-initdb.d do an rs.initiate with --auth (or the user/pass env vars). You need to have the --replSet flag set and the PR removed it for the temporary mongod that runs during /docker-entrypoint-initdb.d if you are trying to create a user. The point of the PR was to remove options that don't work when both are turned on with the empty database.

Even if you do an rs.initiate with the initdb.d scripts, it will not be correct: #339 (comment). (though it seems you have found a clever hack around that with extra_hosts: ["database-no-auth:127.0.0.1"] in your compose file 😍)

@yosifkit - Okay, thanks, that clarifies things. It looks like a custom entrypoint is the way forward.

Also, by the way, many thanks for the work you and others have done on these containers -- they're awesome.

Just for clarification, how would I go about creating a replicaset that also has a user in it?

# docker-compose.yaml
...
 mongo_1:
    image: mongo:latest
    deploy:
      replicas: 1
      update_config:
        parallelism: 1
      restart_policy:
        condition: on-failure
    environment:
      MONGO_INITDB_ROOT_USERNAME: "{{ mongo_root_username }}"
      MONGO_INITDB_ROOT_PASSWORD: "{{ mongo_root_password }}"
    volumes:
      - {{ mongo_1_data_dir }}:/data/db 
      - {{ token_dir }}:/tokens
    command: "mongod --replSet {{ mongo_replica_set_name }} --keyFile /tokens/{{ mongo_replica_set_name }}.key"
    ports: 
      - "{{ mongo_1_port }}:27017"

This creates a replica set with no users. I'm slightly confused at how to go about getting this to work. Can someone assist?

I've just spent 3h looking around to find out why this image cannot be setup correctly with both authentication and a replicaSet. That's why...

You may want to put a caveat in your documentation 🙃

I managed to create a replicaSet with an user on it by starting mongo as background process and then initiating the replicaSet and creating the user name.

services:
  mongodb:
    image : mongo:5.0.5
    container_name: mongodb
    command : [ "/bin/bash","-c", "chmod +x /conf/mongo-init.sh && /conf/mongo-init.sh"]
    environment:
      - PUID=1000
      - PGID=1000
    restart: always
    env_file:
      - ${ENVFILE}
    networks:
      - default
    ports:
      - 27017:27017
    volumes:
      - database:/data
      - mongo-conf:/conf
--- mongo-init.sh
#!/bin/bash

logfile="mongo.out"
expected_message="Waiting for connections"

chmod 400 /conf/mongodb.key
mongod --quiet --logpath $logfile --replSet rs0 --bind_ip_all --port 27017 --dbpath /data/db/ --shardsvr --auth --keyFile /conf/mongodb.key & 


while [ ! -f "$logfile" ]
do
    sleep 1 
done

while :
do
    if grep -q "$expected_message" "$logfile"; then
        echo "Found the expected message: $expected_message"
        break
    fi
    sleep 1 
done

mongo --eval '
  rs.initiate({
    _id: "rs0",
    version: 1,
    members: [
      { _id: 0, host: "localhost:27017" }
    ]
  })
'
sleep 5
mongo admin --eval "db.createUser({ user: '${MONGO_INITDB_ROOT_USERNAME}', pwd: '${MONGO_INITDB_ROOT_PASSWORD}', roles: [ { role: 'root', db: 'admin' } ] })"


tail -f $logfile