MongoDB Mastery
MongoDB Mastery lessons / Module 27: Security

LDAP, Kerberos and x.509 authentication

Level 4: Database Engineer24 min

Connect self-hosted MongoDB to corporate identity: authenticate users against LDAP or Kerberos, map directory groups to MongoDB roles, and use x.509 certificates for services. Covers the $external database, configuration, testing and the failure modes that lock people out.

This lesson is interactive. Run every example against an in-browser MongoDB, see explain() plans drawn as trees, and take the quiz.
Open the interactive lesson
You will learn to
  • Explain the $external database and the PLAIN, GSSAPI and MONGODB-X509 mechanisms
  • Configure LDAP authentication and LDAP authorization with group-to-role mapping
  • Configure Kerberos with a keytab and connect with GSSAPI
  • Plan for directory outages and keep a local emergency account

conceptThe concept

Users created with db.createUser in a normal database are internal users with SCRAM credentials stored in MongoDB. External users live in the virtual $external database: MongoDB stores no password and asks another system to verify the identity.

  • LDAP (mechanism PLAIN): the client sends the username and password to mongod over TLS, and mongod verifies them by binding to the directory (or through saslauthd).
  • Kerberos (mechanism GSSAPI): the client presents a ticket from the KDC; mongod validates it with its service keytab. No password reaches mongod.
  • x.509 (mechanism MONGODB-X509): the client certificate's subject is the username. Good for services and for cluster members (security.clusterAuthMode: x509).

LDAP authorization goes one step further: instead of creating each user in $external with roles, mongod queries the directory for the user's groups (security.ldap.authz.queryTemplate) and grants the roles defined in the admin database whose names are the group DNs. Joining or leaving a directory group then changes database access with no change in MongoDB.

Editions: LDAP and Kerberos are available in MongoDB Enterprise and in Percona Server for MongoDB (free). MongoDB Community supports SCRAM and x.509 only.

whyWhy it matters

Security teams require central identity: one place to disable a leaver, enforce password policy and MFA, and audit access. Local database users created by hand are exactly what audits flag. Getting the mapping right also means DBAs stop creating and deleting users for every team change.

Use caseReal-world scenario

A bank runs Percona Server for MongoDB on its own hosts. Analysts log in with their Active Directory accounts over LDAP; their access comes from AD groups such as cn=mongo-readonly,ou=groups,dc=bank,dc=local, mapped to MongoDB roles. Batch jobs authenticate with Kerberos keytabs, cluster members use x.509, and one local SCRAM admin account, stored in the vault, exists for the day the directory is unreachable.

Try itExamples

Run and edit these in the interactive lesson.

External users have no password in MongoDB
const ext = db.getSiblingDB("$external")
if (!ext.getUser("asha@EXAMPLE.COM")) ext.createUser({ user: "asha@EXAMPLE.COM", roles: [{ role: "read", db: "shop" }] })
if (!ext.getUser("CN=billing-svc,OU=services,O=Example")) ext.createUser({ user: "CN=billing-svc,OU=services,O=Example", roles: [{ role: "readWrite", db: "shop" }] })
ext.getUsers().users.map(u => u.user)
The first is a Kerberos principal, the second an x.509 certificate subject. The user names must match exactly what the mechanism presents.
Map a directory group to MongoDB roles
const admin = db.getSiblingDB("admin")
const dn = "cn=mongo-dba,ou=groups,dc=example,dc=com"
if (!admin.getRoles().some(r => r.role === dn)) admin.createRole({ role: dn, privileges: [], roles: [{ role: "clusterMonitor", db: "admin" }, { role: "dbAdmin", db: "shop" }] })
admin.getRoles().filter(r => r.role.startsWith("cn=")).map(r => ({ role: r.role, grants: r.roles.map(x => x.role + "@" + x.db) }))
With LDAP authorization, members of that directory group receive these roles at login. No $external user is created for them.
LDAP authentication and authorization in mongod.conf
security:
  authorization: enabled
  ldap:
    servers: "ldap1.example.com,ldap2.example.com"
    transportSecurity: tls
    bind:
      method: simple
      queryUser: "cn=mongo-bind,ou=services,dc=example,dc=com"
      queryPassword:
        __exec: "/usr/local/bin/read-secret ldap-bind"   # keep the secret out of the file
    userToDNMapping: '[{ match: "(.+)", substitution: "uid={0},ou=people,dc=example,dc=com" }]'
    authz:
      queryTemplate: "{USER}?memberOf?base"
setParameter:
  authenticationMechanisms: "PLAIN,SCRAM-SHA-256"   # keep SCRAM for the emergency admin

# connect as a directory user
mongosh "mongodb://asha@db1.example.com:27017/?authMechanism=PLAIN&authSource=%24external&tls=true"
Kerberos: keytab, ticket and connection string
# on the server: principal mongodb/db1.example.com@EXAMPLE.COM in the keytab
KRB5_KTNAME=/etc/mongodb/mongodb.keytab mongod --config /etc/mongod.conf
#   setParameter:
#     authenticationMechanisms: "GSSAPI,SCRAM-SHA-256"

