MongoDB replica set authentication guide

1. Create replica set

Launch EC2 instance & install mongoDB

You have to launch at least 3 EC2 instances. Make sure those 3 instances can connect to each other (set up ssh key, ssh config and check security groups). Use ansibe playbook (role XX.mongodb) to install mongoDB

Create mongoDB replica set

  1. In the first instance:
mongo

When mongo shell appear, run:

rs.status()
rs.initiate()

MongoDB will make the current EC2 instance become PRIMARY

  1. Add members to the replica set:
rs.add( { host: "ip-172-31-37-61.ap-northeast-1.compute.internal:27017" } )
rs.add( { host: "ip-172-31-44-98.ap-northeast-1.compute.internal:27017" } )
  1. Try to connect to the replica set:
mongo --host mongo-002
mongo --host mongo-003

Note that mongo-002 and mongo-003 is the DNS of the 2 private-IP instances: 172.31.37.61 and 172.31.44.98 (you can set alias in /etc/hosts)

If success ⇒ OK!

You can log in to mongo shell and see the status of the replica set. Example of result of rs.status() command:

rsCMS:PRIMARY> rs.status()
{
        "set" : "rsCMS",
        "date" : ISODate("2021-07-19T10:35:35.903Z"),
        "myState" : 1,
        "term" : NumberLong(3),
        "syncingTo" : "",
        "syncSourceHost" : "",
        "syncSourceId" : -1,
        "heartbeatIntervalMillis" : NumberLong(2000),
        "optimes" : {
                "lastCommittedOpTime" : {
                        "ts" : Timestamp(1626690933, 1),
                        "t" : NumberLong(3)
                },
                "readConcernMajorityOpTime" : {
                        "ts" : Timestamp(1626690933, 1),
                        "t" : NumberLong(3)
                },
                "appliedOpTime" : {
                        "ts" : Timestamp(1626690933, 1),
                        "t" : NumberLong(3)
                },
                "durableOpTime" : {
                        "ts" : Timestamp(1626690933, 1),
                        "t" : NumberLong(3)
                }
        },
        "lastStableCheckpointTimestamp" : Timestamp(1626690893, 1),
        "electionCandidateMetrics" : {
                "lastElectionReason" : "electionTimeout",
                "lastElectionDate" : ISODate("2021-07-19T09:52:53.446Z"),
                "electionTerm" : NumberLong(3),
                "lastCommittedOpTimeAtElection" : {
                        "ts" : Timestamp(0, 0),
                        "t" : NumberLong(-1)
                },
                "lastSeenOpTimeAtElection" : {
                        "ts" : Timestamp(1626685293, 1),
                        "t" : NumberLong(2)
                },
                "numVotesNeeded" : 2,
                "priorityAtElection" : 1,
                "electionTimeoutMillis" : NumberLong(10000),
                "numCatchUpOps" : NumberLong(0),
                "newTermStartDate" : ISODate("2021-07-19T09:52:53.452Z"),
                "wMajorityWriteAvailabilityDate" : ISODate("2021-07-19T09:52:54.774Z")
        },
        "members" : [
                {
                        "_id" : 0,
                        "name" : "ip-172-31-43-145.ap-northeast-1.compute.internal:27017",
                        "health" : 1,
                        "state" : 1,
                        "stateStr" : "PRIMARY",
                        "uptime" : 2795,
                        "optime" : {
                                "ts" : Timestamp(1626690933, 1),
                                "t" : NumberLong(3)
                        },
                        "optimeDate" : ISODate("2021-07-19T10:35:33Z"),
                        "syncingTo" : "",
                        "syncSourceHost" : "",
                        "syncSourceId" : -1,
                        "infoMessage" : "",
                        "electionTime" : Timestamp(1626688373, 1),
                        "electionDate" : ISODate("2021-07-19T09:52:53Z"),
                        "configVersion" : 5,
                        "self" : true,
                        "lastHeartbeatMessage" : ""
                },
                {
                        "_id" : 1,
                        "name" : "ip-172-31-44-98.ap-northeast-1.compute.internal:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 2416,
                        "optime" : {
                                "ts" : Timestamp(1626690933, 1),
                                "t" : NumberLong(3)
                        },
                        "optimeDurable" : {
                                "ts" : Timestamp(1626690933, 1),
                                "t" : NumberLong(3)
                        },
                        "optimeDate" : ISODate("2021-07-19T10:35:33Z"),
                        "optimeDurableDate" : ISODate("2021-07-19T10:35:33Z"),
                        "lastHeartbeat" : ISODate("2021-07-19T10:35:34.142Z"),
                        "lastHeartbeatRecv" : ISODate("2021-07-19T10:35:34.742Z"),
                        "pingMs" : NumberLong(0),
                        "lastHeartbeatMessage" : "",
                        "syncingTo" : "ip-172-31-37-61.ap-northeast-1.compute.internal:27017",
                        "syncSourceHost" : "ip-172-31-37-61.ap-northeast-1.compute.internal:27017",
                        "syncSourceId" : 2,
                        "infoMessage" : "",
                        "configVersion" : 5
                },
                {
                        "_id" : 2,
                        "name" : "ip-172-31-37-61.ap-northeast-1.compute.internal:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 2570,
                        "optime" : {
                                "ts" : Timestamp(1626690933, 1),
                                "t" : NumberLong(3)
                        },
                        "optimeDurable" : {
                                "ts" : Timestamp(1626690933, 1),
                                "t" : NumberLong(3)
                        },
                        "optimeDate" : ISODate("2021-07-19T10:35:33Z"),
                        "optimeDurableDate" : ISODate("2021-07-19T10:35:33Z"),
                        "lastHeartbeat" : ISODate("2021-07-19T10:35:34.142Z"),
                        "lastHeartbeatRecv" : ISODate("2021-07-19T10:35:35.483Z"),
                        "pingMs" : NumberLong(0),
                        "lastHeartbeatMessage" : "",
                        "syncingTo" : "ip-172-31-43-145.ap-northeast-1.compute.internal:27017",
                        "syncSourceHost" : "ip-172-31-43-145.ap-northeast-1.compute.internal:27017",
                        "syncSourceId" : 0,
                        "infoMessage" : "",
                        "configVersion" : 5
                }
        ],
        "ok" : 1,
        "operationTime" : Timestamp(1626690933, 1),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1626690933, 1),
                "signature" : {
                        "hash" : BinData(0,"hzd0+nqjoPVXbOcLPn1wM0sZiJM="),
                        "keyId" : NumberLong("6986536417509769219")
                }
        }
}
rsCMS:PRIMARY>
  1. Insert some sample data:
db.products.insert( { item: "card", qty: 15 } )

2. Create replica set authentication

1. Create keyfile:

cd /srv/
sudo mkdir mongodb
openssl rand -base64 756 > /srv/mongodb/keyfile
chmod 400 /srv/mongodb/keyfile
sudo chown mongod /srv/mongodb/keyfile

Note: You have to copy the keyfile that you have generated to each replica set member (also have to chmod 400 and chown mongod on member servers)

  1. Run ansible copy toi /nfs/

2. Create superuser:

Note: You have to stand on the Primary server to do this!

You have to create user root before you enable access control for the replica set. To create other users, you have to log into user root!

Log in to the mongo shell

mongo

and run:

admin = db.getSiblingDB("admin")
admin.createUser(
  {
    user: "root",
    pwd: "root",
    roles: [ { role: 'root', db: 'admin' } ]
  }
)

3. Shutdown all the members of the replica set:

  • SSH to the MongoDB servers

  • Run: sudo systemctl stop mongod

4. Modify the /etc/mongod.conf file:

You have to add security.keyFile option to the replica set:

# mongod.conf

# for documentation of all options, see:
#   <http://docs.mongodb.org/manual/reference/configuration-options/>

# where to write logging data.
systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log

# Where and how to store data.
storage:
  dbPath: /var/lib/mongo
  journal:
    enabled: true
#  engine:
#  mmapv1:
#  wiredTiger:

# how the process runs
processManagement:
  fork: true  # fork and run in background
  pidFilePath: /var/run/mongodb/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo

# network interfaces
net:
  port: 27017
#  bindIp: 127.0.0.1  # Enter 0.0.0.0,:: to bind to all IPv4 and IPv6 addresses or, alternatively, use the net.bindIpAll setting.
  bindIp: 0.0.0.0

#security:
security:
   authorization: enabled
   keyFile: /srv/mongodb/keyfile

#operationProfiling:

replication:
  replSetName: "rsCMS"

#sharding:

## Enterprise-Only Options

#auditLog:

#snmp:

5. Start mongod again:

SSH to your mongodb servers and run:

sudo systemctl start mongod

6. Log into the mongo shell with root user:

You now can log into mongoDB using root user:

mongo -u "root" -p  --authenticationDatabase "admin"

Note: With users that authenticationDatabase is admin, –authenticationDatabase “admin” is optional

3. Create users:

1. Create user same as root but can not access to config and local:

admin = db.getSiblingDB("admin")
admin.createUser(
  {
    user: "adminrw",
    pwd: "adminrw",
    roles: [ { role: "readWriteAnyDatabase", db: "admin" } ]
  }
)

2. Create DB read only user (but can not read config and local db):

admin = db.getSiblingDB("admin")
admin.createUser(
  {
    user: "read",
    pwd: "read",
    roles: [ { role: "readAnyDatabase", db: "admin" } ]
  }
)

3. Create Database – level access control user:

use testDB1
db.createUser(
  {
    user: "admin1",
    pwd: "admin1",
    roles: [ { role: "readWrite", db: "testDB1" } ]
  }
)

When log in:

mongo -u "admin1" -p  --authenticationDatabase "testDB1"

Note: –authenticationDatabase “testDB1” is neccessary. If you do not specify ⇒ can not authenticate.

4. Create collection – level access control user:

  1. Create user – defined role:
use admin
db.createRole(
   {
     role: "manageOpRole", 
     privileges: [
       { resource: { db: "test", collection: "products" }, actions: [ "find", "update", "insert" ] },
       { resource: { db: "testDB1", collection: "user" }, actions: [ "find", "update" ] },
       { resource: { db: "testDB2", collection: "" }, actions: [ "listCollections" ] }
     ],
     roles: []
   }
)
  1. Create user and attach role:
admin = db.getSiblingDB("admin")
admin.createUser(
  {
    user: "cadmin2",
    pwd: "cadmin2",
    roles: [ { role: "manageOpRole", db: "admin" } ]
  }
)

4. Testing

Note: In our case, mongo-003 is primary and 001, 002 are secondaries.

Case 1: Log in to mongo shell and do not provide any authentication data ⇒ Can not find db information

Even in the case that you know that there are dbs that exist:

You can not interact with dbs

Case 2: Log into mongo shell, provide wrong authentication data:

  • Wrong password:

  • Wrong username:

Case 3: Log into mongo shell with correct authentication data:

Log into mongo shell with correct authentication data on secondaries:

Case 4: Read Write any database but not config and local:

  • When log into by root user:

  • Log in by root user in secondaries:

  • Log into by adminrw user:

Case 5: Read only but can not read local and config DB: (log in to by read user)

Case 6: Access control for only one DB only: (Log into by admin1 user)

  • If connect to wrong DB:

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e3c16488-adae-4752-b9ae-bc32fe23ffcc/Untitled.png

  • Connect to right DB:

Case 7: Create collection – level access control: