7b83094 May 13, 2016
@craiggwilson @ehershey
523 lines (338 sloc) 20.9 KB

Driver Authentication

Spec:100
Title:Driver Authentication
Author: Craig Wilson
Advisors:Andy Schwerin, Bernie Hacket, Jeff Yemin, David Golden
Status: Accepted
Type:Standards
Minimum Server Version:1.8
Last Modified:February 2nd, 2015

Abstract

MongoDB supports various authentication strategies across various versions. When authentication is turned on in the database, a driver must authenticate before it is allowed to communicate with the server. This spec defines when and how a driver performs authentication with a MongoDB server.

META

The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

References

Server Discovery and Monitoring

Specification

Definitions

Credential
The pieces of information used to establish the authenticity of a user. This is composed of an identity and some form of evidence such as a password or a certificate.
FQDN
Fully Qualified Domain Name
Mechanism
A SASL implementation of a particular type of credential negotiation.
Source
The authority used to establish credentials and/or privileges in reference to a mongodb server. In practice, it is the database to which sasl authentication commands are sent.
Realm
The authority used to establish credentials and/or privileges in reference to GSSAPI.
SASL
Simple Authentication and Security Layer - RFC 4422

Client Implementation

MongoCredential

Drivers SHOULD contain a type called MongoCredential. It SHOULD contain some or all of the following information.

username (string)
  • Applies to all mechanisms.
source (string)
  • Applies to all mechanisms.
  • Always '$external' for GSSAPI, MONGODB-X509, and PLAIN.
  • This is the database to which the authenticate command will be sent.
  • This is the database to which sasl authentication commands will be sent.
password (string)
  • Does not apply to all mechanisms.
mechanism (string)
  • Indicates which mechanism to use with the credential.
mechanism_properties
  • Includes additional properties for the given mechanism.
Errors

Drivers SHOULD raise an error as early as possible when detecting invalid values in a credential. For instance, if a mechanism_property is specified for MONGODB-CR, the driver should raise an error indicating that the property does not apply.

Naming

Naming of this information MUST be idiomatic to the driver's language/framework but still remain consistent. For instance, python would use "mechanism_properties" and .NET would use "MechanismProperties".

Naming of mechanism properties MUST be case-insensitive. For instance, SERVICE_NAME and service_name refer to the same property.

Authentication

This section augments the Server Discovery and Monitoring Spec.

A MongoClient instance MUST be considered a single logical connection to the server/deployment. Hence, all credentials given to an instance of a MongoClient should apply to every currently opened socket. Drivers SHOULD require all credentials to be specified upon construction of the MongoClient. This is defined as eager authentication and drivers MUST support this mode.

Connection Handshake

Drivers MUST consider a server Unknown if authentication fails. Effectively, an authentication failure is equivalent to a network or socket error in that we have failed to establish a connection with the server. The steps to support this are below:

  1. If credentials exist
    1. Upon opening a socket, drivers MUST send an isMaster command immediately. This allows a driver to determine whether the server is an Arbiter.

    2. A driver MUST perform authentication with all supplied credentials for the following server types as defined in the Server Discovery and Monitoring Specification.

      • Standalone
      • Mongos
      • RSPrimary
      • RSSecondary
    3. A single invalid credential is the same as all credentials being invalid.

Lazy Authentication

Some drivers need to support lazy authentication for backwards compatibility. A credential cache MUST be employed to handle authentication within a MongoClient. When a user has requested authentication against a particular database, those credentials MUST be remembered. When a new socket is created, all the existing authentications MUST be applied to the new socket. In addition, when an existing socket is checked out, any authentications that have taken place since its last use MUST also be applied. Should a user request authentication with different credentials against a database that already exists in the credential cache, an error MUST be raised.

db = client.getDB("foo")

## this will send the authentication against the "foo" database
db.auth(user: "user1", password: "password")

## this should NOT raise an error because the credential is the same against the "foo" database
db.auth(user: "user1", password: "password")

## this should raise an error as the credential is different
db.auth(user: "user2", password: "password")

## this should also raise an error even though the "db" instance we are working with is not
## the "foo" database, "foo" is the database the authentication should be tested against.
db = client.getDB("bar")
db.auth(user: "user2", password: "password", source: "foo")

## logout allows the user to log in to a database with a different credential
db = db.client.getDB("foo");
db.logout();
db.auth(user: "user2", password: "password")

In addition, drivers supporting lazy authentication may need to support logout as well. In practice, it works exactly the opposite of authenticate. When logout is called, those credentials MUST be forgotten. When an existing socket is checked out, any forgotten credential must be de-authenticated on that socket.

If the initial authentication fails, an error SHOULD be raised and the credentials SHOULD NOT be added to the credential cache. However, when authentication fails using credentials from the credential cache, all open connections MUST be closed and the server type set to Unknown.

Supported Authentication Methods

Defaults

since:3.0

If the user did not provide a mechanism via the connection string or via code, SCRAM-SHA-1 MUST be used when talking to servers >= 3.0. Prior to server 3.0, MONGODB-CR MUST be used.

When a user has specified a mechanism, regardless of the server version, the driver MUST honor this and attempt to authenticate.

Determining Server Version

Some drivers use the buildinfo command to determine server version. Occasionally, it might be enough to check the wire version. Checking the wire version is only possible when the server has bumped it in accordance with what needs to be checked.

For instance, checking the wire version to determine whether or not the server supports SCRAM-SHA-1 is only possible if the server bumps the wire version when they release server 3.0.

MongoDB Custom Mechanisms

MONGODB-CR
since:1.4
deprecated:3.0

MongoDB Challenge Response is a nonce and MD5 based system. The driver sends a getNonce command, encodes and hashes the password using the returned nonce, and then sends an authenticate command.

Conversation
  1. Send getNonce command
    • { getNonce: 1 }
    • Response: { nonce: <nonce> }
  2. Compute key
    • passwordDigest = HEX( MD5( UTF8( username + ':mongo:' + password )))
    • key = HEX( MD5( UTF8( nonce + username + passwordDigest )))
  3. Send authenticate command
    • { authenticate: 1, nonce: nonce, user: username, key: key }

As an example, given a username of "user" and a password of "pencil", the conversation would appear as follows:

C: {getnonce : 1}
S: {nonce: "2375531c32080ae8", ok: 1}
C: {authenticate: 1, user: "user", nonce: "2375531c32080ae8", key: "21742f26431831d5cfca035a08c5bdf6"}
S: {ok: 1}
MongoCredential Properties
username
MUST be specified.
source
MUST be specified.
password
MUST be specified.
mechanism
MUST be "MONGODB-CR"
mechanism_properties
MUST NOT be specified.
MONGODB-X509
since:2.6

MONGODB-X509 is the usage of X-509 certificates to validate a client. The server will use the distinguished subject name of the client certificate in the SSL negotiation to authenticate. The driver will be required to supply the distinguished subject name outside of the SSL negotiation to the server using the "authenticate" command.

Conversation
  1. Send authenticate command
    • username = openssl x509 -in client.pem -inform PEM -subject -nameopt RFC2253
    • { authenticate: 1, user: username, mechanism: "MONGODB-X509" }

As an example, given a certificate with the RFC2253 subject of "CN=client,OU=kerneluser,O=10Gen,L=New York City,ST=New York,C=US", the conversation would appears as follows:

C: {authenticate: 1, mechanism: "MONGODB-X509", user: "CN=client,OU=kerneluser,O=10Gen,L=New York City,ST=New York,C=US"}
S: {ok: 1}
MongoCredential Properties
username
MUST be specified as RFC2253 form.
source
MUST be $external.
password
MUST NOT be specified.
mechanism
MUST be "MONGODB-X509"
mechanism_properties
MUST NOT be specified.

TODO: Errors

SASL Mechanisms

since:2.4 enterprise

SASL mechanisms are all implemented using the same sasl commands and interpreted as defined by the SASL specification RFC 4422.

  1. Send the saslStart command.
    • { saslStart: 1, mechanism: <mechanism_name>, payload: BinData(...), autoAuthorize: 1 }

    • Response: { conversationId: <number>, code: <code>, done: <boolean>, payload: <payload> }
      • conversationId: the conversation identifier. This will need to be remembered and used for the duration of the conversation.
      • code: A response code that will indicate failure. This field is not included when the command was successful.
      • done: a boolean value indicating whether or not the conversation has completed.
      • payload: a sequence of bytes or a base64 encoded string (depending on input) to pass into the SASL library to transition the state machine.
  2. Continue with the saslContinue command while done is false.
    • { saslContinue: 1, conversationId: conversationId, payload: BinData(...) }
    • Response is the same as that of saslStart