# on the client
kinit asha@EXAMPLE.COM
mongosh "mongodb://asha%40EXAMPLE.COM@db1.example.com:27017/?authMechanism=GSSAPI&authSource=%24external&tls=true"
x.509 for a service
# the certificate subject must match the $external user exactly
openssl x509 -in billing-svc.pem -noout -subject -nameopt RFC2253
# subject=CN=billing-svc,OU=services,O=Example

mongosh "mongodb://db1.example.com:27017/?authMechanism=MONGODB-X509&authSource=%24external&tls=true&tlsCertificateKeyFile=billing-svc.pem&tlsCAFile=ca.pem"
Who am I and which roles did I get?
db.runCommand({ connectionStatus: 1 })
On a real server this shows the authenticated user and every role, including roles granted through LDAP groups. It is the first thing to check when a directory user gets "not authorized".

internalsUnder the hood

With LDAP, security.ldap.userToDNMapping turns the login name into a DN (for example asha into uid=asha,ou=people,dc=example,dc=com), and the authorization query returns group DNs. Results are cached; ldapUserCacheInvalidationInterval (30 seconds by default) controls how quickly group changes take effect. Every new connection that authenticates can cause an LDAP round trip, so connection storms become directory load: keep connection pools warm and point mongod at more than one LDAP server.

Kerberos needs a service principal mongodb/<fqdn>@REALM in the keytab, forward and reverse DNS that match the host name clients use, and clocks within the KDC's allowed skew (five minutes by default).

Avoid / preferDo this, not that

Avoid
setParameter:
  authenticationMechanisms: "PLAIN"
Prefer
setParameter:
  authenticationMechanisms: "PLAIN,SCRAM-SHA-256"
# plus one local admin user whose password lives in the vault

If the directory is unreachable (network change, expired bind password, certificate rotation), PLAIN-only means nobody can log in to fix anything.

Avoid
ldap:
  servers: "ldap.example.com"
  transportSecurity: none
Prefer
ldap:
  servers: "ldap1.example.com,ldap2.example.com"
  transportSecurity: tls

Without TLS, user passwords travel from mongod to the directory in clear text, and a single server makes every login depend on one host.

PitfallsCommon mistakes

  • Enabling PLAIN without TLS. The password travels to mongod in clear text.
  • Removing SCRAM from authenticationMechanisms, so nobody can log in when the directory is down.
  • Naming the role after the group's short name instead of the full DN returned by the authorization query.
  • Connecting to Kerberos-enabled servers by IP address or an alias, so the service principal does not match and authentication fails.

LimitsWhen not to use it

For a small team or a single application, SCRAM users with strong generated passwords and x.509 for services may be enough, and they work in Community Edition. Directory integration adds a dependency on the directory's availability and latency; it pays off when there are many human users or strict joiner-and-leaver requirements.

SecuritySecurity notes

The LDAP bind account should only be able to read users and groups. Store its password with a config expansion (__exec or __rest) rather than in mongod.conf. Protect keytabs like private keys (owner mongod, mode 600). Enable auditing of authentication events so failed logins from directory users are visible.

ChallengeTry it yourself

Create (only if missing) an admin role for the directory group cn=mongo-readonly,ou=groups,dc=example,dc=com that grants read on shop, then return that role's inherited roles.

Show one solution
One solution
const admin = db.getSiblingDB("admin")
const dn = "cn=mongo-readonly,ou=groups,dc=example,dc=com"
if (!admin.getRoles().some(r => r.role === dn)) admin.createRole({ role: dn, privileges: [], roles: [{ role: "read", db: "shop" }] })
admin.getRoles().filter(r => r.role === dn)[0].roles

InterviewQuestions you should be able to answer

How do you integrate self-hosted MongoDB with Active Directory?

Use MongoDB Enterprise or Percona Server for MongoDB. Configure security.ldap with several AD servers over TLS, a least-privilege bind account whose password comes from a secret store, userToDNMapping for the login format, and authz.queryTemplate to read group membership. Create roles in admin named after the group DNs with the privileges each group needs. Enable PLAIN plus SCRAM, keep one vaulted local admin for directory outages, require TLS on client connections, and test with a real user before rolling out, then watch connection authentication latency because each new connection may query AD.

An LDAP user can log in but gets "not authorized" on every command. What do you check?

Run db.runCommand({ connectionStatus: 1 }) as that user: if no roles appear, authorization mapping is the problem. Check that the authorization query returns the expected group DNs (MongoDB Enterprise ships mongoldap to test the configuration), that a role named exactly with that DN exists in admin (case and spacing matter), and that the user really is in the group. Remember the user cache: group changes can take up to ldapUserCacheInvalidationInterval to apply.

QuizCheck your understanding

5 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.