Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

replica set with auth #211

Closed
adalga opened this issue Nov 3, 2017 · 14 comments
Closed

replica set with auth #211

adalga opened this issue Nov 3, 2017 · 14 comments

Comments

@adalga
Copy link

adalga commented Nov 3, 2017

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.

@lgaticaq
Copy link

Same mistake

@tianon
Copy link
Member

tianon commented Nov 13, 2017

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. 😞

@lgaticaq
Copy link

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

@tianon
Copy link
Member

tianon commented Nov 14, 2017

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).

@tianon
Copy link
Member

tianon commented Nov 14, 2017

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)

@r2690698
Copy link

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 ?

@ft0907
Copy link

ft0907 commented Jul 10, 2018

@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

@PeterParker
Copy link

@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.

@yosifkit
Copy link
Member

@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 😍)

@PeterParker
Copy link

@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.

@coltenkrauter
Copy link

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?

@StMarian
Copy link

@coltenkrauter this might help

@CorentinDeBoisset
Copy link

CorentinDeBoisset commented Feb 7, 2023

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 🙃

@pedrocwb
Copy link

pedrocwb commented Sep 25, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests