MongoDB Enterprise Advanced 3.4 a apporté une évolution majeure : la possibilité de gérer l'authentification ET les droits des utilisateurs totalement dans l'annuaire LDAP, sans avoir à créer les utilisateurs aussi dans la base system.users.

Le fonctionnement général est assez simple à comprendre :

  • Les utilisateurs sont déclarés comme objets user dans l'annuaire LDAP
  • Ils sont ensuite ajoutés en membres de groupes qui définissent leurs droits
  • A chaque groupe LDAP correspond un custom role MongoDB (dont le nom est le DN du groupe)
  • MongoDB se connecte à l'annuaire LDAP avec un compte déterminé, passé par le fichier de configuration YAML


Nous allons voir comment mettre en oeuvre ce modèle entre MongoDB 3.6 et Active Directory 2012. Pour faciliter l'analyse et la compréhension des mécanismes, nous activerons une autre fonctionnalité de MongoDB Enterprise Advanced: L'audit log.

Pour mémoire, MongoDB Enterprise Advanced est la version sous licence commerciale de MongoDB, et peut-être téléchargée à des fins d'évaluation (cf: https://www.mongodb.com/products/mongodb-enterprise-advanced)

1) Active Directory (LDAP à l'écoute sur le port TCP/389)

Domaine :

  • mdb.local (Netbios: MDB)
  • Domain controller : dc1.mdb.local


Compte MongoDB :

  • DN:CN=admmongodb,CN=Users,DC=mdb,DC=local


Compte utilisateur test 1 :

  • DN:CN=jean dupont,OU=paris,OU=offices,DC=mdb,DC=local
  • sAMAccountName:jdupont


Compte utilisateur test 2 :

  • DN:CN=paul lepain,OU=paris,OU=offices,DC=mdb,DC=local
  • sAMAccountName:plepain


Groupe donnant les droits (jdupont et plepain sont membres): CN=mongodbRO,OU=paris,OU=offices,DC=mdb,DC=local

2) Vérification de la connectivité LDAP
On utilise ldapsearch avec l'utilisateur/mot de passe créé pour la connexion de MongoDB à l'Active Directory via LDAP. Le paramètre -x permet de spécifier une authentification simple par id/pw.

user> ldapsearch -h dc1.mdb.local -D "admmongodb" -w MongoDB2017 -b "dc=mdb,dc=local"  -x

# extended LDIF
#
# LDAPv3
# base <dc=mdb,dc=local> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# mdb.local
dn: DC=mdb,DC=local
objectClass: top
objectClass: domain
objectClass: domainDNS
distinguishedName: DC=mdb,DC=local
(...)


3) Installation de MongoDB Enterprise Advanced 3.6


4) Vérification de l'installation :
On lance le mongo shell par la commande "mongo" sur la ligne de commande afin de vérifier que le serveur répond bien.

user> mongo

connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.0
MongoDB Enterprise >


5) Création de l'utilisateur administrateur de MongoDB :
Par défaut, MongoDB est accessible sans authentification. La création d'un premier user, puis le démarrage de mongod avec l'option d'authentification empêche l'accès sans authentification. La création d'un utilisateur requiert son id (user), son mot de passe (pwd) et ses rôles, qui sont un tableau de couples (role, db). Le rôle prédéfini "root" est le rôle super-user (tous les droits).

MongoDB Enterprise > use admin
switched to db admin
MongoDB Enterprise > db.createUser({user:'admin',pwd:'MongoDB2017',roles:[{role:'root',db:'admin'}]})'
Successfully added user: {
	"user" : "admin",
	"roles" : [
		{
			"role" : "root",
			"db" : "admin"
		}
	]
}


6) Création d'une base de test avec 2 documents :
On crée une base et une collection de test avec 2 documents, après s'être authentifé

MongoDB Enterprise > use admin
switched to db admin
MongoDB Enterprise > db.auth({user:'admin',pwd:'MongoDB2017'})
1
MongoDB Enterprise > use test
switched to db test
MongoDB Enterprise > db.coll.insert({foo:1})
WriteResult({ "nInserted" : 1 })
MongoDB Enterprise > db.coll.insert({foo:2})
WriteResult({ "nInserted" : 1 })


7) Création du rôle correspondant au groupe AD :
Afin d'attribuer des droits à des utilisateurs AD, on doit créer le custom role correspondant et lui donner le nom (champ "role") correspondant au DN du groupe dans l'Active Directory. Dans notre cas, on attribue au groupe le rôle prédéfini "read" sur la base "test". Ceci interdit toute autre opération que la lecture.

db.createRole({role:"CN=mongodbRO,OU=paris,OU=offices,DC=mdb,DC=local",privileges:[],roles:[{role:"read",db:"test"}]})


8) Configuration de MongoDB pour la connexion LDAP :
Il s'agit ensuite de relancer MongoDB avec tout le paramétrage nécessaire pour authentifier à la fois sur l'Active Directory mais aussi en local (pour l'accès de notre utilisateur admin), et activer l'audit log. Pour cela on crée un fichier au format YAML.

Fichier mongod.conf

net:
  port: 27017
processManagement:
  fork: false 
systemLog:
  destination: "file"
  path: "/data/dataldap/mongod.log"
  logAppend: true
storage:
  dbPath: "/data/dataldap"
security:
  authorization: enabled
  ldap:
    servers: "dc1.mdb.local"
    transportSecurity: "none"
    userToDNMapping:
    '[
     {
     match: "(.+)",
     ldapQuery: "OU=paris,OU=offices,DC=mdb,DC=local??sub?(sAMAccountName={0})"
     }
    ]'
    bind:
      queryUser: "admmongodb@mdb.local"
      queryPassword: "MongoDB2017"
    authz:
      queryTemplate: "{USER}?memberOf?base"
auditLog:
  destination: "file"
  format: "JSON"
  path: "/data/dataldap/auditLog.json"
setParameter: 
  authenticationMechanisms: 'PLAIN,SCRAM-SHA-1'
  auditAuthorizationSuccess: true


Quelques éléments clé :

  • userToDNMapping : détermine comment on construit le DN à partir de l'id utilisé pour se connecter à MongoDB. Dans notre cas, on demande à l'utilisateur d'utiliser son sAMAccountName
  • authz.queryTemplate : détermine comment on trouve les groupes auxquels appartient l'utilisateur. Dans le cas Active Directory, on utilise le champ calculé memberOf. {USER} est un placeholder remplacé à l'exécution par l'identifiant de l'utilisateur connecté
  • authenticationMechanisms : liste des modes d'authentification autorisés sur le serveur. PLAIN=LDAP, SCRAM-SHA-1=user/pw.


On relance ensuite MongoDB en utilisant ce fichier de configuration :

user> killall mongod
user> mongod -f mongod.conf


La log MongoDB montre la connexion LDAP réussie :

2017-12-14T15:48:18.013+0100 I CONTROL  [main] ***** SERVER RESTARTED *****
2017-12-14T15:48:18.348+0100 I CONTROL  [initandlisten] MongoDB starting : pid=50249 port=27017 dbpath=/data/dataldap 64-bit host=srv1.local
2017-12-14T15:48:18.348+0100 I CONTROL  [initandlisten] db version v3.6.0
(...)
2017-12-14T15:48:18.348+0100 I CONTROL  [initandlisten] options: { auditLog: { destination: "file", format: "JSON", path: "/data/dataldap/auditLog.json" }, config: "mongodldap.conf", net: { port: 27017 }, processManagement: { fork: false }, security: { authorization: "enabled", ldap: { authz: { queryTemplate: "{USER}?memberOf?base" }, bind: { queryPassword: "<password>", queryUser: "admmongodb@mdb.local" }, servers: "dc1.mdb.local", transportSecurity: "none", userToDNMapping: "[ { match: "(.+)", ldapQuery: "OU=paris,OU=offices,DC=mdb,DC=local??sub?(sAMAccountName={0})" } ]" } }, setParameter: { auditAuthorizationSuccess: "true", authenticationMechanisms: "PLAIN,SCRAM-SHA-1" }, storage: { dbPath: "/data/dataldap" }, systemLog: { destination: "file", logAppend: true, path: "/data/dataldap/mongod.log" } }
(...)
2017-12-14T15:48:18.858+0100 I ACCESS   [initandlisten] Server configured with LDAP Authorization. Spawned $external user cache invalidator.
2017-12-14T15:48:18.859+0100 I FTDC     [initandlisten] Initializing full-time diagnostic data capture with directory '/data/dataldap/diagnostic.data'
2017-12-14T15:48:18.860+0100 I NETWORK  [initandlisten] waiting for connections on port 27017


9) Connexion à MongoDB via l'authentification LDAP sur Active Directory :
L'authentification LDAP se codifie avec le switch "authenticationDatabase" défini à $external (Attention sous Unix, utiliser des guillemets simples pour ne pas déclencher le parsing de $external comme une variable système), et "authenticationMechanism" à PLAIN.

user> mongo --authenticationDatabase '$external' --authenticationMechanism PLAIN -u "jdupont" -p "MongoDB2017"

MongoDB shell version v3.6.0
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.0
MongoDB Enterprise > 


10) Verification des droits sur la base test :
Une recherche via findOne et une écriture via insert démontrent que l'on a bien uniquement les droits en lecture pour notre utilisateur jdupont.

MongoDB Enterprise > use test
switched to db test
MongoDB Enterprise > db.coll.findOne()
{
  "_id": ObjectId("5a2fe6a915e176ffc5c1900b"),
  "foo": 1
}


MongoDB Enterprise > db.coll.insert({foo:3})
not authorized on test to execute command { insert: "coll", ordered: true, $db: "test" }
WriteResult({
  "writeError": {
    "code": 13,
    "errmsg": "not authorized on test to execute command { insert: \"coll\", ordered: true, $db: \"test\" }"
  }
})


11) Visualisation de l'audit log :
Une extraction de l'audit log nous permet de retrouver les 2 opérations, et leur code (0=succès, 13=accès refusé). (résultat formatté via un JSON Tidy pour faciliter la lecture)

user> tail  /data/dataldap/auditLog.json

{  
   "atype":"authCheck",
   "ts":{  
      "$date":"2017-12-14T15:09:20.975+0100"
   },
   "local":{  
      "ip":"127.0.0.1",
      "port":27017
   },
   "remote":{  
      "ip":"127.0.0.1",
      "port":52187
   },
   "users":[  
      {  
         "user":"jdupont",
         "db":"$external"
      }
   ],
   "roles":[  
      {  
         "role":"read",
         "db":"test"
      },
      {  
         "role":"CN=mongodbRO,OU=paris,OU=offices,DC=mdb,DC=local",
         "db":"admin"
      }
   ],
   "param":{  
      "command":"find",
      "ns":"test.coll",
      "args":{  
         "find":"coll",
         "filter":{  

         },
         "limit":1,
         "singleBatch":true,
         "$db":"test"
      }
   },
   "result":0
}
{  
   "atype":"authCheck",
   "ts":{  
      "$date":"2017-12-14T15:09:52.758+0100"
   },
   "local":{  
      "ip":"127.0.0.1",
      "port":27017
   },
   "remote":{  
      "ip":"127.0.0.1",
      "port":52187
   },
   "users":[  
      {  
         "user":"jdupont",
         "db":"$external"
      }
   ],
   "roles":[  
      {  
         "role":"read",
         "db":"test"
      },
      {  
         "role":"CN=mongodbRO,OU=paris,OU=offices,DC=mdb,DC=local",
         "db":"admin"
      }
   ],
   "param":{  
      "command":"insert",
      "ns":"test.coll",
      "args":{  
         "insert":"coll",
         "ordered":true,
         "$db":"test",
         "documents":[  
            {  
               "_id":{  
                  "$oid":"5a328630c34bfc18d45e0d9c"
               },
               "foo":3
            }
         ]
      }
   },
   "result":13
}



En conclusion nous avons vu dans ce billet comment réaliser l'authentification et la gestion des droits dans MongoDB via une connexion Active Directory. A ce stade, il suffit d'ajouter des utilisateurs dans l'AD, de les ajouter au groupe mongodbRO dans l'AD pour qu'ils puissent se connecter à MongoDB et accéder à la base test en lecture.