Many languages will have the ability to utilize 3rd party libraries. The server uses cyrus-sasl and it would make sense for drivers with a choice to also choose cyrus. However, it is important to ensure that when utilizing a 3rd party library it does implement the mechanism on all supported OS versions and that it interoperates with the server. For instance, the cyrus sasl library offered on RHEL 6 does not implement SCRAM-SHA-1. As such, if your driver supports RHEL 6, you'll need to implement SCRAM-SHA-1 from scratch.

GSSAPI
since:

2.4 enterprise

2.6 enterprise on windows

GSSAPI is kerberos authentication as defined in RFC 4752. Microsoft has a proprietary implementation called SSPI which is compatible with both windows and linux clients.

MongoCredential properties:

username
MUST be specified.
source
MUST be "$external"
password
MAY be specified.
mechanism
MUST be "GSSAPI"
mechanism_properties
SERVICE_NAME
Drivers MUST allow the user to specify a different service name. The default is "mongodb".
CANONICALIZE_HOST_NAME
Drivers MAY allow the user to request canonicalization of the hostname. This might be required when the hosts report different hostnames than what is used in the kerberos database. The default is "false".
SERVICE_REALM
Drivers MAY allow the user to specify a different realm for the service. This might be necessary to support cross-realm authentication where the user exists in one realm and the service in another.
PLAIN
since:2.6 enterprise

The PLAIN mechanism, as defined in RFC 4616, is used in MongoDB to perform LDAP authentication. It cannot be used to perform any other type of authentication. Since the credentials are stored outside of MongoDB, the $external database must be used for authentication.

Conversation

As an example, given a username of "user" and a password of "pencil", the conversation would appear as follows:

C: {saslStart: 1, mechanism: "PLAIN", payload: BinData(0, "AHVzZXIAcGVuY2ls")}
S: {conversationId: 1, payload: BinData(0,""), done: true, ok: 1}

If your sasl client is also sending the authzid, it would be "user" and the conversation would appear as follows:

C: {saslStart: 1, mechanism: "PLAIN", payload: BinData(0, "dXNlcgB1c2VyAHBlbmNpbA==")}
S: {conversationId: 1, payload: BinData(0,""), done: true, ok: 1}

MongoDB supports either of these forms.

MongoCredential Properties
username
MUST be specified.
source
MUST be $external.
password
MUST be specified.
mechanism
MUST be "PLAIN"
mechanism_properties
MUST NOT be specified.
SCRAM-SHA-1
since:3.0

SCRAM-SHA-1 is defined in RFC 5802.

Page 8 of the RFC identifies the "SaltedPassword" as := Hi(Normalize(password), salt, i). The password variable MUST be the mongodb hashed variant. The mongo hashed variant is computed as hash = HEX( MD5( UTF8( username + ':mongo:' + plain_text_password ))), where plain_text_password is actually plain text. For example, to compute the ClientKey according to the RFC:

// note that "salt" and "i" have been provided by the server
function computeClientKey(username, plain_text_password) {
        mongo_hashed_password = HEX( MD5( UTF8( username + ':mongo:' + plain_text_password )));
        saltedPassword  = Hi(Normalize(mongo_hashed_password), salt, i);
        clientKey = HMAC(saltedPassword, "Client Key");
}

In addition, SCRAM-SHA-1 requires that a client create a randomly generated nonce. It is imperative, for security sake, that this be as secure and truly random as possible. For instance, java provides both a Random class as well as a SecureRandom class. SecureRandom is cryptographically generated while Random is just a pseudo-random generator with predictable outcomes.

Conversation

As an example, given a username of "user" and a password of "pencil" and an r value of "fyko+d2lbbFgONRv9qkxdawL", the scram conversation would appear as follows:

C: n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
S: r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,s=rQ9ZY3MntBeuP3E1TDVC4w==,i=10000
C: c=biws,r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,p=MC2T8BvbmWRckDw8oWl5IVghwCY=
S: v=UMWeI25JD1yNYZRMpZ4VHvhZ9e0=

This same conversation over mongodb's sasl implementation would appear as follows:

C: {saslStart: 1, mechanism: "SCRAM-SHA-1", payload: BinData(0, "biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM")}
S: {conversationId : 1, payload: BinData(0,"cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0xIbytWZ2s3cXZVT0tVd3VXTElXZzRsLzlTcmFHTUhFRSxzPXJROVpZM01udEJldVAzRTFURFZDNHc9PSxpPTEwMDAw"), done: false, ok: 1}
C: {saslContinue: 1, conversationId: 1, payload: BinData(0, "Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMSG8rVmdrN3F2VU9LVXd1V0xJV2c0bC85U3JhR01IRUUscD1NQzJUOEJ2Ym1XUmNrRHc4b1dsNUlWZ2h3Q1k9")}
S: {conversationId: 1, payload: BinData(0,"dj1VTVdlSTI1SkQxeU5ZWlJNcFo0Vkh2aFo5ZTA9"), done: false, ok: 1}
C: {saslContinue: 1, conversationId: 1, payload: BinData(0, "")}
S: {conversationId: 1, payload: BinData(0,""), done: true, ok: 1}

Note

There is an extra round trip due to an implementation decision on the server. This is accomplished by sending no bytes back to the server for what is effectively a no-op.

MongoCredential Properties
username
MUST be specified.
source
MUST be specified.
password
MUST be specified.
mechanism
MUST be "SCRAM-SHA-1"
mechanism_properties
MUST NOT be specified.

Connection String Options

mongodb://[username[:password]@]host1[:port1][,[host2:[port2]],...[hostN:[portN]]][/database][?options]

Auth Related Options

authMechanism

MONGODB-CR, MONGODB-X509, GSSAPI, PLAIN, SCRAM-SHA-1

Sets the Mechanism property on the MongoCredential. The default is MONGODB-CR if <= 2.6, otherwise SCRAM-SHA-1.

authSource
Sets the Source property on the MongoCredential. This overrides the database name on the connection string for where authentication occurs. The default is admin.
authMechanismProperties=PROPERTY_NAME:PROPERTY_VALUE,PROPERTY_NAME2:PROPERTY_VALUE2

A generic method to set mechanism properties in the connection string.

For example, to set REALM and CANONICALIZE_HOST_NAME, the option would be authMechanismProperties=CANONICALIZE_HOST_NAME:true,SERVICE_REALM:AWESOME.

gssapiServiceName (deprecated)
An alias for authMechanismProperties=SERVICE_NAME:mongodb.

Implementation

  1. Credentials MAY be specified in the connection string immediately after the scheme separator "//".

  2. A realm MAY be passed as a part of the username in the url. It would be something like dev@MONGODB.COM, where dev is the username and MONGODB.COM is the realm. Per the RFC, the @ symbol should be url encoded using %40.
    • When GSSAPI is specified, this should be interpretted as the realm.
    • When non-GSSAPI is specified, this should be interpetted as part of the username.
  3. It is permissible for only the username to appear in the connection string. This would be identified by having no colon follow the username before the '@' hostname separator.

  4. The source is determined by the following:
    • if authSource is specified, it is used.
    • otherwise, if database is specified, it is used.
    • otherwise, the admin database is used.

Test Plan

Tests have been defined in the associated files:

Backwards Compatibility

There should be no backwards compatibility concerns. Drivers currently supporting late-bound authentication only should be able to migrate to eager authentication while still allowing lazy authentication.

Reference Implementation

The .NET driver currently uses eager authentication and abides by this specification. The Java driver abides by this specification and uses a mix of eager and lazy authentication.

Q & A

Q: According to Connection Handshake, we are calling isMaster for every socket. Isn't this a lot?
Drivers should be pooling connections and, as such, new sockets getting opened should be relatively infrequent. It's simply part of the protocol for setting up a socket to be used.
Q: Where is information related to user management?
Not here currently. Should it be? This is about authentication, not user management. Perhaps a new spec is necessary.
Q: I've heard isMaster will require authentication in the future. Should we consider that here?
Not right now. We don't know what the future looks like yet and, as such, any preparation would be a guess. This spec will be augmented when the server changes connection protocols.
Q: It's possible to continue using authenticated sockets even if new sockets fail authentication. Why can't we do that so that applications continue to work.
Yes, that's technically true. The issue with doing that is for drivers using connection pooling. An application would function normally until an operation needed an additional connection(s) during a spike. Each new connection would fail to authenticate causing intermittent failures that would be very difficult to understand for a user.

Version History

Version 1.2 Changes
  • Added SCRAM-SHA-1 sasl mechanism
  • Added Connection Handshake
  • Changed connection string to support mechanism properties in generic form
  • Added example conversations for all mechanisms except GSSAPI
  • Miscellaneous wording changes for clarification
Version 1.1 Changes
  • Added MONGODB-X509
  • Added PLAIN sasl mechanism
  • Added support for GSSAPI mechanism property gssapiServiceName