diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 768312c202..106aeb2552 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,9 +26,9 @@ repos: rev: 0.7.17 hooks: - id: mdformat - args: ["--wrap=120"] + args: ["--wrap=120", "--number"] additional_dependencies: - [mdformat-gfm] + [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - repo: https://github.com/tcort/markdown-link-check rev: v3.11.2 diff --git a/LICENSE.md b/LICENSE.md index 02edea40fd..0cb4085dc8 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -40,11 +40,11 @@ CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. "License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, Noncommercial, ShareAlike. -1. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, +2. Fair Use Rights. Nothing in this license is intended to reduce, limit, or restrict any rights arising from fair use, first sale or other limitations on the exclusive rights of the copyright owner under copyright law or other applicable laws. -1. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, +3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: @@ -144,7 +144,7 @@ forth in Sections 4(e) and 4(f). public digital performance is primarily intended for or directed toward commercial advantage or private monetary compensation. -1. Representations, Warranties and Disclaimer +5. Representations, Warranties and Disclaimer UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND ONLY TO THE EXTENT OF ANY RIGHTS HELD IN THE LICENSED WORK BY THE LICENSOR. THE LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND @@ -157,7 +157,7 @@ THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -1. Termination +7. Termination a. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Derivative Works (as defined in Section 1 above) or @@ -170,7 +170,7 @@ THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. -1. Miscellaneous +8. Miscellaneous a. Each time You distribute or publicly digitally perform the Work (as defined in Section 1 above) or a Collective Work (as defined in Section 1 above), the Licensor offers to the recipient a license to the Work on the same terms diff --git a/markdown_link_config.json b/markdown_link_config.json index f50d947b85..251b923090 100644 --- a/markdown_link_config.json +++ b/markdown_link_config.json @@ -2,6 +2,9 @@ "ignorePatterns": [ { "pattern": "^/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#" + }, + { + "pattern": "^https://github.com/10gen/mongo-enterprise-modules" } ] } \ No newline at end of file diff --git a/scripts/migrate_to_md.py b/scripts/migrate_to_md.py index 6ab903767a..1f598dfb8e 100644 --- a/scripts/migrate_to_md.py +++ b/scripts/migrate_to_md.py @@ -92,10 +92,16 @@ # (https://github.com/mongodb/specifications/blob/master/source/...) # and rewrite them to use appropriate md links. # If the link is malformed we ignore and print an error. -rel_pattern = re.compile(f'(\.\.\S*/{path.name})') -md_pattern = re.compile(f'(\(http\S*/{path.name})') -rst_pattern = re.compile(f'(."} +``` + +In this example `` is the authentication database name that either SCRAM-SHA-1 or SCRAM-SHA-256 would use (they +are the same; either from the connection string or else defaulting to 'admin') and `` is the username provided +in the auth credential. The username MUST NOT be modified from the form provided by the user (i.e. do not normalize with +SASLprep), as the server uses the raw form to look for conflicts with legacy credentials. + +If the handshake response includes a `saslSupportedMechs` field, then drivers MUST use the contents of that field to +select a default mechanism as described later. If the command succeeds and the response does not include a +`saslSupportedMechs` field, then drivers MUST use the legacy default mechanism rules for servers older than 4.0. + +### Single-credential drivers + +When the authentication mechanism is not specified, drivers that allow only a single credential per client MUST perform +mechanism negotiation as part of the MongoDB Handshake portion of the authentication handshake. This lets authentication +proceed without a separate negotiation round-trip exchange with the server. + +### Multi-credential drivers + +The use of multiple credentials within a driver is discouraged, but some legacy drivers still allow this. Such drivers +may not have user credentials when connections are opened and thus will not be able to do negotiation. + +Drivers with a list of credentials at the time a connection is opened MAY do mechanism negotiation on the initial +handshake, but only for the first credential in the list of credentials. + +When authenticating each credential, if the authentication mechanism is not specified and has not been negotiated for +that credential: + +- If the connection handshake results indicate the server version is 4.0 or later, drivers MUST send a new `hello` or + legacy hello negotiation command for the credential to determine the default authentication mechanism. +- Otherwise, when the server version is earlier than 4.0, the driver MUST select a default authentication mechanism for + the credential following the instructions for when the `saslSupportedMechs` field is not present in a legacy hello + response. + +### Caching credentials in SCRAM + +In the implementation of SCRAM authentication mechanisms (e.g. SCRAM-SHA-1 and SCRAM-SHA-256), drivers MUST maintain a +cache of computed SCRAM credentials. The cache entries SHOULD be identified by the password, salt, iteration count, and +a value that uniquely identifies the authentication mechanism (e.g. "SHA1" or "SCRAM-SHA-256"). + +The cache entry value MUST be either the `saltedPassword` parameter or the combination of the `clientKey` and +`serverKey` parameters. + +### Reauthentication + +On any operation that requires authentication, the server may raise the error `ReauthenticationRequired` (391), +typically if the user's credential has expired. Drivers MUST immediately attempt a reauthentication on the connection +using suitable credentials, as specified by the particular authentication mechanism when this error is raised, and then +re-attempt the operation. This attempt MUST be irrespective of whether the operation is considered retryable. Drivers +MUST NOT resend a `hello` message during reauthentication, instead using SASL messages directly. Any errors that could +not be recovered from during reauthentication, or that were encountered during the subsequent re-attempt of the +operation MUST be raised to the user. + +Currently the only authentication mechanism on the server that supports reauthentication is MONGODB-OIDC. See the +[MONGODB-OIDC](#mongodb-oidc) section on reauthentication for more details. Note that in order to implement the unified +spec tests for reauthentication, it may be necessary to add reauthentication support for whichever auth mechanism is +used when running the authentication spec tests. + +### Default Authentication Methods + +- Since: 3.0 +- Revised: 4.0 + +If the user did not provide a mechanism via the connection string or via code, the following logic describes how to +select a default. + +If a `saslSupportedMechs` field was present in the handshake response for mechanism negotiation, then it MUST be +inspected to select a default mechanism: + +```javascript +{ + "hello" : true, + "saslSupportedMechs": ["SCRAM-SHA-1", "SCRAM-SHA-256"], + ... + "ok" : 1 +} +``` + +If SCRAM-SHA-256 is present in the list of mechanism, then it MUST be used as the default; otherwise, SCRAM-SHA-1 MUST +be used as the default, regardless of whether SCRAM-SHA-1 is in the list. Drivers MUST NOT attempt to use any other +mechanism (e.g. PLAIN) as the default. + +If `saslSupportedMechs` is not present in the handshake response for mechanism negotiation, then 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. + +#### Determining Server Version + +Drivers SHOULD use the server's wire version ranges to determine the server's version. + +### MONGODB-CR + +- Since: 1.4 +- Deprecated: 3.0 +- Removed: 4.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 + +```javascript +CMD = { getnonce: 1 } +RESP = { nonce: } +``` + +2. Compute key + +```javascript +passwordDigest = HEX( MD5( UTF8( username + ':mongo:' + password ))) +key = HEX( MD5( UTF8( nonce + username + passwordDigest ))) +``` + +3. Send `authenticate` command + +```javascript +CMD = { 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: + +```javascript +CMD = {getnonce : 1} +RESP = {nonce: "2375531c32080ae8", ok: 1} +CMD = {authenticate: 1, user: "user", nonce: "2375531c32080ae8", key: "21742f26431831d5cfca035a08c5bdf6"} +RESP = {ok: 1} +``` + +#### [MongoCredential](#mongocredential) Properties + +- username\ + MUST be specified and non-zero length. + +- source\ + MUST be specified. Defaults to the database name if supplied on the connection string or `admin`. + +- password\ + MUST be specified. + +- mechanism\ + MUST be "MONGODB-CR" + +- mechanism_properties\ + MUST NOT be specified. + +### MONGODB-X509 + +- Since: 2.6 +- Changed: 3.4 + +MONGODB-X509 is the usage of X.509 certificates to validate a client where the distinguished subject name of the client +certificate acts as the username. + +When connected to MongoDB 3.4: + +- You MUST NOT raise an error when the application only provides an X.509 certificate and no username. +- If the application does not provide a username you MUST NOT send a username to the server. +- If the application provides a username you MUST send that username to the server. + +When connected to MongoDB 3.2 or earlier: + +- You MUST send a username to the server. +- If no username is provided by the application, you MAY extract the username from the X.509 certificate instead of + requiring the application to provide it. +- If you choose not to automatically extract the username from the certificate you MUST error when no username is + provided by the application. + +#### Conversation + +1. Send `authenticate` command (MongoDB 3.4+) + +```javascript +CMD = {"authenticate": 1, "mechanism": "MONGODB-X509"} +RESP = {"dbname" : "$external", "user" : "C=IS,ST=Reykjavik,L=Reykjavik,O=MongoDB,OU=Drivers,CN=client", "ok" : 1} +``` + +2. Send `authenticate` command with username: + +```bash +username = $(openssl x509 -subject -nameopt RFC2253 -noout -inform PEM -in my-cert.pem) +``` + +```javascript +CMD = {authenticate: 1, mechanism: "MONGODB-X509", user: "C=IS,ST=Reykjavik,L=Reykjavik,O=MongoDB,OU=Drivers,CN=client"} +RESP = {"dbname" : "$external", "user" : "C=IS,ST=Reykjavik,L=Reykjavik,O=MongoDB,OU=Drivers,CN=client", "ok" : 1} +``` + +#### [MongoCredential](#mongocredential) Properties + +- username\ + SHOULD NOT be provided for MongoDB 3.4+ MUST be specified and non-zero length for MongoDB prior to 3.4 + +- source\ + MUST be "$external". Defaults to `$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](http://tools.ietf.org/html/rfc4422). + +1. Send the `saslStart` command. + +```javascript +CMD = { saslStart: 1, mechanism: , payload: BinData(...), autoAuthorize: 1 } +RESP = { conversationId: , code: , done: , 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`. + +```javascript +CMD = { saslContinue: 1, conversationId: conversationId, payload: BinData(...) } +RESP = { conversationId: , code: , done: , payload: } +``` + +Many languages will have the ability to utilize 3rd party libraries. The server uses +[cyrus-sasl](https://www.cyrusimap.org/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](http://tools.ietf.org/html/rfc4752). Microsoft has a +proprietary implementation called SSPI which is compatible with both Windows and Linux clients. + +[MongoCredential](#mongocredential) properties: + +- username\ + MUST be specified and non-zero length. + +- source\ + MUST be "$external". Defaults to `$external`. + +- password\ + MAY be specified. If omitted, drivers MUST NOT pass the username without password to SSPI on Windows and + instead use the default credentials. + +- 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 value is a string + of either "none", "forward", or "forwardAndReverse". "none" is the default and performs no canonicalization. + "forward" performs a forward DNS lookup to canonicalize the hostname. "forwardAndReverse" performs a forward DNS + lookup and then a reverse lookup on that value to canonicalize the hostname. The driver MUST fallback to the + provided host if any lookup errors or returns no results. Drivers MAY decide to also keep the legacy boolean values + where `true` equals the "forwardAndReverse" behaviour and `false` equals "none". + + - 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. + + - SERVICE_HOST\ + Drivers MAY allow the user to specify a different host for the service. This is stored in the service + principal name instead of the standard host name. This is generally used for cases where the initial role is being + created from localhost but the actual service host would differ. + +#### Hostname Canonicalization + +Valid values for CANONICALIZE_HOST_NAME are `true`, `false`, "none", "forward", "forwardAndReverse". If a value is +provided that does not match one of these the driver MUST raise an error. + +If CANONICALIZE_HOST_NAME is `false`, "none", or not provided, the driver MUST NOT canonicalize the host name. + +If CANONICALIZE_HOST_NAME is `true`, "forward", or "forwardAndReverse", the client MUST canonicalize the name of each +host it uses for authentication. There are two options. First, if the client's underlying GSSAPI library provides +hostname canonicalization, the client MAY rely on it. For example, MIT Kerberos has +[a configuration option for canonicalization](https://web.mit.edu/kerberos/krb5-1.13/doc/admin/princ_dns.html#service-principal-canonicalization). + +Second, the client MAY implement its own canonicalization. If so, the canonicalization algorithm MUST be: + +```python +addresses = fetch addresses for host +if no addresses: + throw error + +address = first result in addresses + +while true: + cnames = fetch CNAME records for host + if no cnames: + break + + # Unspecified which CNAME is used if > 1. + host = one of the records in cnames + +if forwardAndReverse or true: + reversed = do a reverse DNS lookup for address + canonicalized = lowercase(reversed) +else: + canonicalized = lowercase(host) +``` + +For example, here is a Python implementation of this algorithm using `getaddrinfo` (for address and CNAME resolution) +and `getnameinfo` (for reverse DNS). + +```python +from socket import * +import sys + + +def canonicalize(host, mode): + # Get a CNAME for host, if any. + af, socktype, proto, canonname, sockaddr = getaddrinfo( + host, None, 0, 0, IPPROTO_TCP, AI_CANONNAME)[0] + + print('address from getaddrinfo: [%s]' % (sockaddr[0],)) + print('canonical name from getaddrinfo: [%s]' % (canonname,)) + + if (mode == true or mode == 'forwardAndReverse'): + try: + # NI_NAMEREQD requests an error if getnameinfo fails. + name = getnameinfo(sockaddr, NI_NAMEREQD) + except gaierror as exc: + print('getname info failed: "%s"' % (exc,)) + return canonname.lower() + return name[0].lower() + else: + return canonname.lower() + + +canonicalized = canonicalize(sys.argv[1]) +print('canonicalized: [%s]' % (canonicalized,)) +``` + +Beware of a bug in older glibc where `getaddrinfo` uses PTR records instead of CNAMEs if the address family hint is +AF_INET6, and beware of a bug in older MIT Kerberos that causes it to always do reverse DNS lookup even if the `rdns` +configuration option is set to `false`. + +### PLAIN + +- Since: 2.6 Enterprise + +The PLAIN mechanism, as defined in [RFC 4616](http://tools.ietf.org/html/rfc4616), 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: + +```javascript +CMD = {saslStart: 1, mechanism: "PLAIN", payload: BinData(0, "AHVzZXIAcGVuY2ls")} +RESP = {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: + +```javascript +CMD = {saslStart: 1, mechanism: "PLAIN", payload: BinData(0, "dXNlcgB1c2VyAHBlbmNpbA==")} +RESP = {conversationId: 1, payload: BinData(0,""), done: true, ok: 1} +``` + +MongoDB supports either of these forms. + +#### [MongoCredential](#mongocredential) Properties + +- username\ + MUST be specified and non-zero length. + +- source\ + MUST be specified. Defaults to the database name if supplied on the connection string or `$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](http://tools.ietf.org/html/rfc5802). + +[Page 11 of the RFC](http://tools.ietf.org/html/rfc5802#page-11) specifies that user names be prepared with SASLprep, +but drivers MUST NOT do so. + +[Page 8 of the RFC](http://tools.ietf.org/html/rfc5802#page-8) 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. The `username` and `password` MUST NOT be prepared with SASLprep before +hashing. + +For example, to compute the ClientKey according to the RFC: + +```javascript +// 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. + +Additionally, drivers MUST enforce a minimum iteration count of 4096 and MUST error if the authentication conversation +specifies a lower count. This mitigates downgrade attacks by a man-in-the-middle attacker. + +Drivers MUST NOT advertise support for channel binding, as the server does not support it and legacy servers may fail +authentication if drivers advertise support. I.e. the client-first-message MUST start with `n,`. + +Drivers MUST add a top-level `options` field to the saslStart command, whose value is a document containing a field +named `skipEmptyExchange` whose value is true. Older servers will ignore the `options` field and continue with the +longer conversation as shown in the "Backwards Compatibility" section. Newer servers will set the `done` field to `true` +when it responds to the client at the end of the second round trip, showing proof that it knows the password. This will +shorten the conversation by one round trip. + +#### Conversation + +As an example, given a username of "user" and a password of "pencil" and an r value of "fyko+d2lbbFgONRv9qkxdawL", a +SCRAM-SHA-1 conversation would appear as follows: + +```javascript +CMD = "n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL" +RESP = "r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,s=rQ9ZY3MntBeuP3E1TDVC4w==,i=10000" +CMD = "c=biws,r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,p=MC2T8BvbmWRckDw8oWl5IVghwCY=" +RESP = "v=UMWeI25JD1yNYZRMpZ4VHvhZ9e0=" +``` + +This same conversation over MongoDB's SASL implementation would appear as follows: + +```javascript +CMD = {saslStart: 1, mechanism: "SCRAM-SHA-1", payload: BinData(0, "biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM"), options: { skipEmptyExchange: true }} +RESP = {conversationId : 1, payload: BinData(0,"cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0xIbytWZ2s3cXZVT0tVd3VXTElXZzRsLzlTcmFHTUhFRSxzPXJROVpZM01udEJldVAzRTFURFZDNHc9PSxpPTEwMDAw"), done: false, ok: 1} +CMD = {saslContinue: 1, conversationId: 1, payload: BinData(0, "Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMSG8rVmdrN3F2VU9LVXd1V0xJV2c0bC85U3JhR01IRUUscD1NQzJUOEJ2Ym1XUmNrRHc4b1dsNUlWZ2h3Q1k9")} +RESP = {conversationId: 1, payload: BinData(0,"dj1VTVdlSTI1SkQxeU5ZWlJNcFo0Vkh2aFo5ZTA9"), done: true, ok: 1} +``` + +#### [MongoCredential](#mongocredential) Properties + +- username\ + MUST be specified and non-zero length. + +- source\ + MUST be specified. Defaults to the database name if supplied on the connection string or `admin`. + +- password\ + MUST be specified. + +- mechanism\ + MUST be "SCRAM-SHA-1" + +- mechanism_properties\ + MUST NOT be specified. + +### SCRAM-SHA-256 + +- Since: 4.0 + +SCRAM-SHA-256 extends [RFC 5802](http://tools.ietf.org/html/rfc5802) and is formally defined in +[RFC 7677](https://tools.ietf.org/html/rfc7677). + +The MongoDB SCRAM-SHA-256 mechanism works similarly to the SCRAM-SHA-1 mechanism, with the following changes: + +- The SCRAM algorithm MUST use SHA-256 as the hash function instead of SHA-1. +- User names MUST NOT be prepared with SASLprep. This intentionally contravenes the "SHOULD" provision of RFC 5802. +- Passwords MUST be prepared with SASLprep, per RFC 5802. Passwords are used directly for key derivation ; they MUST NOT + be digested as they are in SCRAM-SHA-1. + +Additionally, drivers MUST enforce a minimum iteration count of 4096 and MUST error if the authentication conversation +specifies a lower count. This mitigates downgrade attacks by a man-in-the-middle attacker. + +Drivers MUST add a top-level `options` field to the saslStart command, whose value is a document containing a field +named `skipEmptyExchange` whose value is true. Older servers will ignore the `options` field and continue with the +longer conversation as shown in the "Backwards Compatibility" section. Newer servers will set the `done` field to `true` +when it responds to the client at the end of the second round trip, showing proof that it knows the password. This will +shorten the conversation by one round trip. + +#### Conversation + +As an example, given a username of "user" and a password of "pencil" and an r value of "rOprNGfwEbeRWgbNEkqO", a +SCRAM-SHA-256 conversation would appear as follows: + +```javascript +CMD = "n,,n=user,r=rOprNGfwEbeRWgbNEkqO" +RESP = "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096" +CMD = "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=" +RESP = "v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=" +``` + +This same conversation over MongoDB's SASL implementation would appear as follows: + +```javascript +CMD = {saslStart: 1, mechanism:"SCRAM-SHA-256", options: {skipEmptyExchange: true}, payload: BinData(0, "biwsbj11c2VyLHI9ck9wck5HZndFYmVSV2diTkVrcU8=")} +RESP = {conversationId: 1, payload: BinData(0, "cj1yT3ByTkdmd0ViZVJXZ2JORWtxTyVodllEcFdVYTJSYVRDQWZ1eEZJbGopaE5sRiRrMCxzPVcyMlphSjBTTlk3c29Fc1VFamI2Z1E9PSxpPTQwOTY="), done: false, ok: 1} +CMD = {saslContinue: 1, conversationId: 1, payload: BinData(0, "Yz1iaXdzLHI9ck9wck5HZndFYmVSV2diTkVrcU8laHZZRHBXVWEyUmFUQ0FmdXhGSWxqKWhObEYkazAscD1kSHpiWmFwV0lrNGpVaE4rVXRlOXl0YWc5empmTUhnc3FtbWl6N0FuZFZRPQ==")} +RESP = {conversationId: 1, payload: BinData(0, "dj02cnJpVFJCaTIzV3BSUi93dHVwK21NaFVaVW4vZEI1bkxUSlJzamw5NUc0PQ=="), done: true, ok: 1} +``` + +#### [MongoCredential](#mongocredential) Properties + +- username\ + MUST be specified and non-zero length. + +- source\ + MUST be specified. Defaults to the database name if supplied on the connection string or `admin`. + +- password\ + MUST be specified. + +- mechanism\ + MUST be "SCRAM-SHA-256" + +- mechanism_properties\ + MUST NOT be specified. + +### MONGODB-AWS + +- Since: 4.4 + +MONGODB-AWS authenticates using AWS IAM credentials (an access key ID and a secret access key), +[temporary AWS IAM credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html) obtained from +an [AWS Security Token Service (STS)](https://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html) +[Assume Role](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) request, an OpenID Connect ID +token that supports +[AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html), or +temporary AWS IAM credentials assigned to an +[EC2 instance](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2.html) or ECS task. +Temporary credentials, in addition to an access key ID and a secret access key, includes a security (or session) token. + +MONGODB-AWS 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. Additionally, the secret access key and only the secret access key is sensitive. +Drivers MUST take proper precautions to ensure we do not leak this info. + +All messages between MongoDB clients and servers are sent as BSON V1.1 Objects in the payload field of saslStart and +saslContinue. All fields in these messages have a "short name" which is used in the serialized BSON representation and a +human-readable "friendly name" which is used in this specification. They are as follows: + +| Name | Friendly Name | Type | Description | +| ---- | -------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| r | client nonce | BinData Subtype 0 | 32 byte cryptographically secure random number | +| p | gs2-cb-flag | int32 | The integer representation of the ASCII character 'n' or 'y', i.e., `110` or `121` | +| s | server nonce | BinData Subtype 0 | 64 bytes total, 32 bytes from the client first message and a 32 byte cryptographically secure random number generated by the server | +| h | sts host | string | FQDN of the STS service | +| a | authorization header | string | Authorization header for [AWS Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html?shortFooter=true) | +| d | X-AMZ-Date | string | Current date in UTC. See [AWS Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html?shortFooter=true) | +| t | X-AMZ-Security-Token | string | Optional AWS security token | + +Drivers MUST NOT advertise support for channel binding, as the server does not support it and legacy servers may fail +authentication if drivers advertise support. The client-first-message MUST set the gs2-cb-flag to the integer +representation of the ASCII character `n`, i.e., `110`. + +#### Conversation + +The first message sent by drivers MUST contain a `client nonce` and `gs2-cb-flag`. In response, the server will send a +`server nonce` and `sts host`. Drivers MUST validate that the server nonce is exactly 64 bytes and the first 32 bytes +are the same as the client nonce. Drivers MUST also validate that the length of the host is greater than 0 and less than +or equal to 255 bytes per [RFC 1035](https://tools.ietf.org/html/rfc1035). Drivers MUST reject FQDN names with empty +labels (e.g., "abc..def"), names that start with a period (e.g., ".abc.def") and names that end with a period (e.g., +"abc.def."). Drivers MUST respond to the server's message with an `authorization header` and a `date`. + +As an example, given a client nonce value of "dzw1U2IwSEtgaWI0IUxZMVJqc2xuQzNCcUxBc05wZjI=", a MONGODB-AWS conversation +decoded from BSON to JSON would appear as follows: + +Client First + +```javascript +{ + "r" : new BinData(0, "dzw1U2IwSEtgaWI0IUxZMVJqc2xuQzNCcUxBc05wZjI="), + "p" : 110 +} +``` + +Server First + +```javascript +{ + "s" : new BinData(0, "dzw1U2IwSEtgaWI0IUxZMVJqc2xuQzNCcUxBc05wZjIGS0J9EgLwzEZ9dIzr/hnnK2mgd4D7F52t8g9yTC5cIA=="), + "h" : "sts.amazonaws.com" +} +``` + +Client Second + +```javascript +{ + "a" : "AWS4-HMAC-SHA256 Credential=AKIAICGVLKOKZVY3X3DA/20191107/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-mongodb-gs2-cb-flag;x-mongodb-server-nonce, Signature=ab62ce1c75f19c4c8b918b2ed63b46512765ed9b8bb5d79b374ae83eeac11f55", + "d" : "20191107T002607Z" + "t" : "" +} +``` + +Note that `X-AMZ-Security-Token` is required when using temporary credentials. When using regular credentials, it MUST +be omitted. Each message above will be encoded as BSON V1.1 objects and sent to the peer as the value of `payload`. +Therefore, the SASL conversation would appear as: + +Client First + +```javascript +{ + "saslStart" : 1, + "mechanism" : "MONGODB-AWS" + "payload" : new BinData(0, "NAAAAAVyACAAAAAAWj0lSjp8M0BMKGU+QVAzRSpWfk0hJigqO1V+b0FaVz4QcABuAAAAAA==") +} +``` + +Server First + +```javascript +{ + "conversationId" : 1, + "done" : false, + "payload" : new BinData(0, "ZgAAAAVzAEAAAAAAWj0lSjp8M0BMKGU+QVAzRSpWfk0hJigqO1V+b0FaVz5Rj7x9UOBHJLvPgvgPS9sSzZUWgAPTy8HBbI1cG1WJ9gJoABIAAABzdHMuYW1hem9uYXdzLmNvbQAA"), + "ok" : 1.0 +} +``` + +Client Second: + +```javascript +{ + "saslContinue" : 1, + "conversationId" : 1, + "payload" : new BinData(0, "LQEAAAJhAAkBAABBV1M0LUhNQUMtU0hBMjU2IENyZWRlbnRpYWw9QUtJQUlDR1ZMS09LWlZZM1gzREEvMjAxOTExMTIvdXMtZWFzdC0xL3N0cy9hd3M0X3JlcXVlc3QsIFNpZ25lZEhlYWRlcnM9Y29udGVudC1sZW5ndGg7Y29udGVudC10eXBlO2hvc3Q7eC1hbXotZGF0ZTt4LW1vbmdvZGItZ3MyLWNiLWZsYWc7eC1tb25nb2RiLXNlcnZlci1ub25jZSwgU2lnbmF0dXJlPThhMTI0NGZjODYyZTI5YjZiZjc0OTFmMmYwNDE5NDY2ZGNjOTFmZWU1MTJhYTViM2ZmZjQ1NDY3NDEwMjJiMmUAAmQAEQAAADIwMTkxMTEyVDIxMDEyMloAAA==") +} +``` + +In response to the Server First message, drivers MUST send an `authorization header`. Drivers MUST follow the +[Signature Version 4 Signing Process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) to +calculate the signature for the `authorization header`. The required and optional headers and their associated values +drivers MUST use for the canonical request (see +[Summary of Signing Steps](https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html)) are +specified in the table below. The following pseudocode shows the construction of the Authorization header. + +```javascript +Authorization: algorithm Credential=access key ID/credential scope, SignedHeaders=SignedHeaders, Signature=signature +``` + +The following example shows a finished Authorization header. + +```javascript +Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7 +``` + +The following diagram is a summary of the steps drivers MUST follow to calculate the signature. + +![image](includes/calculating_a_signature.png) + +| Name | Value | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| HTTP Request Method | POST | +| URI | / | +| Content-Type\* | application/x-www-form-urlencoded | +| Content-Length\* | 43 | +| Host\* | Host field from Server First Message | +| Region | Derived from Host - see [Region Calculation](#region-calculation) below | +| X-Amz-Date\* | See [Amazon Documentation](https://docs.aws.amazon.com/general/latest/gr/sigv4_elements.html) | +| X-Amz-Security-Token\* | Optional, see [Amazon Documentation](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html?shortFooter=true) | +| X-MongoDB-Server-Nonce\* | Base64 string of server nonce | +| X-MongoDB-GS2-CB-Flag\* | ASCII lower-case character ‘n’ or ‘y’ or ‘p’ | +| X-MongoDB-Optional-Data\* | Optional data, base64 encoded representation of the optional object provided by the client | +| Body | Action=GetCallerIdentity&Version=2011-06-15 | + +> \[!NOTE\] +> +> `*`, Denotes a header that MUST be included in SignedHeaders, if present. + +#### Region Calculation + +To get the region from the host, the driver MUST follow the algorithm expressed in pseudocode below. : + +``` +if the host is invalid according to the rules described earlier + the region is undefined and the driver must raise an error. +else if the host is "aws.amazonaws.com" + the region is "us-east-1" +else if the host contains the character '.' (a period) + split the host by its periods. The region is the second label. +else // the valid host string contains no periods and is not "aws.amazonaws.com" + the region is "us-east-1" +``` + +Examples are provided below. + +| Host | Region | Notes | +| ------------------------------ | --------- | ------------------------------------------------ | +| sts.amazonaws.com | us-east-1 | the host is "sts.amazonaws.com"; use `us-east-1` | +| sts.us-west-2.amazonaws.com | us-west-2 | use the second label | +| sts.us-west-2.amazonaws.com.ch | us-west-2 | use the second label | +| example.com | com | use the second label | +| localhost | us-east-1 | no "`.`" character; use the default region | +| sts..com | | second label is empty | +| .amazonaws.com | | starts with a period | +| sts.amazonaws. | | ends with a period | +| "" | | empty string | +| "string longer than 255" | | string longer than 255 bytes | + +#### [MongoCredential](#mongocredential) Properties + +- username\ + MAY be specified. The non-sensitive AWS access key. + +- source\ + MUST be "$external". Defaults to `$external`. + +- password\ + MAY be specified. The sensitive AWS secret key. + +- mechanism\ + MUST be "MONGODB-AWS" + +- mechanism_properties + + - AWS_SESSION_TOKEN\ + Drivers MUST allow the user to specify an AWS session token for authentication with temporary + credentials. + +#### Obtaining Credentials + +Drivers will need AWS IAM credentials (an access key, a secret access key and optionally a session token) to complete +the steps in the +[Signature Version 4 Signing Process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html?shortFooter=true). +If a username and password are provided drivers MUST use these for the AWS IAM access key and AWS IAM secret key, +respectively. If, additionally, a session token is provided Drivers MUST use it as well. If a username is provided +without a password (or vice-versa) or if *only* a session token is provided Drivers MUST raise an error. In other words, +regardless of how Drivers obtain credentials the only valid combination of credentials is an access key ID and a secret +access key or an access key ID, a secret access key and a session token. + +AWS recommends using an SDK to "take care of some of the heavy lifting necessary in successfully making API calls, +including authentication, retry behavior, and more". + +A recommended pattern for drivers with existing custom implementation is to not further enhance existing +implementations, and take an optional dependency on the AWS SDK. If the SDK is available, use it, otherwise fallback to +the existing implementation. + +One thing to be mindful of when adopting an AWS SDK is that they typically will check for credentials in a shared AWS +credentials file when one is present, which may be confusing for users relying on the previous authentication handling +behavior. It would be helpful to include a note like the following: + +"Because we are now using the AWS SDK to handle credentials, if you have a shared AWS credentials or config file, then +those credentials will be used by default if AWS auth environment variables are not set. To override this behavior, set +`AWS_SHARED_CREDENTIALS_FILE=""` in your shell or set the equivalent environment variable value in your script or +application. Alternatively, you can create an AWS profile specifically for your MongoDB credentials and set the +`AWS_PROFILE` environment variable to that profile name." + +The order in which Drivers MUST search for credentials is: + +1. The URI +2. Environment variables +3. Using `AssumeRoleWithWebIdentity` if `AWS_WEB_IDENTITY_TOKEN_FILE` and `AWS_ROLE_ARN` are set. +4. The ECS endpoint if `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` is set. Otherwise, the EC2 endpoint. + +> \[!NOTE\] +> +> See *Should drivers support accessing Amazon EC2 instance metadata in Amazon ECS* in [Q & A](#q-a) +> +> Drivers are not expected to handle +> [AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) requests directly. See +> description of `AssumeRole` below, which is distinct from `AssumeRoleWithWebIdentity` requests that are meant to be +> handled directly by the driver. + +##### URI + +An example URI for authentication with MONGODB-AWS using AWS IAM credentials passed through the URI is as follows: + +```javascript +"mongodb://:@mongodb.example.com/?authMechanism=MONGODB-AWS" +``` + +Users MAY have obtained temporary credentials through an +[AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html) request. If so, then in addition +to a username and password, users MAY also provide an `AWS_SESSION_TOKEN` as a `mechanism_property`. + +```javascript +"mongodb://:@mongodb.example.com/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:" +``` + +##### Environment variables + +AWS Lambda runtimes set several +[environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime) +during initialization. To support AWS Lambda runtimes Drivers MUST check a subset of these variables, i.e., +`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`, for the access key ID, secret access key and +session token, respectively if AWS credentials are not explicitly provided in the URI. The `AWS_SESSION_TOKEN` may or +may not be set. However, if `AWS_SESSION_TOKEN` is set Drivers MUST use its value as the session token. Drivers +implemented in programming languages that support altering environment variables MUST always read environment variables +dynamically during authorization, to handle the case where another part the application has refreshed the credentials. + +However, if environment variables are not present during initial authorization, credentials may be fetched from another +source and cached. Even if the environment variables are present in subsequent authorization attempts, the driver MUST +use the cached credentials, or refresh them if applicable. This behavior is consistent with how the AWS SDKs behave. + +##### AssumeRoleWithWebIdentity + +AWS EKS clusters can be configured to automatically provide a valid OpenID Connect ID token and associated role ARN. +These can be exchanged for temporary credentials using an +[AssumeRoleWithWebIdentity request](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html). + +If the `AWS_WEB_IDENTITY_TOKEN_FILE` and `AWS_ROLE_ARN` environment variables are set, drivers MUST make an +`AssumeRoleWithWebIdentity` request to obtain temporary credentials. AWS recommends using an AWS Software Development +Kit (SDK) to make STS requests. + +The `WebIdentityToken` value is obtained by reading the contents of the file given by `AWS_WEB_IDENTITY_TOKEN_FILE`. The +`RoleArn` value is obtained from `AWS_ROLE_ARN`. If `AWS_ROLE_SESSION_NAME` is set, it MUST be used for the +`RoleSessionName` parameter, otherwise a suitable random name can be chosen. No other request parameters need to be set +if using an SDK. + +If not using an AWS SDK, the request must be made manually. If making a manual request, the `Version` should be +specified as well. An example manual POST request looks like the following: + +```html +https://sts.amazonaws.com/ +?Action=AssumeRoleWithWebIdentity +&RoleSessionName=app1 +&RoleArn= +&WebIdentityToken= +&Version=2011-06-15 +``` + +with the header: + +```html +Accept: application/json +``` + +The JSON response from the STS endpoint will contain credentials in this format: + +```javascript +{ + "Credentials": { + "AccessKeyId": , + "Expiration": , + "RoleArn": , + "SecretAccessKey": , + "SessionToken": + } +} +``` + +Note that the token is called `SessionToken` and not `Token` as it would be with other credential responses. + +##### ECS endpoint + +If a username and password are not provided and the aforementioned environment variables are not set, drivers MUST query +a link-local AWS address for temporary credentials. If temporary credentials cannot be obtained then drivers MUST fail +authentication and raise an error. Drivers SHOULD enforce a 10 second read timeout while waiting for incoming content +from both the ECS and EC2 endpoints. If the environment variable `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` is set then +drivers MUST assume that it was set by an AWS ECS agent and use the URI +`http://169.254.170.2/$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` to obtain temporary credentials. Querying the URI will +return the JSON response: + +```javascript +{ + "AccessKeyId": , + "Expiration": , + "RoleArn": , + "SecretAccessKey": , + "Token": +} +``` + +##### EC2 endpoint + +If the environment variable `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` is unset, drivers MUST use the EC2 endpoint, + +```html +http://169.254.169.254/latest/meta-data/iam/security-credentials/ +``` + +with the required header, + +```html +X-aws-ec2-metadata-token: +``` + +to access the EC2 instance's metadata. Drivers MUST obtain the role name from querying the URI + +```html +http://169.254.169.254/latest/meta-data/iam/security-credentials/ +``` + +The role name request also requires the header `X-aws-ec2-metadata-token`. Drivers MUST use v2 of the EC2 Instance +Metadata Service +([IMDSv2](https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service/)) +to access the secret token. In other words, Drivers MUST + +- Start a session with a simple HTTP PUT request to IMDSv2. + + - The URL is `http://169.254.169.254/latest/api/token`. + - The required header is `X-aws-ec2-metadata-token-ttl-seconds`. Its value is the number of seconds the secret token + should remain valid with a max of six hours (`21600` seconds). + +- Capture the secret token IMDSv2 returned as a response to the PUT request. This token is the value for the header + `X-aws-ec2-metadata-token`. + +The curl recipe below demonstrates the above. It retrieves a secret token that's valid for 30 seconds. It then uses that +token to access the EC2 instance's credentials: + +```bash +$ TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 30"` +$ ROLE_NAME=`curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ -H "X-aws-ec2-metadata-token: $TOKEN"` +$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME -H "X-aws-ec2-metadata-token: $TOKEN" +``` + +Drivers can test this process using the mock EC2 server in +[mongo-enterprise-modules](https://github.com/10gen/mongo-enterprise-modules/blob/master/jstests/external_auth/lib/ec2_metadata_http_server.py). +The script must be run with `python3`: + +```bash +python3 ec2_metadata_http_server.py +``` + +To re-direct queries from the EC2 endpoint to the mock server, replace the link-local address (`http://169.254.169.254`) +with the IP and port of the mock server (by default, `http://localhost:8000`). For example, the curl script above +becomes: + +```bash +$ TOKEN=`curl -X PUT "http://localhost:8000/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 30"` +$ ROLE_NAME=`curl http://localhost:8000/latest/meta-data/iam/security-credentials/ -H "X-aws-ec2-metadata-token: $TOKEN"` +$ curl http://localhost:8000/latest/meta-data/iam/security-credentials/$ROLE_NAME -H "X-aws-ec2-metadata-token: $TOKEN" +``` + +The JSON response from both the actual and mock EC2 endpoint will be in this format: + +```javascript +{ + "Code": "Success", + "LastUpdated" : , + "Type": "AWS-HMAC", + "AccessKeyId" : , + "SecretAccessKey": , + "Token" : , + "Expiration": +} +``` + +From the JSON response drivers MUST obtain the `access_key`, `secret_key` and `security_token` which will be used during +the +[Signature Version 4 Signing Process](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html?shortFooter=true). + +##### Caching Credentials + +Credentials fetched by the driver using AWS endpoints MUST be cached and reused to avoid hitting AWS rate limitations. +AWS recommends using a suitable Software Development Kit (SDK) for your language. If that SDK supports credential fetch +and automatic refresh/caching, then that mechanism can be used in lieu of manual caching. + +If using manual caching, the "Expiration" field MUST be stored and used to determine when to clear the cache. +Credentials are considered valid if they are more than five minutes away from expiring; to the reduce the chance of +expiration before they are validated by the server. Credentials that are retrieved from environment variables MUST NOT +be cached. + +If there are no current valid cached credentials, the driver MUST initiate a credential request. To avoid adding a +bottleneck that would override the `maxConnecting` setting, the driver MUST not place a lock on making a request. The +cache MUST be written atomically. + +If AWS authentication fails for any reason, the cache MUST be cleared. + +> \[!NOTE\] +> +> Five minutes was chosen based on the AWS documentation for +> [IAM roles for EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html) : "We make new +> credentials available at least five minutes before the expiration of the old credentials". The intent is to have some +> buffer between when the driver fetches the credentials and when the server verifies them. + +### MONGODB-OIDC + +- Since: 7.0 Enterprise + +MONGODB-OIDC authenticates using an [OpenID Connect (OIDC)](https://openid.net/specs/openid-connect-core-1_0.html) +access token. + +There are two OIDC authentication flows that drivers can support: machine-to-machine ("machine") and human-in-the-loop +("human"). Drivers MUST support the machine authentication flow. Drivers MAY support the human authentication flow. + +The MONGODB-OIDC specification refers to the following OIDC concepts: + +- **Identity Provider (IdP)**: A service that manages user accounts and authenticates users or applications, such as + Okta or OneLogin. In the [Human Authentication Flow](#human-authentication-flow), the + [OIDC Human Callback](#oidc-human-callback) interacts directly the IdP. In the + [Machine Authentication Flow](#machine-authentication-flow), only the MongoDB server interacts directly the IdP. +- **Access token**: Used to authenticate requests to protected resources. OIDC access tokens are signed JWT strings. +- **Refresh token**: Some OIDC providers may return a refresh token in addition to an access token. A refresh token can + be used to retrieve new access tokens without requiring a human to re-authorize the application. Refresh tokens are + typically only supported by the [Human Authentication Flow](#human-authentication-flow). + +#### Machine Authentication Flow + +The machine authentication flow is intended to be used in cases where human interaction is not necessary or practical, +such as to authenticate database access for a web service. Some OIDC documentation refers to the machine authentication +flow as "workload authentication". + +Drivers MUST implement all behaviors described in the MONGODB-OIDC specification, unless the section or block +specifically says that it only applies to the [Human Authentication Flow](#human-authentication-flow). + +#### Human Authentication Flow + +The human authentication flow is intended to be used for applications that involve direct human interaction, such as +database tools or CLIs. Some OIDC documentation refers to the human authentication flow as "workforce authentication". + +Drivers that support the [Human Authentication Flow](#human-authentication-flow) MUST implement all behaviors described +in the MONGODB-OIDC specification, including sections or blocks that specifically say that it only applies the +[Human Authentication Flow](#human-authentication-flow). + +#### [MongoCredential](#mongocredential) Properties + +- username\ + MAY be specified. Its meaning varies depending on the OIDC provider integration used. + +- source\ + MUST be "$external". Defaults to `$external`. + +- password\ + MUST NOT be specified. + +- mechanism\ + MUST be "MONGODB-OIDC" + +- mechanism_properties + + - PROVIDER_NAME\ + Drivers MUST allow the user to specify the name of a built-in OIDC provider integration to use to + obtain credentials. If provided, the value MUST be one of `["aws"]`. If both `PROVIDER_NAME` and an + [OIDC Callback](#oidc-callback) or [OIDC Human Callback](#oidc-human-callback) are provided for the same + `MongoClient`, the driver MUST raise an error. + + - OIDC_CALLBACK\ + An [OIDC Callback](#oidc-callback) that returns OIDC credentials. Drivers MAY allow the user to + specify an [OIDC Callback](#oidc-callback) using a `MongoClient` configuration instead of a mechanism property, + depending on what is idiomatic for the driver. Drivers MUST NOT support both the `OIDC_CALLBACK` mechanism property + and a `MongoClient` configuration. + + - OIDC_HUMAN_CALLBACK\ + An [OIDC Human Callback](#oidc-human-callback) that returns OIDC credentials. Drivers MAY allow + the user to specify a [OIDC Human Callback](#oidc-human-callback) using a `MongoClient` configuration instead of a + mechanism property, depending on what is idiomatic for the driver. Drivers MUST NOT support both the + `OIDC_HUMAN_CALLBACK` mechanism property and a `MongoClient` configuration. Drivers MUST return an error if both an + [OIDC Callback](#oidc-callback) and `OIDC Human Callback` are provided for the same `MongoClient`. This property is + only required for drivers that support the [Human Authentication Flow](#human-authentication-flow). + + - ALLOWED_HOSTS\ + The list of allowed hostnames or ip-addresses (ignoring ports) for MongoDB connections. The hostnames + may include a leading "\*." wildcard, which allows for matching (potentially nested) subdomains. `ALLOWED_HOSTS` is + a security feature and MUST default to + `["*.mongodb.net", "*.mongodb-qa.net", "*.mongodb-dev.net", "*.mongodbgov.net", "localhost", "127.0.0.1", "::1"]`. + When MONGODB-OIDC authentication using a [OIDC Human Callback](#oidc-human-callback) is attempted against a hostname + that does not match any of list of allowed hosts, the driver MUST raise a client-side error without invoking any + user-provided callbacks. This value MUST NOT be allowed in the URI connection string. The hostname check MUST be + performed after SRV record resolution, if applicable. This property is only required for drivers that support the + [Human Authentication Flow](#human-authentication-flow). + +#### Built-in Provider Integrations + +Drivers MUST support all of the following built-in OIDC providers. + +####### AWS + +The AWS provider is enabled by setting auth mechanism property `PROVIDER_NAME:aws`. + +If enabled, drivers MUST read the file path from environment variable `AWS_WEB_IDENTITY_TOKEN_FILE` and then read the +OIDC access token from that file. The driver MUST use the contents of that file as value in the `jwt` field of the +`saslStart` payload. + +Drivers MAY implement the AWS provider so that it conforms to the function signature of the +[OIDC Callback](#oidc-callback) to prevent having to re-implement the AWS provider logic in the OIDC prose tests. + +#### OIDC Callback + +Drivers MUST allow users to provide a callback that returns an OIDC access token. The purpose of the callback is to +allow users to integrate with OIDC providers not supported by the +[Built-in Provider Integrations](#built-in-provider-integrations). Callbacks can be synchronous or asynchronous, +depending on the driver and/or language. Asynchronous callbacks should be preferred when other operations in the driver +use asynchronous functions. + +Drivers MUST provide a way for the callback to be either automatically canceled, or to cancel itself. This can be as a +timeout argument to the callback, a cancellation context passed to the callback, or some other language-appropriate +mechanism. The timeout value MUST be `min(remaining connectTimeoutMS, remaining timeoutMS)` as described in the Server +Selection section of the CSOT spec. + +The driver MUST pass the following information to the callback: + +- `timeout`: A timeout, in milliseconds, a deadline, or a `timeoutContext`. + +- `version`: The callback API version number. The version number is used to communicate callback API changes that are + not breaking but that users may want to know about and review their implementation. Drivers MUST pass `1` for the + initial callback API version number and increment the version number anytime the API changes. Note that this may + eventually lead to some drivers having different callback version numbers. + + For example, users may add the following check in their callback: + + ```typescript + if(params.version > 1) { + throw new Error("OIDC callback API has changed!"); + } + ``` + +The callback MUST be able to return the following information: + +- `accessToken`: An OIDC access token string. The driver MUST NOT attempt to validate `accessToken` directly. +- `expiresIn`: An optional expiry duration for the access token. Drivers MUST interpret the value 0 as an infinite + duration and error if a negative value is returned. Drivers SHOULD use the most idiomatic type for representing a + duration in the driver's language. Note that the access token expiry value is currently not used in + [Credential Caching](#credential-caching), but is intended to support future caching optimizations. + +The signature and naming of the callback API is up to the driver's discretion. Drivers MUST ensure that additional +optional input parameters and return values can be added to the callback signature in the future without breaking +backward compatibility. + +An example callback API might look like: + +```typescript +interface OIDCCallbackParams { + callbackTimeoutMS: int; + version: int; +} + +interface OIDCCredential { + accessToken: string; + expiresInSeconds: Optional; +} + +function oidcCallback(params: OIDCCallbackParams): OIDCCredential +``` + +##### OIDC Human Callback + +The human callback is an OIDC callback that includes additional information that is required when using the +[Human Authentication Flow](#human-authentication-flow). Drivers that support the +[Human Authentication Flow](#human-authentication-flow) MUST implement the human callback. + +In addition to the information described in the [OIDC Callback](#oidc-callback) section, drivers MUST be able to pass +the following information to the callback: + +- `idpInfo`: Information used to authenticate with the IdP. + - `issuer`: A URL which describes the Authentication Server. This identifier should be the iss of provided access + tokens, and be viable for RFC8414 metadata discovery and RFC9207 identification. + - `clientId`: A unique client ID for this OIDC client. + - `requestScopes`: A list of additional scopes to request from IdP. +- `refreshToken`: The refresh token, if applicable, to be used by the callback to request a new token from the issuer. + +In addition to the information described in the [OIDC Callback](#oidc-callback) section, the callback MUST be able to +return the following information: + +- `refreshToken`: An optional refresh token that can be used to fetch new access tokens. + +The signature and naming of the callback API is up to the driver's discretion. Drivers MAY use a single callback API for +both callback types or separate callback APIs for each callback type. Drivers MUST ensure that additional optional input +parameters and return values can be added to the callback signature in the future without breaking backward +compatibility. + +An example human callback API might look like: + +```typescript +interface IdpInfo { + issuer: string; + clientId: string; + requestScopes: Optional>; +} + +interface OIDCCallbackParams { + callbackTimeoutMS: int; + version: int; + idpInfo: Optional; + refreshToken: Optional; +} + +interface OIDCCredential { + accessToken: string; + expiresInSeconds: Optional; + refreshToken: Optional; +} + +function oidcCallback(params: OIDCCallbackParams): OIDCCredential +``` + +When a human callback is provided, drivers MUST use the following behaviors when calling the callback: + +- The driver MUST pass the `IdpInfo` and the refresh token (if available) to the callback. + - If there is no cached `IdpInfo`, drivers MUST start a [Two-Step](#two-step) conversation before calling the human + callback. See the Conversation and [Credential Caching](#credential-caching) sections for more details. +- The timeout duration MUST be 5 minutes. This is to account for the human interaction required to complete the + callback. In this case, the callback is not subject to CSOT. + +#### Conversation + +OIDC supports two conversation styles: one-step and two-step. The server detects whether the driver is using a one-step +or two-step conversation based on the structure of the `saslStart` payload. + +##### One-Step + +A one-step conversation is used for OIDC providers that allow direct access to an access token. For example, an OIDC +provider configured for machine-to-machine authentication may provide an access token via a local file pre-loaded on an +application host. + +Drivers MUST use a one-step conversation when using a cached access token, one of the +[Built-in Provider Integrations](#built-in-provider-integrations), or an [OIDC Callback](#oidc-callback) (not an +[OIDC Human Callback](#oidc-human-callback)). + +The one-step conversation starts with a `saslStart` containing a `JwtStepRequest` payload. The value of `jwt` is the +OIDC access token string. + +```typescript +interface JwtStepRequest: + // Compact serialized JWT with signature. + jwt: string; +} +``` + +An example OIDC one-step SASL conversation with access token string "abcd1234" looks like: + +```javascript +// Client: +{ + saslStart: 1, + mechanism: "MONGODB-OIDC", + // payload is a BSON generic binary field containing a JwtStepRequest BSON + // document: {"jwt": "abcd1234"} + payload: BinData(0, "FwAAAAJqd3QACQAAAGFiY2QxMjM0AAA=") +} + +// Server: +{ + conversationId : 1, + payload: BinData(0, ""), + done: true, + ok: 1 +} +``` + +##### Two-Step + +A two-step conversation is used for OIDC providers that require an extra authorization step before issuing a credential. +For example, an OIDC provider configured for end-user authentication may require redirecting the user to a webpage so +they can authorize the request. + +Drivers that support the [Human Authentication Flow](#human-authentication-flow) MUST implement the two-step +conversation. Drivers MUST use a two-step conversation when using a [OIDC Human Callback](#oidc-human-callback) and when +there is no cached access token. + +The two-step conversation starts with a `saslStart` containing a `PrincipalStepRequest` payload. The value of `n` is the +`username` from the connection string. If a `username` is not provided, field `n` should be omitted. + +```typescript +interface PrincipalStepRequest { + // Name of the OIDC user principal. + n: Optional; +} +``` + +The server uses `n` (if provided) to select an appropriate IdP. Note that the principal name is optional as it may be +provided by the IdP in environments where only one IdP is used. + +The server responds to the `PrincipalStepRequest` with `IdpInfo` for the selected IdP: + +```typescript +interface IdpInfo { + // A URL which describes the Authentication Server. This identifier should + // be the iss of provided access tokens, and be viable for RFC8414 metadata + // discovery and RFC9207 identification. + issuer: string; + + // A unique client ID for this OIDC client. + clientId: string; + + // A list of additional scopes to request from IdP. + requestScopes: Optional>; +} +``` + +The driver passes the IdP information to the [OIDC Human Callback](#oidc-human-callback), which should return an OIDC +credential containing an access token and, optionally, a refresh token. + +The driver then sends a `saslContinue` with a `JwtStepRequest` payload to complete authentication. The value of `jwt` is +the OIDC access token string. + +```typescript +interface JwtStepRequest: + // Compact serialized JWT with signature. + jwt: string; +} +``` + +An example OIDC two-step SASL conversation with username "myidp" and access token string "abcd1234" looks like: + +```javascript +// Client: +{ + saslStart: 1, + mechanism: "MONGODB-OIDC", + // payload is a BSON generic binary field containing a PrincipalStepRequest + // BSON document: {"n": "myidp"} + payload: BinData(0, "EgAAAAJuAAYAAABteWlkcAAA") +} + +// Server: +{ + conversationId : 1, + // payload is a BSON generic binary field containing an IdpInfo BSON document: + // {"issuer": "https://issuer", "clientId": "abcd", "requestScopes": ["a","b"]} + payload: BinData(0, "WQAAAAJpc3N1ZXIADwAAAGh0dHBzOi8vaXNzdWVyAAJjbGllbnRJZAAFAAAAYWJjZAAEcmVxdWVzdFNjb3BlcwAXAAAAAjAAAgAAAGEAAjEAAgAAAGIAAAA="), + done: false, + ok: 1 +} + +// Client: +{ + saslContinue: 1, + conversationId: 1, + // payload is a BSON generic binary field containing a JwtStepRequest BSON + // document: {"jwt": "abcd1234"} + payload: BinData(0, "FwAAAAJqd3QACQAAAGFiY2QxMjM0AAA=") +} + +// Server: +{ + conversationId: 1, + payload: BinData(0, ""), + done: true, + ok: 1 +} +``` + +#### Credential Caching + +Some OIDC providers may impose rate limits, incur per-request costs, or be slow to return. To minimize those issues, +drivers MUST cache and reuse access tokens returned by OIDC providers. + +Drivers MUST cache the most recent access token per `MongoClient` (henceforth referred to as the *Client Cache*). +Drivers MAY store the *Client Cache* on the `MongoClient` object or any object that guarantees exactly 1 cached access +token per `MongoClient`. Additionally, drivers MUST cache the access token used to authenticate a connection on the +connection object (henceforth referred to as the *Connection Cache*). + +Drivers MUST ensure that only one call to the configured provider or OIDC callback can happen at a time. To avoid adding +a bottleneck that would override the `maxConnecting` setting, the driver MUST NOT hold an exclusive lock while running +`saslStart` or `saslContinue`. + +Example code for credential caching using the read-through cache pattern: + +```python +def get_access_token(): + # Lock the OIDC authenticator so that only one caller can modify the cache + # and call the configured OIDC provider at a time. + client.oidc_cache.lock() + + # Check if we can use the access token from the Client Cache or if we need + # to fetch and cache a new access token from the OIDC provider. + access_token = client.oidc_cache.access_token + is_cache = True + if access_token is None + credential = oidc_provider() + is_cache = False + client.oidc_cache.access_token = credential.access_token + + client.oidc_cache.unlock() + + return access_token, is_cache +``` + +Drivers MUST have a way to invalidate a specific access token from the *Client Cache*. Invalidation MUST only clear the +cached access token if it is the same as the invalid access token and MUST be an atomic operation (e.g. using a mutex or +a compare-and-swap operation). + +Example code for invalidation: + +```python +def invalidate(access_token): + client.oidc_cache.lock() + + if client.oidc_cache.access_token == access_token: + client.oidc_cache.access_token = None + + client.oidc_cache.unlock() +``` + +Drivers that support the [Human Authentication Flow](#human-authentication-flow) MUST also cache the `IdPInfo` and +refresh token in the *Client Cache* when a [OIDC Human Callback](#oidc-human-callback) is configured. + +####### Authentication + +Use the following algorithm to authenticate a new connection: + +- Check if the the *Client Cache* has an access token. + - If it does, cache the access token in the *Connection Cache* and perform a `One-Step` SASL conversation using the + access token in the *Client Cache*. If the server returns an error, invalidate that access token, sleep 100ms then + continue. +- Call the configured built-in provider integration or the OIDC callback to retrieve a new access token. +- Cache the new access token in the *Client Cache* and *Connection Cache*. +- Perform a `One-Step` SASL conversation using the new access token. Raise any errors to the user. + +Example code to authenticate a connection using the `get_access_token` and `invalidate` functions described above: + +```python +def auth(connection): + access_token, is_cache = get_access_token() + + # If there is a cached access token, try to authenticate with it. If + # authentication fails, it's possible the cached access token is expired. In + # that case, invalidate the access token, fetch a new access token, and try + # to authenticate again. + if is_cache: + try: + connection.oidc_cache.access_token = access_token + sasl_start(connection, payload={"jwt": access_token}) + return + except ServerError: + invalidate(access_token) + sleep(0.1) + access_token, _ = get_access_token() + + connection.oidc_cache.access_token = access_token + sasl_start(connection, payload={"jwt": access_token}) +``` + +For drivers that support the [Human Authentication Flow](#human-authentication-flow), use the following algorithm to +authenticate a new connection when a [OIDC Human Callback](#oidc-human-callback) is configured: + +- Check if the *Client Cache* has an access token. + - If it does, cache the access token in the *Connection Cache* and perform a [One-Step](#one-step) SASL conversation + using the access token. If the server returns an error, invalidate the access token token from the *Client Cache*, + clear the *Connection Cache*, and continue. +- Check if the *Client Cache* has a refresh token. + - If it does, call the [OIDC Human Callback](#oidc-human-callback) with the cached refresh token and `IdpInfo` to get + a new access token. Cache the new access token in the *Client Cache* and *Connection Cache*. Perform a + [One-Step](#one-step) SASL conversation using the new access token. If the + [OIDC Human Callback](#oidc-human-callback) or the server return an error, invalidate the access token from the + *Client Cache*, clear the *Connection Cache*, and continue. +- Start a new [Two-Step](#two-step) SASL conversation. +- Run a `PrincipalStepRequest` to get the `IdpInfo`. +- Call the [OIDC Human Callback](#oidc-human-callback) with the new `IdpInfo` to get a new access token and optional + refresh token. Drivers MUST NOT pass a cached refresh token to the callback when performing a new + [Two-Step](#two-step) conversation. +- Cache the new `IdpInfo` and refresh token in the *Client Cache* and the new access token in the *Client Cache* and + *Connection Cache*. +- Attempt to authenticate using a `JwtStepRequest` with the new access token. Raise any errors to the user. + +#### Speculative Authentication + +Drivers MUST implement speculative authentication for MONGODB-OIDC during the `hello` handshake. Drivers MUST NOT +attempt speculative authentication if the *Client Cache* does not have a cached access token. Drivers MUST NOT +invalidate tokens from the *Client Cache* if speculative authentication does not succeed. + +Use the following algorithm to perform speculative authentication: + +- Check if the *Client Cache* has an access token. + - If it does, cache the access token in the *Connection Cache* and send a `JwtStepRequest` with the cached access + token in the speculative authentication SASL payload. If the response is missing a speculative authentication + document or the speculative authentication document indicates authentication was not successful, clear the the + *Connection Cache* and continue. +- Authenticate with the standard authentication handshake. + +Example code for speculative authentication using the `auth` function described above: + +```python +def speculative_auth(connection): + access_token = client.oidc_cache.access_token + if access_token != None: + connection.oidc_cache.access_token = access_token + res = hello(connection, payload={"jwt": access_token}) + if res.speculative_authenticate.done: + return + + connection.oidc_cache.access_token = None + auth(connection) +``` + +#### Reauthentication + +If any operation fails with `ReauthenticationRequired` (error code 391) and MONGODB-OIDC is in use, the driver MUST +reauthenticate the connection. Drivers MUST NOT resend a `hello` message during reauthentication, instead using SASL +messages directly. See the main [reauthentication](#reauthentication-1) section for more information. + +To reauthenticate a connection, invalidate the access token stored on the connection (i.e. the *Connection Cache*) from +the *Client Cache*, fetch a new access token, and re-run the SASL conversation. + +Example code for reauthentication using the `auth` function described above: + +```python +def reauth(connection): + invalidate(connection.oidc_cache.access_token) + connection.oidc_cache.access_token = None + auth(connection) +``` + +### 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, SCRAM-SHA-256, MONGODB-AWS + +Sets the Mechanism property on the MongoCredential. When not set, the default will be one of SCRAM-SHA-256, SCRAM-SHA-1 +or MONGODB-CR, following the auth spec default mechanism rules. + +- authSource\ + Sets the Source property on the MongoCredential. + +For GSSAPI, MONGODB-X509 and MONGODB-AWS authMechanisms the authSource defaults to `$external`. For PLAIN the authSource +defaults to the database name if supplied on the connection string or `$external`. For MONGODB-CR, SCRAM-SHA-1 and +SCRAM-SHA-256 authMechanisms, the authSource defaults to the database name if supplied on the connection string or +`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:forward,SERVICE_REALM:AWESOME`. + +- gssapiServiceName (deprecated)\ + An alias for `authMechanismProperties=SERVICE_NAME:mongodb`. + +#### Errors + +Drivers MUST raise an error if the `authSource` option is specified in the connection string with an empty value, e.g. +`mongodb://localhost/admin?authSource=`. + +#### 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 , 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 interpreted as the realm. + - When non-GSSAPI is specified, this should be interpreted 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 + +Connection string tests have been defined in the associated files: + +- [Connection String](./tests/legacy/connection-string.json). + +### SCRAM-SHA-256 and mechanism negotiation + +Testing SCRAM-SHA-256 requires server version 3.7.3 or later with `featureCompatibilityVersion` of "4.0" or later. + +Drivers that allow specifying auth parameters in code as well as via connection string should test both for the test +cases described below. + +#### Step 1 + +Create three test users, one with only SHA-1, one with only SHA-256 and one with both. For example: + +```javascript +db.runCommand({createUser: 'sha1', pwd: 'sha1', roles: ['root'], mechanisms: ['SCRAM-SHA-1']}) +db.runCommand({createUser: 'sha256', pwd: 'sha256', roles: ['root'], mechanisms: ['SCRAM-SHA-256']}) +db.runCommand({createUser: 'both', pwd: 'both', roles: ['root'], mechanisms: ['SCRAM-SHA-1', 'SCRAM-SHA-256']}) +``` + +#### Step 2 + +For each test user, verify that you can connect and run a command requiring authentication for the following cases: + +- Explicitly specifying each mechanism the user supports. +- Specifying no mechanism and relying on mechanism negotiation. + +For the example users above, the `dbstats` command could be used as a test command. + +For a test user supporting both SCRAM-SHA-1 and SCRAM-SHA-256, drivers should verify that negotiation selects +SCRAM-SHA-256. This may require monkey patching, manual log analysis, etc. + +#### Step 3 + +For test users that support only one mechanism, verify that explicitly specifying the other mechanism fails. + +For a non-existent username, verify that not specifying a mechanism when connecting fails with the same error type that +would occur with a correct username but incorrect password or mechanism. (Because negotiation with a non-existent user +name at one point during server development caused a handshake error, we want to verify this is seen by users as similar +to other authentication errors, not as a network or database command error on the `hello` or legacy hello commands +themselves.) + +#### Step 4 + +To test SASLprep behavior, create two users: + +1. username: "IX", password "IX" +2. username: "\\u2168" (ROMAN NUMERAL NINE), password "\\u2163" (ROMAN NUMERAL FOUR) + +To create the users, use the exact bytes for username and password without SASLprep or other normalization and specify +SCRAM-SHA-256 credentials: + +```javascript +db.runCommand({createUser: 'IX', pwd: 'IX', roles: ['root'], mechanisms: ['SCRAM-SHA-256']}) +db.runCommand({createUser: '\\u2168', pwd: '\\u2163', roles: ['root'], mechanisms: ['SCRAM-SHA-256']}) +``` + +For each user, verify that the driver can authenticate with the password in both SASLprep normalized and non-normalized +forms: + +- User "IX": use password forms "IX" and "I\\u00ADX" +- User "\\u2168": use password forms "IV" and "I\\u00ADV" + +As a URI, those have to be UTF-8 encoded and URL-escaped, e.g.: + +- +- +- +- + +### Speculative Authentication + +See the speculative authentication section in the +[MongoDB Handshake spec](https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.rst). + +### Minimum iteration count + +For SCRAM-SHA-1 and SCRAM-SHA-256, test that the minimum iteration count is respected. This may be done via unit testing +of an underlying SCRAM library. + +## Backwards Compatibility + +Drivers may need to remove support for association of more than one credential with a MongoClient, including + +- Deprecation and removal of MongoClient constructors that take as an argument more than a single credential +- Deprecation and removal of methods that allow lazy authentication (i.e post-MongoClient construction) + +Drivers need to support both the shorter and longer SCRAM-SHA-1 and SCRAM-SHA-256 conversations over MongoDB's SASL +implementation. Earlier versions of the server required an extra round trip due to an implementation decision. This was +accomplished by sending no bytes back to the server, as seen in the following conversation (extra round trip +emphasized): + +```javascript +CMD = {saslStart: 1, mechanism: "SCRAM-SHA-1", payload: BinData(0, "biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM"), options: {skipEmptyExchange: true}} +RESP = {conversationId : 1, payload: BinData(0,"cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0xIbytWZ2s3cXZVT0tVd3VXTElXZzRsLzlTcmFHTUhFRSxzPXJROVpZM01udEJldVAzRTFURFZDNHc9PSxpPTEwMDAw"), done: false, ok: 1} +CMD = {saslContinue: 1, conversationId: 1, payload: BinData(0, "Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMSG8rVmdrN3F2VU9LVXd1V0xJV2c0bC85U3JhR01IRUUscD1NQzJUOEJ2Ym1XUmNrRHc4b1dsNUlWZ2h3Q1k9")} +RESP = {conversationId: 1, payload: BinData(0,"dj1VTVdlSTI1SkQxeU5ZWlJNcFo0Vkh2aFo5ZTA9"), done: false, ok: 1} +# Extra round trip +CMD = {saslContinue: 1, conversationId: 1, payload: BinData(0, "")} +RESP = {conversationId: 1, payload: BinData(0,""), done: true, ok: 1} +``` + +The extra round trip will be removed in server version 4.4 when `options: { skipEmptyExchange: true }` is specified +during `saslStart`. + +## Reference Implementation + +The Java and .NET drivers currently uses eager authentication and abide by this specification. + +## Q & A + +Q: According to [Authentication Handshake](#authentication-handshake), we are calling `hello` or legacy hello 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: 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. + +Q: Should a driver support multiple credentials?\ +No. + +Historically, the MongoDB server and drivers have supported multiple credentials, one per authSource, on a single +connection. It was necessary because early versions of MongoDB allowed a user to be granted privileges to access the +database in which the user was defined (or all databases in the special case of the "admin" database). But with the +introduction of role-based access control in MongoDB 2.6, that restriction was removed and it became possible to create +applications that access multiple databases with a single authenticated user. + +Role-based access control also introduces the potential for accidental privilege escalation. An application may, for +example, authenticate user A from authSource X, and user B from authSource Y, thinking that user A has privileges only +on collections in X and user B has privileges only on collections in Y. But with role-based access control that +restriction no longer exists, and it's possible that user B has, for example, more privileges on collections in X than +user A does. Due to this risk it's generally safer to create a single user with only the privileges required for a given +application, and authenticate only that one user in the application. + +In addition, since only a single credential is supported per authSource, certain mechanisms are restricted to a single +credential and some credentials cannot be used in conjunction (GSSAPI and X509 both use the "$external" database). + +Finally, MongoDB 3.6 introduces sessions, and allows at most a single authenticated user on any connection which makes +use of one. Therefore any application that requires multiple authenticated users will not be able to make use of any +feature that builds on sessions (e.g. retryable writes). + +Drivers should therefore guide application creators in the right direction by supporting the association of at most one +credential with a MongoClient instance. + +Q: Should a driver support lazy authentication?\ +No, for the same reasons as given in the previous section, as lazy +authentication is another mechanism for allowing multiple credentials to be associated with a single MongoClient +instance. + +Q: Why does SCRAM sometimes SASLprep and sometimes not?\ +When MongoDB implemented SCRAM-SHA-1, it required drivers to +*NOT* SASLprep usernames and passwords. The primary reason for this was to allow a smooth upgrade path from MongoDB-CR +using existing usernames and passwords. Also, because MongoDB's SCRAM-SHA-1 passwords are hex characters of a digest, +SASLprep of passwords was irrelevant. + +With the introduction of SCRAM-SHA-256, MongoDB requires users to explicitly create new SCRAM-SHA-256 credentials +distinct from those used for MONGODB-CR and SCRAM-SHA-1. This means SCRAM-SHA-256 passwords are not digested and any +Unicode character could now appear in a password. Therefore, the SCRAM-SHA-256 mechanism requires passwords to be +normalized with SASLprep, in accordance with the SCRAM RFC. + +However, usernames must be unique, which creates a similar upgrade path problem. SASLprep maps multiple byte +representations to a single normalized one. An existing database could have multiple existing users that map to the same +SASLprep form, which makes it impossible to find the correct user document for SCRAM authentication given only a +SASLprep username. After considering various options to address or workaround this problem, MongoDB decided that the +best user experience on upgrade and lowest technical risk of implementation is to require drivers to continue to not +SASLprep usernames in SCRAM-SHA-256. + +Q: Should drivers support accessing Amazon EC2 instance metadata in Amazon ECS?\ +No. While it's possible to allow access +to EC2 instance metadata in ECS, for security reasons, Amazon states it's best practice to avoid this. (See +[accessing EC2 metadata in ECS](https://aws.amazon.com/premiumsupport/knowledge-center/ecs-container-ec2-metadata/) and +[IAM Roles for Tasks](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html)) + +## Changelog + +- 2024-01-31: Migrated from reStructuredText to Markdown. + +- 2024-01-17: Added MONGODB-OIDC machine auth flow spec and combine with human\ + auth flow specs. + +- 2023-04-28: Added MONGODB-OIDC auth mechanism + +- 2022-11-02: Require environment variables to be read dynamically. + +- 2022-10-28: Recommend the use of AWS SDKs where available. + +- 2022-10-07: Require caching of AWS credentials fetched by the driver. + +- 2022-10-05: Remove spec front matter and convert version history to changelog. + +- 2022-09-07: Add support for AWS AssumeRoleWithWebIdentity. + +- 2022-01-20: Require that timeouts be applied per the client-side operations timeout spec. + +- 2022-01-14: Clarify that `OP_MSG` must be used for authentication when it is supported. + +- 2021-04-23: Updated to use hello and legacy hello. + +- 2021-03-04: Note that errors encountered during auth are handled by SDAM. + +- 2020-03-06: Add reference to the speculative authentication section of the handshake spec. + +- 2020-02-15: Rename MONGODB-IAM to MONGODB-AWS + +- 2020-02-04: Support shorter SCRAM conversation starting in version 4.4 of the server. + +- 2020-01-31: Clarify that drivers must raise an error when a connection string\ + has an empty value for authSource. + +- 2020-01-23: Clarify when authentication will occur. + +- 2020-01-22: Clarify that authSource in URI is not treated as a user configuring\ + auth credentials. + +- 2019-12-05: Added MONGODB-IAM auth mechanism + +- 2019-07-13: Clarify database to use for auth mechanism negotiation. + +- 2019-04-26: Test format changed to improve specificity of behavior assertions. + + - Clarify that database name in URI is not treated as a user configuring auth credentials. + +- 2018-08-08: Unknown users don't cause handshake errors. This was changed before\ + server 4.0 GA in SERVER-34421, so the + auth spec no longer refers to such a possibility. + +- 2018-04-17: Clarify authSource defaults + + - Fix PLAIN authSource rule to allow user provided values + - Change SCRAM-SHA-256 rules such that usernames are *NOT* normalized; this follows a change in the server design and + should be available in server 4.0-rc0. + +- 2018-03-29: Clarify auth handshake and that it only applies to non-monitoring sockets. + +- 2018-03-15: Describe CANONICALIZE_HOST_NAME algorithm. + +- 2018-03-02: Added SCRAM-SHA-256 and mechanism negotiation as provided by server 4.0 + + - Updated default mechanism determination + - Clarified SCRAM-SHA-1 rules around SASLprep + - Require SCRAM-SHA-1 and SCRAM-SHA-256 to enforce a minimum iteration count + +- 2017-11-10: Updated minimum server version to 2.6 + + - Updated the Q & A to recommend support for at most a single credential per MongoClient + - Removed lazy authentication section + - Changed the list of server types requiring authentication + - Made providing username for X509 authentication optional + +- 2015-02-04: 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 + - Added MONGODB-X509 + - Added PLAIN sasl mechanism + - Added support for GSSAPI mechanism property gssapiServiceName + +______________________________________________________________________ diff --git a/source/auth/auth.rst b/source/auth/auth.rst deleted file mode 100644 index 983e19d248..0000000000 --- a/source/auth/auth.rst +++ /dev/null @@ -1,2080 +0,0 @@ -.. role:: javascript(code) - :language: javascript - -===================== -Driver Authentication -===================== - -:Status: Accepted -:Minimum Server Version: 2.6 - -.. contents:: - --------- - -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. - * Optional for MONGODB-X509 and MONGODB-AWS. -source (string) - * Applies to all mechanisms. - * Always '$external' for GSSAPI and MONGODB-X509. - * 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. - -Each mechanism requires certain properties to be present in a MongoCredential for authentication to occur. See the individual mechanism definitions in the "MongoCredential Properties" section. All requirements listed for a mechanism must be met for authentication to occur. - -Credential delimiter in URI implies authentication -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The presence of a credential delimiter (i.e. @) in the URI connection string is evidence that the user has unambiguously specified user information and MUST be interpreted as a user configuring authentication credentials (even if the username and/or password are empty strings). - -Authentication source and URI database do not imply authentication -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The presence of a database name in the URI connection string MUST NOT be -interpreted as a user configuring authentication credentials. The URI database -name is only used as a default source for some mechanisms when authentication -has been configured and a source is required but has not been specified. See -individual mechanism definitions for details. - -Similarly, the presence of the ``authSource`` option in the URI connection -string without other credential data such as Userinfo or authentication parameters -in connection options MUST NOT be interpreted as a request for authentication. - -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. - -Drivers MUST raise an error if any required information for a mechanism is missing. For instance, if a ``username`` is not specified for SCRAM-SHA-256, the driver must raise an error indicating the the property is missing. - - -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 --------------- - -A MongoClient instance MUST be considered a single logical connection to -the server/deployment. - -Socket connections from a MongoClient to deployment members can be one -of two types: - -* Monitoring-only socket: multi-threaded drivers maintain monitoring - sockets separate from sockets in connection pools. - -* General-use socket: for multi-threaded drivers, these are sockets in - connection pools used for (non-monitoring) user operations; in - single-threaded drivers, these are used for both monitoring and user - operations. - -Authentication (including mechanism negotiation) MUST NOT happen on -monitoring-only sockets. - -If one or more credentials are provided to a MongoClient, then whenever -a general-use socket is opened, drivers MUST immediately conduct an -authentication handshake over that 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. - -Authentication Handshake -~~~~~~~~~~~~~~~~~~~~~~~~ - -An authentication handshake consists of an initial ``hello`` or -legacy hello command possibly followed by one or more authentication -conversations. - -Drivers MUST follow the following steps for an authentication -handshake: - -#. Upon opening a general-use socket to a server for a given - MongoClient, drivers MUST issue a `MongoDB Handshake - <../mongodb-handshake/handshake.rst>`_ immediately. This allows a - driver to determine the server type. If the ``hello`` or legacy hello - of the MongoDB Handshake fails with an error, drivers MUST treat this as - an authentication error. - -#. If the server is of type RSArbiter, no authentication is possible and the - handshake is complete. - -#. Inspect the value of ``maxWireVersion``. If the value is greater than or - equal to ``6``, then the driver MUST use ``OP_MSG`` for authentication. - Otherwise, it MUST use ``OP_QUERY``. - -#. If credentials exist: - - #. A driver MUST authenticate with all credentials provided to the - MongoClient. - - #. A single invalid credential is the same as all credentials being - invalid. - -If the authentication handshake fails for a socket, drivers MUST mark the -server Unknown and clear the server's connection pool. (See `Q & A`_ below and -SDAM's `Why mark a server Unknown after an auth error`_ for rationale.) - -All blocking operations executed as part of the authentication handshake MUST -apply timeouts per the `Client Side Operations Timeout -<../client-side-operations-timeout/client-side-operations-timeout.md>`__ -specification. - -Mechanism Negotiation via Handshake -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -:since: 4.0 - -If an application provides a username but does not provide an -authentication mechanism, drivers MUST negotiate a mechanism via a -``hello`` or legacy hello command requesting a user's supported SASL mechanisms:: - - {hello: 1, saslSupportedMechs: "."} - -In this example ```` is the authentication database name that -either SCRAM-SHA-1 or SCRAM-SHA-256 would use (they are the same; either from -the connection string or else defaulting to 'admin') and ```` -is the username provided in the auth credential. -The username MUST NOT be modified from the form provided by the user (i.e. do -not normalize with SASLprep), as the server uses the raw form to look for -conflicts with legacy credentials. - -If the handshake response includes a -``saslSupportedMechs`` field, then drivers MUST use the contents of that field -to select a default mechanism as described later. If the command succeeds and -the response does not include a ``saslSupportedMechs`` field, then drivers MUST -use the legacy default mechanism rules for servers older than 4.0. - -Single-credential drivers -````````````````````````` - -When the authentication mechanism is not specified, drivers that allow -only a single credential per client MUST perform mechanism negotiation -as part of the MongoDB Handshake portion of the authentication -handshake. This lets authentication proceed without a separate -negotiation round-trip exchange with the server. - -Multi-credential drivers -```````````````````````` - -The use of multiple credentials within a driver is discouraged, but some -legacy drivers still allow this. Such drivers may not have user credentials -when connections are opened and thus will not be able to do negotiation. - -Drivers with a list of credentials at the time a connection is opened MAY do -mechanism negotiation on the initial handshake, but only for the first -credential in the list of credentials. - -When authenticating each credential, if the authentication mechanism is not -specified and has not been negotiated for that credential: - -- If the connection handshake results indicate the server version is 4.0 or - later, drivers MUST send a new ``hello`` or legacy hello negotiation command - for the credential to determine the default authentication mechanism. - -- Otherwise, when the server version is earlier than 4.0, the driver MUST - select a default authentication mechanism for the credential following the - instructions for when the ``saslSupportedMechs`` field is not present in - a legacy hello response. - -Caching credentials in SCRAM -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In the implementation of SCRAM authentication mechanisms (e.g. SCRAM-SHA-1 -and SCRAM-SHA-256), drivers MUST maintain a cache of computed SCRAM credentials. -The cache entries SHOULD be identified by the password, salt, iteration count, -and a value that uniquely identifies the authentication mechanism (e.g. "SHA1" -or "SCRAM-SHA-256"). - -The cache entry value MUST be either the ``saltedPassword`` parameter or the -combination of the ``clientKey`` and ``serverKey`` parameters. - -.. _reauthentication: - -Reauthentication -~~~~~~~~~~~~~~~~ - -On any operation that requires authentication, the server may raise the error -``ReauthenticationRequired`` (391), typically if the user's credential has -expired. Drivers MUST immediately attempt a reauthentication on the connection -using suitable credentials, as specified by the particular authentication -mechanism when this error is raised, and then re-attempt the operation. This -attempt MUST be irrespective of whether the operation is considered retryable. -Drivers MUST NOT resend a ``hello`` message during reauthentication, instead -using SASL messages directly. Any errors that could not be recovered from during -reauthentication, or that were encountered during the subsequent re-attempt of -the operation MUST be raised to the user. - -Currently the only authentication mechanism on the server that supports -reauthentication is MONGODB-OIDC. See the `MONGODB-OIDC`_ section on -reauthentication for more details. Note that in order to implement the unified -spec tests for reauthentication, it may be necessary to add reauthentication -support for whichever auth mechanism is used when running the authentication -spec tests. - --------------------------------- -Supported Authentication Methods --------------------------------- - -Defaults --------- - -:since: 3.0 -:revised: 4.0 - -If the user did not provide a mechanism via the connection string or via code, -the following logic describes how to select a default. - -If a ``saslSupportedMechs`` field was present in the handshake response for -mechanism negotiation, then it MUST be inspected to select a default -mechanism:: - - { - "hello" : true, - "saslSupportedMechs": ["SCRAM-SHA-1", "SCRAM-SHA-256"], - ... - "ok" : 1 - } - -If SCRAM-SHA-256 is present in the list of mechanism, then it MUST be -used as the default; otherwise, SCRAM-SHA-1 MUST be used as the default, -regardless of whether SCRAM-SHA-1 is in the list. Drivers MUST NOT -attempt to use any other mechanism (e.g. PLAIN) as the default. - -If ``saslSupportedMechs`` is not present in the handshake response for -mechanism negotiation, then 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. - -Determining Server Version -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Drivers SHOULD use the server's wire version ranges to determine the server's -version. - -MongoDB Custom Mechanisms -------------------------- - -MONGODB-CR -~~~~~~~~~~ - -:since: 1.4 -:deprecated: 3.0 -:removed: 4.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 -```````````` - -#. Send ``getnonce`` command - * :javascript:`{ getnonce: 1 }` - * Response: :javascript:`{ nonce: }` -#. Compute key - * :javascript:`passwordDigest = HEX( MD5( UTF8( username + ':mongo:' + password )))` - * :javascript:`key = HEX( MD5( UTF8( nonce + username + passwordDigest )))` -#. Send ``authenticate`` command - * :javascript:`{ 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: :javascript:`{getnonce : 1}` -| S: :javascript:`{nonce: "2375531c32080ae8", ok: 1}` -| C: :javascript:`{authenticate: 1, user: "user", nonce: "2375531c32080ae8", key: "21742f26431831d5cfca035a08c5bdf6"}` -| S: :javascript:`{ok: 1}` - -`MongoCredential`_ Properties -````````````````````````````` - -username - MUST be specified and non-zero length. - -source - MUST be specified. Defaults to the database name if supplied on the connection string or ``admin``. - -password - MUST be specified. - -mechanism - MUST be "MONGODB-CR" - -mechanism_properties - MUST NOT be specified. - - -MONGODB-X509 -~~~~~~~~~~~~ - -:since: 2.6 -:changed: 3.4 - - -MONGODB-X509 is the usage of X.509 certificates to validate a client where the -distinguished subject name of the client certificate acts as the username. - -When connected to MongoDB 3.4: - * You MUST NOT raise an error when the application only provides an X.509 certificate and no username. - * If the application does not provide a username you MUST NOT send a username to the server. - * If the application provides a username you MUST send that username to the server. -When connected to MongoDB 3.2 or earlier: - * You MUST send a username to the server. - * If no username is provided by the application, you MAY extract the username from the X.509 certificate instead of requiring the application to provide it. - * If you choose not to automatically extract the username from the certificate you MUST error when no username is provided by the application. - - -Conversation -```````````` - -#. Send ``authenticate`` command (MongoDB 3.4+) - * C: :javascript:`{"authenticate": 1, "mechanism": "MONGODB-X509"}` - * S: :javascript:`{"dbname" : "$external", "user" : "C=IS,ST=Reykjavik,L=Reykjavik,O=MongoDB,OU=Drivers,CN=client", "ok" : 1}` - -#. Send ``authenticate`` command with username: - * ``username = openssl x509 -subject -nameopt RFC2253 -noout -inform PEM -in my-cert.pem`` - * C: :javascript:`{authenticate: 1, mechanism: "MONGODB-X509", user: "C=IS,ST=Reykjavik,L=Reykjavik,O=MongoDB,OU=Drivers,CN=client"}` - * S: :javascript:`{"dbname" : "$external", "user" : "C=IS,ST=Reykjavik,L=Reykjavik,O=MongoDB,OU=Drivers,CN=client", "ok" : 1}` - - -`MongoCredential`_ Properties -````````````````````````````` - -username - SHOULD NOT be provided for MongoDB 3.4+ - MUST be specified and non-zero length for MongoDB prior to 3.4 - -source - MUST be "$external". Defaults to ``$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 `_. - -#. Send the `saslStart` command. - * :javascript:`{ saslStart: 1, mechanism: , payload: BinData(...), autoAuthorize: 1 }` - * Response: :javascript:`{ conversationId: , code: , done: , 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. -#. Continue with the `saslContinue` command while `done` is `false`. - * :javascript:`{ 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 and non-zero length. - -source - MUST be "$external". Defaults to ``$external``. - -password - MAY be specified. If omitted, drivers MUST NOT pass the username without password to SSPI on Windows and instead use the default credentials. - -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 value is a string of either "none", "forward", or "forwardAndReverse". "none" is the default and performs no canonicalization. "forward" performs a forward DNS lookup to canonicalize the hostname. "forwardAndReverse" performs a forward DNS lookup and then a reverse lookup on that value to canonicalize the hostname. The driver MUST fallback to the provided host if any lookup errors or returns no results. Drivers MAY decide to also keep the legacy boolean values where `true` equals the "forwardAndReverse" behaviour and `false` equals "none". - - 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. - - SERVICE_HOST - Drivers MAY allow the user to specify a different host for the service. This is stored in the service principal name instead of the standard host name. This is generally used for cases where the initial role is being created from localhost but the actual service host would differ. - -Hostname Canonicalization -````````````````````````` - -Valid values for CANONICALIZE_HOST_NAME are `true`, `false`, "none", "forward", "forwardAndReverse". If a value is provided that does not match one of these the driver MUST raise an error. - -If CANONICALIZE_HOST_NAME is `false`, "none", or not provided, the driver MUST NOT canonicalize the host name. - -If CANONICALIZE_HOST_NAME is `true`, "forward", or "forwardAndReverse", the client MUST canonicalize the name of each host it uses for authentication. There are two options. First, if the client's underlying GSSAPI library provides hostname canonicalization, the client MAY rely on it. For example, MIT Kerberos has `a configuration option for canonicalization `_. - -Second, the client MAY implement its own canonicalization. If so, the canonicalization algorithm MUST be:: - - addresses = fetch addresses for host - if no addresses: - throw error - - address = first result in addresses - - while true: - cnames = fetch CNAME records for host - if no cnames: - break - - # Unspecified which CNAME is used if > 1. - host = one of the records in cnames - - if forwardAndReverse or true: - reversed = do a reverse DNS lookup for address - canonicalized = lowercase(reversed) - else: - canonicalized = lowercase(host) - -For example, here is a Python implementation of this algorithm using ``getaddrinfo`` (for address and CNAME resolution) and ``getnameinfo`` (for reverse DNS). - -.. code-block:: python - - from socket import * - import sys - - - def canonicalize(host, mode): - # Get a CNAME for host, if any. - af, socktype, proto, canonname, sockaddr = getaddrinfo( - host, None, 0, 0, IPPROTO_TCP, AI_CANONNAME)[0] - - print('address from getaddrinfo: [%s]' % (sockaddr[0],)) - print('canonical name from getaddrinfo: [%s]' % (canonname,)) - - if (mode == true or mode == 'forwardAndReverse'): - try: - # NI_NAMEREQD requests an error if getnameinfo fails. - name = getnameinfo(sockaddr, NI_NAMEREQD) - except gaierror as exc: - print('getname info failed: "%s"' % (exc,)) - return canonname.lower() - return name[0].lower() - else: - return canonname.lower() - - - canonicalized = canonicalize(sys.argv[1]) - print('canonicalized: [%s]' % (canonicalized,)) - -Beware of a bug in older glibc where ``getaddrinfo`` uses PTR records instead of CNAMEs if the address family hint is AF_INET6, and beware of a bug in older MIT Kerberos that causes it to always do reverse DNS lookup even if the ``rdns`` configuration option is set to ``false``. - -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: :javascript:`{saslStart: 1, mechanism: "PLAIN", payload: BinData(0, "AHVzZXIAcGVuY2ls")}` -| S: :javascript:`{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: :javascript:`{saslStart: 1, mechanism: "PLAIN", payload: BinData(0, "dXNlcgB1c2VyAHBlbmNpbA==")}` -| S: :javascript:`{conversationId: 1, payload: BinData(0,""), done: true, ok: 1}` - -MongoDB supports either of these forms. - -`MongoCredential`_ Properties -````````````````````````````` - -username - MUST be specified and non-zero length. - -source - MUST be specified. Defaults to the database name if supplied on the connection string or ``$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 11 of the RFC `_ specifies -that user names be prepared with SASLprep, but drivers MUST NOT do so. - -`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 :javascript:`hash = HEX( MD5( UTF8( username + ':mongo:' + -plain_text_password )))`, where ``plain_text_password`` is actually plain text. -The ``username`` and ``password`` MUST NOT be prepared with SASLprep before -hashing. - -For example, to compute the ClientKey according to the RFC: - -.. code:: javascript - - // 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. - -Additionally, drivers MUST enforce a minimum iteration count of 4096 and MUST -error if the authentication conversation specifies a lower count. This -mitigates downgrade attacks by a man-in-the-middle attacker. - -Drivers MUST NOT advertise support for channel binding, as the server does -not support it and legacy servers may fail authentication if drivers advertise -support. I.e. the client-first-message MUST start with ``n,``. - -Drivers MUST add a top-level ``options`` field to the saslStart command, whose value -is a document containing a field named ``skipEmptyExchange`` whose value is true. -Older servers will ignore the ``options`` field and continue with the longer -conversation as shown in the "Backwards Compatibility" section. Newer servers will -set the ``done`` field to ``true`` when it responds to the client at the end of the -second round trip, showing proof that it knows the password. This will shorten the -conversation by one round trip. - - -Conversation -```````````` - -As an example, given a username of "user" and a password of "pencil" and an r -value of "fyko+d2lbbFgONRv9qkxdawL", a SCRAM-SHA-1 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: :javascript:`{saslStart: 1, mechanism: "SCRAM-SHA-1", payload: BinData(0, "biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM"), options: { skipEmptyExchange: true }}` -| S: :javascript:`{conversationId : 1, payload: BinData(0,"cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0xIbytWZ2s3cXZVT0tVd3VXTElXZzRsLzlTcmFHTUhFRSxzPXJROVpZM01udEJldVAzRTFURFZDNHc9PSxpPTEwMDAw"), done: false, ok: 1}` -| C: :javascript:`{saslContinue: 1, conversationId: 1, payload: BinData(0, "Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMSG8rVmdrN3F2VU9LVXd1V0xJV2c0bC85U3JhR01IRUUscD1NQzJUOEJ2Ym1XUmNrRHc4b1dsNUlWZ2h3Q1k9")}` -| S: :javascript:`{conversationId: 1, payload: BinData(0,"dj1VTVdlSTI1SkQxeU5ZWlJNcFo0Vkh2aFo5ZTA9"), done: true, ok: 1}` - -`MongoCredential`_ Properties -````````````````````````````` - -username - MUST be specified and non-zero length. - -source - MUST be specified. Defaults to the database name if supplied on the connection string or ``admin``. - -password - MUST be specified. - -mechanism - MUST be "SCRAM-SHA-1" - -mechanism_properties - MUST NOT be specified. - -SCRAM-SHA-256 -~~~~~~~~~~~~~ - -:since: 4.0 - -SCRAM-SHA-256 extends `RFC 5802 `_ and -is formally defined in `RFC 7677 `_. - -The MongoDB SCRAM-SHA-256 mechanism works similarly to the SCRAM-SHA-1 -mechanism, with the following changes: - -- The SCRAM algorithm MUST use SHA-256 as the hash function instead of SHA-1. -- User names MUST NOT be prepared with SASLprep. This intentionally - contravenes the "SHOULD" provision of RFC 5802. -- Passwords MUST be prepared with SASLprep, per RFC 5802. Passwords are - used directly for key derivation ; they MUST NOT be digested as they are in - SCRAM-SHA-1. - -Additionally, drivers MUST enforce a minimum iteration count of 4096 and MUST -error if the authentication conversation specifies a lower count. This -mitigates downgrade attacks by a man-in-the-middle attacker. - -Drivers MUST add a top-level ``options`` field to the saslStart command, whose value -is a document containing a field named ``skipEmptyExchange`` whose value is true. -Older servers will ignore the ``options`` field and continue with the longer -conversation as shown in the "Backwards Compatibility" section. Newer servers will -set the ``done`` field to ``true`` when it responds to the client at the end of the -second round trip, showing proof that it knows the password. This will shorten the -conversation by one round trip. - -Conversation -```````````` - -As an example, given a username of "user" and a password of "pencil" and an r -value of "rOprNGfwEbeRWgbNEkqO", a SCRAM-SHA-256 conversation would appear as -follows: - -| C: ``n,,n=user,r=rOprNGfwEbeRWgbNEkqO`` -| S: ``r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096`` -| C: ``c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=`` -| S: ``v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=`` - -This same conversation over MongoDB's SASL implementation would appear as follows: - -| C: :javascript:`{saslStart: 1, mechanism:"SCRAM-SHA-256", options: {skipEmptyExchange: true}, payload: BinData(0, "biwsbj11c2VyLHI9ck9wck5HZndFYmVSV2diTkVrcU8=")}` -| S: :javascript:`{conversationId: 1, payload: BinData(0, "cj1yT3ByTkdmd0ViZVJXZ2JORWtxTyVodllEcFdVYTJSYVRDQWZ1eEZJbGopaE5sRiRrMCxzPVcyMlphSjBTTlk3c29Fc1VFamI2Z1E9PSxpPTQwOTY="), done: false, ok: 1}` -| C: :javascript:`{saslContinue: 1, conversationId: 1, payload: BinData(0, "Yz1iaXdzLHI9ck9wck5HZndFYmVSV2diTkVrcU8laHZZRHBXVWEyUmFUQ0FmdXhGSWxqKWhObEYkazAscD1kSHpiWmFwV0lrNGpVaE4rVXRlOXl0YWc5empmTUhnc3FtbWl6N0FuZFZRPQ==")}` -| S: :javascript:`{conversationId: 1, payload: BinData(0, "dj02cnJpVFJCaTIzV3BSUi93dHVwK21NaFVaVW4vZEI1bkxUSlJzamw5NUc0PQ=="), done: true, ok: 1}` - -`MongoCredential`_ Properties -````````````````````````````` - -username - MUST be specified and non-zero length. - -source - MUST be specified. Defaults to the database name if supplied on the connection string or ``admin``. - -password - MUST be specified. - -mechanism - MUST be "SCRAM-SHA-256" - -mechanism_properties - MUST NOT be specified. - -MONGODB-AWS -~~~~~~~~~~~ - -:since: 4.4 - -MONGODB-AWS authenticates using AWS IAM credentials (an access key ID and a secret access key), `temporary AWS IAM credentials `_ obtained from an -`AWS Security Token Service (STS) `_ -`Assume Role `_ request, an OpenID Connect ID token that supports `AssumeRoleWithWebIdentity `_, -or temporary AWS IAM credentials assigned to an `EC2 instance `_ or ECS task. Temporary credentials, in addition to an access key ID and a secret access key, includes a security (or session) token. - -MONGODB-AWS 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. Additionally, the secret access key and only the secret access key is sensitive. Drivers MUST take proper precautions to ensure we do not leak this info. - -All messages between MongoDB clients and servers are sent as BSON V1.1 Objects in the payload field of saslStart and saslContinue. -All fields in these messages have a "short name" which is used in the serialized -BSON representation and a human-readable "friendly name" which is used in this specification. They are as follows: - -==== ==================== ================= ============================================================================================================================================== -Name Friendly Name Type Description -==== ==================== ================= ============================================================================================================================================== -r client nonce BinData Subtype 0 32 byte cryptographically secure random number -p gs2-cb-flag int32 The integer representation of the ASCII character 'n' or 'y', i.e., ``110`` or ``121`` -s server nonce BinData Subtype 0 64 bytes total, 32 bytes from the client first message and a 32 byte cryptographically secure random number generated by the server -h sts host string FQDN of the STS service -a authorization header string Authorization header for `AWS Signature Version 4 `_ -d X-AMZ-Date string Current date in UTC. See `AWS Signature Version 4 `_ -t X-AMZ-Security-Token string Optional AWS security token -==== ==================== ================= ============================================================================================================================================== - -Drivers MUST NOT advertise support for channel binding, as the server does -not support it and legacy servers may fail authentication if drivers advertise -support. The client-first-message MUST set the gs2-cb-flag to the integer representation -of the ASCII character ``n``, i.e., ``110``. - -Conversation -```````````` - -The first message sent by drivers MUST contain a ``client nonce`` and ``gs2-cb-flag``. In response, the server will send a ``server nonce`` -and ``sts host``. Drivers MUST validate that the server nonce is exactly 64 bytes and the first 32 bytes are the same as the client nonce. Drivers MUST also validate that the length of the host is greater than 0 and less than or equal to 255 bytes per `RFC 1035 `_. Drivers MUST reject FQDN names with empty labels (e.g., "abc..def"), names that start with a period (e.g., ".abc.def") and names that end with a period (e.g., "abc.def."). Drivers MUST respond to the server's message with an ``authorization header`` and a ``date``. - -As an example, given a client nonce value of "dzw1U2IwSEtgaWI0IUxZMVJqc2xuQzNCcUxBc05wZjI=", a MONGODB-AWS conversation decoded from -BSON to JSON would appear as follows: - -Client First - -.. code:: javascript - - { - "r" : new BinData(0, "dzw1U2IwSEtgaWI0IUxZMVJqc2xuQzNCcUxBc05wZjI="), - "p" : 110 - } - -Server First - -.. code:: javascript - - { - "s" : new BinData(0, "dzw1U2IwSEtgaWI0IUxZMVJqc2xuQzNCcUxBc05wZjIGS0J9EgLwzEZ9dIzr/hnnK2mgd4D7F52t8g9yTC5cIA=="), - "h" : "sts.amazonaws.com" - } - -Client Second - -.. code:: javascript - - { - "a" : "AWS4-HMAC-SHA256 Credential=AKIAICGVLKOKZVY3X3DA/20191107/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-mongodb-gs2-cb-flag;x-mongodb-server-nonce, Signature=ab62ce1c75f19c4c8b918b2ed63b46512765ed9b8bb5d79b374ae83eeac11f55", - "d" : "20191107T002607Z" - "t" : "" - } - -Note that `X-AMZ-Security-Token` is required when using temporary credentials. When using regular credentials, it -MUST be omitted. Each message above will be encoded as BSON V1.1 objects and sent to the peer as the value of -``payload``. Therefore, the SASL conversation would appear as: - -Client First - -.. code:: javascript - - { - "saslStart" : 1, - "mechanism" : "MONGODB-AWS" - "payload" : new BinData(0, "NAAAAAVyACAAAAAAWj0lSjp8M0BMKGU+QVAzRSpWfk0hJigqO1V+b0FaVz4QcABuAAAAAA==") - } - -Server First - -.. code:: javascript - - { - "conversationId" : 1, - "done" : false, - "payload" : new BinData(0, "ZgAAAAVzAEAAAAAAWj0lSjp8M0BMKGU+QVAzRSpWfk0hJigqO1V+b0FaVz5Rj7x9UOBHJLvPgvgPS9sSzZUWgAPTy8HBbI1cG1WJ9gJoABIAAABzdHMuYW1hem9uYXdzLmNvbQAA"), - "ok" : 1.0 - } - -Client Second: - -.. code:: javascript - - { - "saslContinue" : 1, - "conversationId" : 1, - "payload" : new BinData(0, "LQEAAAJhAAkBAABBV1M0LUhNQUMtU0hBMjU2IENyZWRlbnRpYWw9QUtJQUlDR1ZMS09LWlZZM1gzREEvMjAxOTExMTIvdXMtZWFzdC0xL3N0cy9hd3M0X3JlcXVlc3QsIFNpZ25lZEhlYWRlcnM9Y29udGVudC1sZW5ndGg7Y29udGVudC10eXBlO2hvc3Q7eC1hbXotZGF0ZTt4LW1vbmdvZGItZ3MyLWNiLWZsYWc7eC1tb25nb2RiLXNlcnZlci1ub25jZSwgU2lnbmF0dXJlPThhMTI0NGZjODYyZTI5YjZiZjc0OTFmMmYwNDE5NDY2ZGNjOTFmZWU1MTJhYTViM2ZmZjQ1NDY3NDEwMjJiMmUAAmQAEQAAADIwMTkxMTEyVDIxMDEyMloAAA==") - } - -In response to the Server First message, drivers MUST send an ``authorization header``. Drivers MUST follow the -`Signature Version 4 Signing Process `__ to -calculate the signature for the ``authorization header``. The required and optional headers and their associated -values drivers MUST use for the canonical request (see `Summary of Signing Steps -`_) are specified in the table -below. The following pseudocode shows the construction of the Authorization header. - -.. code:: javascript - - Authorization: algorithm Credential=access key ID/credential scope, SignedHeaders=SignedHeaders, Signature=signature - -The following example shows a finished Authorization header. - -.. code:: javascript - - Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7 - -The following diagram is a summary of the steps drivers MUST follow to calculate the signature. - -.. image:: includes/calculating_a_signature.png - -======================== ====================================================================================================== -Name Value -======================== ====================================================================================================== -HTTP Request Method POST -URI / -Content-Type* application/x-www-form-urlencoded -Content-Length* 43 -Host* Host field from Server First Message -Region Derived from Host - see `Region Calculation`_ below -X-Amz-Date* See `Amazon Documentation `__ -X-Amz-Security-Token* Optional, see `Amazon Documentation `__ -X-MongoDB-Server-Nonce* Base64 string of server nonce -X-MongoDB-GS2-CB-Flag* ASCII lower-case character ‘n’ or ‘y’ or ‘p’ -X-MongoDB-Optional-Data* Optional data, base64 encoded representation of the optional object provided by the client -Body Action=GetCallerIdentity&Version=2011-06-15 -======================== ====================================================================================================== - -.. note:: - ``*``, Denotes a header that MUST be included in SignedHeaders, if present. - -Region Calculation -`````````````````` - -To get the region from the host, the driver MUST follow the algorithm expressed in pseudocode below. :: - - if the host is invalid according to the rules described earlier - the region is undefined and the driver must raise an error. - else if the host is "aws.amazonaws.com" - the region is "us-east-1" - else if the host contains the character '.' (a period) - split the host by its periods. The region is the second label. - else // the valid host string contains no periods and is not "aws.amazonaws.com" - the region is "us-east-1" - -Examples are provided below. - -============================== ========= ====================================================== -Host Region Notes -============================== ========= ====================================================== -sts.amazonaws.com us-east-1 the host is "sts.amazonaws.com"; use `us-east-1` -sts.us-west-2.amazonaws.com us-west-2 use the second label -sts.us-west-2.amazonaws.com.ch us-west-2 use the second label -example.com com use the second label -localhost us-east-1 no "``.``" character; use the default region -sts..com second label is empty -.amazonaws.com starts with a period -sts.amazonaws. ends with a period -"" empty string -"string longer than 255" string longer than 255 bytes -============================== ========= ====================================================== - -`MongoCredential`_ Properties -````````````````````````````` - -username - MAY be specified. The non-sensitive AWS access key. - -source - MUST be "$external". Defaults to ``$external``. - -password - MAY be specified. The sensitive AWS secret key. - -mechanism - MUST be "MONGODB-AWS" - -mechanism_properties - AWS_SESSION_TOKEN - Drivers MUST allow the user to specify an AWS session token for authentication with temporary credentials. - - -.. _obtaining-credentials: - -Obtaining Credentials -````````````````````` -Drivers will need AWS IAM credentials (an access key, a secret access key and optionally a session token) to complete the steps in the `Signature Version 4 Signing Process -`_. If a username and password are provided drivers -MUST use these for the AWS IAM access key and AWS IAM secret key, respectively. If, additionally, a session token is provided Drivers MUST use it as well. If a username is provided without a password (or vice-versa) or if *only* a session token is provided Drivers MUST raise an error. In other words, regardless of how Drivers obtain credentials the only valid combination of credentials is an access key ID and a secret access key or an access key ID, a secret access key and a session token. - -AWS recommends using an SDK to "take care of some of the heavy lifting -necessary in successfully making API calls, including authentication, retry -behavior, and more". - -A recommended pattern for drivers with existing custom implementation is to not -further enhance existing implementations, and take an optional dependency on -the AWS SDK. If the SDK is available, use it, otherwise fallback to the -existing implementation. - -One thing to be mindful of when adopting an AWS SDK is that they typically will -check for credentials in a shared AWS credentials file when one is present, -which may be confusing for users relying on the previous authentication -handling behavior. It would be helpful to include a note like the following: - -"Because we are now using the AWS SDK to handle credentials, if you have a -shared AWS credentials or config file, then those credentials will be used by -default if AWS auth environment variables are not set. To override this -behavior, set ``AWS_SHARED_CREDENTIALS_FILE=""`` in your shell or set the -equivalent environment variable value in your script or application. -Alternatively, you can create an AWS profile specifically for your MongoDB -credentials and set the ``AWS_PROFILE`` environment variable to that profile -name." - -The order in which Drivers MUST search for credentials is: - -#. The URI -#. Environment variables -#. Using ``AssumeRoleWithWebIdentity`` if ``AWS_WEB_IDENTITY_TOKEN_FILE`` and - ``AWS_ROLE_ARN`` are set. -#. The ECS endpoint if ``AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`` is set. Otherwise, the EC2 endpoint. - -.. note:: - See *Should drivers support accessing Amazon EC2 instance metadata in Amazon ECS* in `Q & A`_ - - Drivers are not expected to handle `AssumeRole `_ requests directly. See - description of ``AssumeRole`` below, which is distinct from - ``AssumeRoleWithWebIdentity`` requests that are meant to be handled - directly by the driver. - -URI -___ -An example URI for authentication with MONGODB-AWS using AWS IAM credentials passed through the URI is as follows: - -.. code:: javascript - - "mongodb://:@mongodb.example.com/?authMechanism=MONGODB-AWS" - -Users MAY have obtained temporary credentials through an `AssumeRole `_ -request. If so, then in addition to a username and password, users MAY also provide an ``AWS_SESSION_TOKEN`` as a ``mechanism_property``. - -.. code:: javascript - - "mongodb://:@mongodb.example.com/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN:" - -Environment variables -_____________________ -AWS Lambda runtimes set several `environment variables `_ during initialization. To support AWS Lambda runtimes Drivers MUST check a subset of these variables, i.e., ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, and ``AWS_SESSION_TOKEN``, for the access key ID, secret access key and session token, respectively if AWS credentials are not explicitly provided in the URI. The ``AWS_SESSION_TOKEN`` may or may not be set. However, if ``AWS_SESSION_TOKEN`` is set Drivers MUST use its value as the session token. Drivers implemented -in programming languages that support altering environment variables MUST always -read environment variables dynamically during authorization, to handle the -case where another part the application has refreshed the credentials. - -However, if environment variables are not present during initial authorization, -credentials may be fetched from another source and cached. Even if the -environment variables are present in subsequent authorization attempts, -the driver MUST use the cached credentials, or refresh them if applicable. -This behavior is consistent with how the AWS SDKs behave. - -AssumeRoleWithWebIdentity -_________________________ -AWS EKS clusters can be configured to automatically provide a valid OpenID -Connect ID token and associated role ARN. These can be exchanged for temporary -credentials using an `AssumeRoleWithWebIdentity request `_. - -If the ``AWS_WEB_IDENTITY_TOKEN_FILE`` and ``AWS_ROLE_ARN`` environment -variables are set, drivers MUST make an ``AssumeRoleWithWebIdentity`` request -to obtain temporary credentials. AWS recommends using an AWS Software -Development Kit (SDK) to make STS requests. - -The ``WebIdentityToken`` value is obtained by reading the contents of the -file given by ``AWS_WEB_IDENTITY_TOKEN_FILE``. The ``RoleArn`` value is -obtained from ``AWS_ROLE_ARN``. If ``AWS_ROLE_SESSION_NAME`` is set, -it MUST be used for the ``RoleSessionName`` parameter, otherwise a suitable -random name can be chosen. No other request parameters need to be set if -using an SDK. - -If not using an AWS SDK, the request must be made manually. If making a manual request, the ``Version`` should be specified as well. An example manual -POST request looks like the following: - -.. code:: html - - https://sts.amazonaws.com/ - ?Action=AssumeRoleWithWebIdentity - &RoleSessionName=app1 - &RoleArn= - &WebIdentityToken= - &Version=2011-06-15 - -with the header: - -.. code:: html - - Accept: application/json - -The JSON response from the STS endpoint will contain credentials in -this format: - -.. code:: javascript - - { - "Credentials": { - "AccessKeyId": , - "Expiration": , - "RoleArn": , - "SecretAccessKey": , - "SessionToken": - } - } - -Note that the token is called ``SessionToken`` and not ``Token`` as it -would be with other credential responses. - -ECS endpoint -____________ -If a username and password are not provided and the aforementioned environment variables are not set, drivers MUST query a link-local AWS address for temporary credentials. -If temporary credentials cannot be obtained then drivers MUST fail authentication and raise an error. Drivers SHOULD -enforce a 10 second read timeout while waiting for incoming content from both the ECS and EC2 endpoints. If the -environment variable ``AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`` is set then drivers MUST assume that it was set by an -AWS ECS agent and use the URI ``http://169.254.170.2/$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`` to obtain temporary -credentials. Querying the URI will return the JSON response: - -.. code:: javascript - - { - "AccessKeyId": , - "Expiration": , - "RoleArn": , - "SecretAccessKey": , - "Token": - } - -EC2 endpoint -____________ -If the environment variable ``AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`` is unset, drivers MUST use the EC2 endpoint, - -.. code:: html - - http://169.254.169.254/latest/meta-data/iam/security-credentials/ - -with the required header, - -.. code:: html - - X-aws-ec2-metadata-token: - -to access the EC2 instance's metadata. Drivers MUST obtain the role name from querying the URI - -.. code:: html - - http://169.254.169.254/latest/meta-data/iam/security-credentials/ - -The role name request also requires the header ``X-aws-ec2-metadata-token``. Drivers MUST use v2 of the EC2 Instance Metadata Service (`IMDSv2 `_) to access the secret token. In other words, Drivers MUST - -* Start a session with a simple HTTP PUT request to IMDSv2. - * The URL is ``http://169.254.169.254/latest/api/token``. - * The required header is ``X-aws-ec2-metadata-token-ttl-seconds``. Its value is the number of seconds the secret token should remain valid with a max of six hours (`21600` seconds). -* Capture the secret token IMDSv2 returned as a response to the PUT request. This token is the value for the header ``X-aws-ec2-metadata-token``. - -The curl recipe below demonstrates the above. It retrieves a secret token that's valid for 30 seconds. It then uses that token to access the EC2 instance's credentials: - -.. code:: shell-session - - $ TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 30"` - $ ROLE_NAME=`curl http://169.254.169.254/latest/meta-data/iam/security-credentials/ -H "X-aws-ec2-metadata-token: $TOKEN"` - $ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME -H "X-aws-ec2-metadata-token: $TOKEN" - -Drivers can test this process using the mock EC2 server in `mongo-enterprise-modules `_. The script must be run with `python3`: - -.. code:: shell-session - - python3 ec2_metadata_http_server.py - -To re-direct queries from the EC2 endpoint to the mock server, replace the link-local address (``http://169.254.169.254``) with the IP and port of the mock server (by default, ``http://localhost:8000``). For example, the curl script above becomes: - -.. code:: shell-session - - $ TOKEN=`curl -X PUT "http://localhost:8000/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 30"` - $ ROLE_NAME=`curl http://localhost:8000/latest/meta-data/iam/security-credentials/ -H "X-aws-ec2-metadata-token: $TOKEN"` - $ curl http://localhost:8000/latest/meta-data/iam/security-credentials/$ROLE_NAME -H "X-aws-ec2-metadata-token: $TOKEN" - -The JSON response from both the actual and mock EC2 endpoint will be in this format: - -.. code:: javascript - - { - "Code": "Success", - "LastUpdated" : , - "Type": "AWS-HMAC", - "AccessKeyId" : , - "SecretAccessKey": , - "Token" : , - "Expiration": - } - -From the JSON response drivers -MUST obtain the ``access_key``, ``secret_key`` and ``security_token`` which will be used during the `Signature Version 4 Signing Process -`_. - -Caching Credentials -___________________ -Credentials fetched by the driver using AWS endpoints MUST be cached and reused -to avoid hitting AWS rate limitations. AWS recommends using a suitable -Software Development Kit (SDK) for your language. If that SDK supports -credential fetch and automatic refresh/caching, then that mechanism can -be used in lieu of manual caching. - -If using manual caching, the "Expiration" field MUST be stored -and used to determine when to clear the cache. Credentials are considered -valid if they are more than five minutes away from expiring; to the reduce the -chance of expiration before they are validated by the server. Credentials -that are retrieved from environment variables MUST NOT be cached. - -If there are no current valid cached credentials, the driver MUST initiate a -credential request. To avoid adding a bottleneck that would override the -``maxConnecting`` setting, the driver MUST not place a lock on making a -request. The cache MUST be written atomically. - -If AWS authentication fails for any reason, the cache MUST be cleared. - -.. note:: - Five minutes was chosen based on the AWS documentation for `IAM roles for EC2 `_ : "We make new credentials available at least five minutes before the expiration of the old credentials". The intent is to have some buffer between when the driver fetches the credentials and when the server verifies them. - - -MONGODB-OIDC -~~~~~~~~~~~~ - -:since: 7.0 Enterprise - -MONGODB-OIDC authenticates using an `OpenID Connect (OIDC) -`_ access token. - -There are two OIDC authentication flows that drivers can support: -machine-to-machine ("machine") and human-in-the-loop ("human"). Drivers MUST -support the machine authentication flow. Drivers MAY support the human -authentication flow. - -The MONGODB-OIDC specification refers to the following OIDC concepts: - -- **Identity Provider (IdP)**: A service that manages user accounts and - authenticates users or applications, such as Okta or OneLogin. In the `Human - Authentication Flow`_, the `OIDC Human Callback`_ interacts directly the IdP. - In the `Machine Authentication Flow`_, only the MongoDB server interacts - directly the IdP. -- **Access token**: Used to authenticate requests to protected resources. OIDC - access tokens are signed JWT strings. -- **Refresh token**: Some OIDC providers may return a refresh token in addition - to an access token. A refresh token can be used to retrieve new access tokens - without requiring a human to re-authorize the application. Refresh tokens are - typically only supported by the `Human Authentication Flow`_. - -Machine Authentication Flow -``````````````````````````` -The machine authentication flow is intended to be used in cases where human -interaction is not necessary or practical, such as to authenticate database -access for a web service. Some OIDC documentation refers to the machine -authentication flow as "workload authentication". - -Drivers MUST implement all behaviors described in the MONGODB-OIDC -specification, unless the section or block specifically says that it only -applies to the `Human Authentication Flow`_. - -Human Authentication Flow -````````````````````````` -The human authentication flow is intended to be used for applications that -involve direct human interaction, such as database tools or CLIs. Some OIDC -documentation refers to the human authentication flow as "workforce -authentication". - -Drivers that support the `Human Authentication Flow`_ MUST implement all -behaviors described in the MONGODB-OIDC specification, including sections or -blocks that specifically say that it only applies the `Human Authentication -Flow`_. - -`MongoCredential`_ Properties -````````````````````````````` - -username - MAY be specified. Its meaning varies depending on the OIDC provider - integration used. - -source - MUST be "$external". Defaults to ``$external``. - -password - MUST NOT be specified. - -mechanism - MUST be "MONGODB-OIDC" - -mechanism_properties - PROVIDER_NAME - Drivers MUST allow the user to specify the name of a built-in OIDC provider - integration to use to obtain credentials. If provided, the value MUST be one - of ["aws"]. If both ``PROVIDER_NAME`` and an `OIDC Callback`_ or `OIDC Human - Callback`_ are provided for the same ``MongoClient``, the driver MUST raise - an error. - - OIDC_CALLBACK - An `OIDC Callback`_ that returns OIDC credentials. Drivers MAY allow the - user to specify an `OIDC Callback`_ using a ``MongoClient`` configuration - instead of a mechanism property, depending on what is idiomatic for the - driver. Drivers MUST NOT support both the ``OIDC_CALLBACK`` mechanism - property and a ``MongoClient`` configuration. - - OIDC_HUMAN_CALLBACK - An `OIDC Human Callback`_ that returns OIDC credentials. Drivers MAY allow - the user to specify a `OIDC Human Callback`_ using a ``MongoClient`` - configuration instead of a mechanism property, depending on what is - idiomatic for the driver. Drivers MUST NOT support both the - ``OIDC_HUMAN_CALLBACK`` mechanism property and a ``MongoClient`` - configuration. Drivers MUST return an error if both an `OIDC Callback`_ and - `OIDC Human Callback` are provided for the same ``MongoClient``. This - property is only required for drivers that support the `Human Authentication - Flow`_. - - ALLOWED_HOSTS - The list of allowed hostnames or ip-addresses (ignoring ports) for MongoDB - connections. The hostnames may include a leading "\*." wildcard, which - allows for matching (potentially nested) subdomains. ``ALLOWED_HOSTS`` is a - security feature and MUST default to ``["*.mongodb.net", "*.mongodb-qa.net", - "*.mongodb-dev.net", "*.mongodbgov.net", "localhost", "127.0.0.1", "::1"]``. - When MONGODB-OIDC authentication using a `OIDC Human Callback`_ is attempted - against a hostname that does not match any of list of allowed hosts, the - driver MUST raise a client-side error without invoking any user-provided - callbacks. This value MUST NOT be allowed in the URI connection string. The - hostname check MUST be performed after SRV record resolution, if applicable. - This property is only required for drivers that support the `Human - Authentication Flow`_. - -Built-in Provider Integrations -`````````````````````````````` -Drivers MUST support all of the following built-in OIDC providers. - -AWS -___ -The AWS provider is enabled by setting auth mechanism property -``PROVIDER_NAME:aws``. - -If enabled, drivers MUST read the file path from environment variable -``AWS_WEB_IDENTITY_TOKEN_FILE`` and then read the OIDC access token from that -file. The driver MUST use the contents of that file as value in the ``jwt`` -field of the ``saslStart`` payload. - -Drivers MAY implement the AWS provider so that it conforms to the function -signature of the `OIDC Callback`_ to prevent having to re-implement the AWS -provider logic in the OIDC prose tests. - -OIDC Callback -````````````` -Drivers MUST allow users to provide a callback that returns an OIDC access -token. The purpose of the callback is to allow users to integrate with OIDC -providers not supported by the `Built-in Provider Integrations`_. Callbacks can -be synchronous or asynchronous, depending on the driver and/or language. -Asynchronous callbacks should be preferred when other operations in the driver -use asynchronous functions. - -Drivers MUST provide a way for the callback to be either automatically canceled, -or to cancel itself. This can be as a timeout argument to the callback, a -cancellation context passed to the callback, or some other language-appropriate -mechanism. The timeout value MUST be ``min(remaining connectTimeoutMS, remaining -timeoutMS)`` as described in the Server Selection section of the CSOT spec. - -The driver MUST pass the following information to the callback: - -- ``timeout``: A timeout, in milliseconds, a deadline, or a ``timeoutContext``. -- ``version``: The callback API version number. The version number is used to - communicate callback API changes that are not breaking but that users may want - to know about and review their implementation. Drivers MUST pass ``1`` for the - initial callback API version number and increment the version number anytime - the API changes. Note that this may eventually lead to some drivers having - different callback version numbers. - - For example, users may add the following check in their callback: - - .. code:: typescript - - if(params.version > 1) { - throw new Error("OIDC callback API has changed!"); - } - -The callback MUST be able to return the following information: - -- ``accessToken``: An OIDC access token string. The driver MUST NOT attempt to - validate ``accessToken`` directly. -- ``expiresIn``: An optional expiry duration for the access token. Drivers MUST - interpret the value 0 as an infinite duration and error if a negative value is - returned. Drivers SHOULD use the most idiomatic type for representing a - duration in the driver's language. Note that the access token expiry value is - currently not used in `Credential Caching`_, but is intended to support future - caching optimizations. - -The signature and naming of the callback API is up to the driver's discretion. -Drivers MUST ensure that additional optional input parameters and return values -can be added to the callback signature in the future without breaking backward -compatibility. - -An example callback API might look like: - -.. code:: typescript - - interface OIDCCallbackParams { - callbackTimeoutMS: int; - version: int; - } - - interface OIDCCredential { - accessToken: string; - expiresInSeconds: Optional; - } - - function oidcCallback(params: OIDCCallbackParams): OIDCCredential - -OIDC Human Callback -___________________ -The human callback is an OIDC callback that includes additional information that -is required when using the `Human Authentication Flow`_. Drivers that support -the `Human Authentication Flow`_ MUST implement the human callback. - -In addition to the information described in the `OIDC Callback`_ section, -drivers MUST be able to pass the following information to the callback: - -- ``idpInfo``: Information used to authenticate with the IdP. - - - ``issuer``: A URL which describes the Authentication Server. This identifier - should be the iss of provided access tokens, and be viable for RFC8414 - metadata discovery and RFC9207 identification. - - ``clientId``: A unique client ID for this OIDC client. - - ``requestScopes``: A list of additional scopes to request from IdP. - -- ``refreshToken``: The refresh token, if applicable, to be used by the callback - to request a new token from the issuer. - -In addition to the information described in the `OIDC Callback`_ section, the -callback MUST be able to return the following information: - -- ``refreshToken``: An optional refresh token that can be used to fetch new - access tokens. - -The signature and naming of the callback API is up to the driver's discretion. -Drivers MAY use a single callback API for both callback types or separate -callback APIs for each callback type. Drivers MUST ensure that additional -optional input parameters and return values can be added to the callback -signature in the future without breaking backward compatibility. - -An example human callback API might look like: - -.. code:: typescript - - interface IdpInfo { - issuer: string; - clientId: string; - requestScopes: Optional>; - } - - interface OIDCCallbackParams { - callbackTimeoutMS: int; - version: int; - idpInfo: Optional; - refreshToken: Optional; - } - - interface OIDCCredential { - accessToken: string; - expiresInSeconds: Optional; - refreshToken: Optional; - } - - function oidcCallback(params: OIDCCallbackParams): OIDCCredential - -When a human callback is provided, drivers MUST use the following behaviors when -calling the callback: - -- The driver MUST pass the ``IdpInfo`` and the refresh token (if available) - to the callback. - - - If there is no cached ``IdpInfo``, drivers MUST start a `Two-Step`_ - conversation before calling the human callback. See the Conversation and - `Credential Caching`_ sections for more details. - -- The timeout duration MUST be 5 minutes. This is to account for the human - interaction required to complete the callback. In this case, the callback is - not subject to CSOT. - -Conversation -```````````` -OIDC supports two conversation styles: one-step and two-step. The server detects -whether the driver is using a one-step or two-step conversation based on the -structure of the ``saslStart`` payload. - -One-Step -________ -A one-step conversation is used for OIDC providers that allow direct access to -an access token. For example, an OIDC provider configured for machine-to-machine -authentication may provide an access token via a local file pre-loaded on an -application host. - -Drivers MUST use a one-step conversation when using a cached access token, one -of the `Built-in Provider Integrations`_, or an `OIDC Callback`_ (not an `OIDC -Human Callback`_). - -The one-step conversation starts with a ``saslStart`` containing a -``JwtStepRequest`` payload. The value of ``jwt`` is the OIDC access token -string. - -.. code:: typescript - - interface JwtStepRequest: - // Compact serialized JWT with signature. - jwt: string; - } - -An example OIDC one-step SASL conversation with access token string "abcd1234" -looks like: - -.. code:: javascript - - // Client: - { - saslStart: 1, - mechanism: "MONGODB-OIDC", - // payload is a BSON generic binary field containing a JwtStepRequest BSON - // document: {"jwt": "abcd1234"} - payload: BinData(0, "FwAAAAJqd3QACQAAAGFiY2QxMjM0AAA=") - } - - // Server: - { - conversationId : 1, - payload: BinData(0, ""), - done: true, - ok: 1 - } - -Two-Step -________ -A two-step conversation is used for OIDC providers that require an extra -authorization step before issuing a credential. For example, an OIDC provider -configured for end-user authentication may require redirecting the user to a -webpage so they can authorize the request. - -Drivers that support the `Human Authentication Flow`_ MUST implement the -two-step conversation. Drivers MUST use a two-step conversation when using a -`OIDC Human Callback`_ and when there is no cached access token. - -The two-step conversation starts with a ``saslStart`` containing a -``PrincipalStepRequest`` payload. The value of ``n`` is the ``username`` from -the connection string. If a ``username`` is not provided, field ``n`` should be -omitted. - -.. code:: typescript - - interface PrincipalStepRequest { - // Name of the OIDC user principal. - n: Optional; - } - -The server uses ``n`` (if provided) to select an appropriate IdP. Note that the -principal name is optional as it may be provided by the IdP in environments -where only one IdP is used. - -The server responds to the ``PrincipalStepRequest`` with ``IdpInfo`` for the -selected IdP: - -.. code:: typescript - - interface IdpInfo { - // A URL which describes the Authentication Server. This identifier should - // be the iss of provided access tokens, and be viable for RFC8414 metadata - // discovery and RFC9207 identification. - issuer: string; - - // A unique client ID for this OIDC client. - clientId: string; - - // A list of additional scopes to request from IdP. - requestScopes: Optional>; - } - -The driver passes the IdP information to the `OIDC Human Callback`_, which -should return an OIDC credential containing an access token and, optionally, a -refresh token. - -The driver then sends a ``saslContinue`` with a ``JwtStepRequest`` payload to -complete authentication. The value of ``jwt`` is the OIDC access token string. - -.. code:: typescript - - interface JwtStepRequest: - // Compact serialized JWT with signature. - jwt: string; - } - -An example OIDC two-step SASL conversation with username "myidp" and access -token string "abcd1234" looks like: - -.. code:: javascript - - // Client: - { - saslStart: 1, - mechanism: "MONGODB-OIDC", - // payload is a BSON generic binary field containing a PrincipalStepRequest - // BSON document: {"n": "myidp"} - payload: BinData(0, "EgAAAAJuAAYAAABteWlkcAAA") - } - - // Server: - { - conversationId : 1, - // payload is a BSON generic binary field containing an IdpInfo BSON document: - // {"issuer": "https://issuer", "clientId": "abcd", "requestScopes": ["a","b"]} - payload: BinData(0, "WQAAAAJpc3N1ZXIADwAAAGh0dHBzOi8vaXNzdWVyAAJjbGllbnRJZAAFAAAAYWJjZAAEcmVxdWVzdFNjb3BlcwAXAAAAAjAAAgAAAGEAAjEAAgAAAGIAAAA="), - done: false, - ok: 1 - } - - // Client: - { - saslContinue: 1, - conversationId: 1, - // payload is a BSON generic binary field containing a JwtStepRequest BSON - // document: {"jwt": "abcd1234"} - payload: BinData(0, "FwAAAAJqd3QACQAAAGFiY2QxMjM0AAA=") - } - - // Server: - { - conversationId: 1, - payload: BinData(0, ""), - done: true, - ok: 1 - } - -Credential Caching -`````````````````` -Some OIDC providers may impose rate limits, incur per-request costs, or be slow -to return. To minimize those issues, drivers MUST cache and reuse access tokens -returned by OIDC providers. - -Drivers MUST cache the most recent access token per ``MongoClient`` (henceforth -referred to as the *Client Cache*). Drivers MAY store the *Client Cache* on the -``MongoClient`` object or any object that guarantees exactly 1 cached access -token per ``MongoClient``. Additionally, drivers MUST cache the access token -used to authenticate a connection on the connection object (henceforth referred -to as the *Connection Cache*). - -Drivers MUST ensure that only one call to the configured provider or OIDC -callback can happen at a time. To avoid adding a bottleneck that would override -the ``maxConnecting`` setting, the driver MUST NOT hold an exclusive lock while -running ``saslStart`` or ``saslContinue``. - -Example code for credential caching using the read-through cache pattern: - -.. code:: python - - def get_access_token(): - # Lock the OIDC authenticator so that only one caller can modify the cache - # and call the configured OIDC provider at a time. - client.oidc_cache.lock() - - # Check if we can use the access token from the Client Cache or if we need - # to fetch and cache a new access token from the OIDC provider. - access_token = client.oidc_cache.access_token - is_cache = True - if access_token is None - credential = oidc_provider() - is_cache = False - client.oidc_cache.access_token = credential.access_token - - client.oidc_cache.unlock() - - return access_token, is_cache - -Drivers MUST have a way to invalidate a specific access token from the *Client -Cache*. Invalidation MUST only clear the cached access token if it is the same -as the invalid access token and MUST be an atomic operation (e.g. using a mutex -or a compare-and-swap operation). - -Example code for invalidation: - -.. code:: python - - def invalidate(access_token): - client.oidc_cache.lock() - - if client.oidc_cache.access_token == access_token: - client.oidc_cache.access_token = None - - client.oidc_cache.unlock() - -Drivers that support the `Human Authentication Flow`_ MUST also cache the -``IdPInfo`` and refresh token in the *Client Cache* when a `OIDC Human -Callback`_ is configured. - -Authentication -______________ -Use the following algorithm to authenticate a new connection: - -- Check if the the *Client Cache* has an access token. - - - If it does, cache the access token in the *Connection Cache* and perform a - `One-Step` SASL conversation using the access token in the *Client Cache*. - If the server returns an error, invalidate that access token, sleep 100ms - then continue. - -- Call the configured built-in provider integration or the OIDC callback to - retrieve a new access token. -- Cache the new access token in the *Client Cache* and *Connection Cache*. -- Perform a `One-Step` SASL conversation using the new access token. - Raise any errors to the user. - -Example code to authenticate a connection using the ``get_access_token`` and -``invalidate`` functions described above: - -.. code:: python - - def auth(connection): - access_token, is_cache = get_access_token() - - # If there is a cached access token, try to authenticate with it. If - # authentication fails, it's possible the cached access token is expired. In - # that case, invalidate the access token, fetch a new access token, and try - # to authenticate again. - if is_cache: - try: - connection.oidc_cache.access_token = access_token - sasl_start(connection, payload={"jwt": access_token}) - return - except ServerError: - invalidate(access_token) - sleep(0.1) - access_token, _ = get_access_token() - - connection.oidc_cache.access_token = access_token - sasl_start(connection, payload={"jwt": access_token}) - -For drivers that support the `Human Authentication Flow`_, use the following -algorithm to authenticate a new connection when a `OIDC Human Callback`_ is -configured: - -- Check if the *Client Cache* has an access token. - - - If it does, cache the access token in the *Connection Cache* and perform a - `One-Step`_ SASL conversation using the access token. If the server returns - an error, invalidate the access token token from the *Client Cache*, clear - the *Connection Cache*, and continue. - -- Check if the *Client Cache* has a refresh token. - - - If it does, call the `OIDC Human Callback`_ with the cached refresh token - and ``IdpInfo`` to get a new access token. Cache the new access token in the - *Client Cache* and *Connection Cache*. Perform a `One-Step`_ SASL - conversation using the new access token. If the `OIDC Human Callback`_ or - the server return an error, invalidate the access token from the *Client - Cache*, clear the *Connection Cache*, and continue. - -- Start a new `Two-Step`_ SASL conversation. -- Run a ``PrincipalStepRequest`` to get the ``IdpInfo``. -- Call the `OIDC Human Callback`_ with the new ``IdpInfo`` to get a new access - token and optional refresh token. Drivers MUST NOT pass a cached refresh token - to the callback when performing a new `Two-Step`_ conversation. -- Cache the new ``IdpInfo`` and refresh token in the *Client Cache* and the new - access token in the *Client Cache* and *Connection Cache*. -- Attempt to authenticate using a ``JwtStepRequest`` with the new access token. - Raise any errors to the user. - -Speculative Authentication -`````````````````````````` -Drivers MUST implement speculative authentication for MONGODB-OIDC during the -``hello`` handshake. Drivers MUST NOT attempt speculative authentication if the -*Client Cache* does not have a cached access token. Drivers MUST NOT invalidate -tokens from the *Client Cache* if speculative authentication does not succeed. - -Use the following algorithm to perform speculative authentication: - -- Check if the *Client Cache* has an access token. - - - If it does, cache the access token in the *Connection Cache* and send a - ``JwtStepRequest`` with the cached access token in the speculative - authentication SASL payload. If the response is missing a speculative - authentication document or the speculative authentication document indicates - authentication was not successful, clear the the *Connection Cache* and - continue. - -- Authenticate with the standard authentication handshake. - -Example code for speculative authentication using the ``auth`` function -described above: - -.. code:: python - - def speculative_auth(connection): - access_token = client.oidc_cache.access_token - if access_token != None: - connection.oidc_cache.access_token = access_token - res = hello(connection, payload={"jwt": access_token}) - if res.speculative_authenticate.done: - return - - connection.oidc_cache.access_token = None - auth(connection) - -Reauthentication -```````````````` -If any operation fails with ``ReauthenticationRequired`` (error code 391) and -MONGODB-OIDC is in use, the driver MUST reauthenticate the connection. Drivers -MUST NOT resend a ``hello`` message during reauthentication, instead using SASL -messages directly. See the main `reauthentication`_ section for more -information. - -To reauthenticate a connection, invalidate the access token stored on the -connection (i.e. the *Connection Cache*) from the *Client Cache*, fetch a new -access token, and re-run the SASL conversation. - -Example code for reauthentication using the ``auth`` function described above: - -.. code:: python - - def reauth(connection): - invalidate(connection.oidc_cache.access_token) - connection.oidc_cache.access_token = None - auth(connection) - -------------------------- -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, SCRAM-SHA-256, MONGODB-AWS - - Sets the Mechanism property on the MongoCredential. When not set, the default will be one of SCRAM-SHA-256, SCRAM-SHA-1 or MONGODB-CR, following the auth spec default mechanism rules. - -authSource - Sets the Source property on the MongoCredential. - - For GSSAPI, MONGODB-X509 and MONGODB-AWS authMechanisms the authSource defaults to ``$external``. - For PLAIN the authSource defaults to the database name if supplied on the connection string or ``$external``. - For MONGODB-CR, SCRAM-SHA-1 and SCRAM-SHA-256 authMechanisms, the authSource defaults to the database name if supplied on the connection string or ``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:forward,SERVICE_REALM:AWESOME``. - -gssapiServiceName (deprecated) - An alias for ``authMechanismProperties=SERVICE_NAME:mongodb``. - - -Errors ------- - -Drivers MUST raise an error if the ``authSource`` option is specified in the connection string with an empty value, e.g. ``mongodb://localhost/admin?authSource=``. - - -Implementation --------------- - -#. Credentials MAY be specified in the connection string immediately after the scheme separator "//". -#. 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 interpreted as the realm. - * When non-GSSAPI is specified, this should be interpreted as part of the username. -#. 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. -#. 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 -========= - -Connection string tests have been defined in the associated files: - -* `Connection String `_. - ---------------------------------------- -SCRAM-SHA-256 and mechanism negotiation ---------------------------------------- - -Testing SCRAM-SHA-256 requires server version 3.7.3 or later with -``featureCompatibilityVersion`` of "4.0" or later. - -Drivers that allow specifying auth parameters in code as well as via -connection string should test both for the test cases described below. - -Step 1 ------- - -Create three test users, one with only SHA-1, one with only SHA-256 and one -with both. For example:: - - db.runCommand({createUser: 'sha1', pwd: 'sha1', roles: ['root'], mechanisms: ['SCRAM-SHA-1']}) - db.runCommand({createUser: 'sha256', pwd: 'sha256', roles: ['root'], mechanisms: ['SCRAM-SHA-256']}) - db.runCommand({createUser: 'both', pwd: 'both', roles: ['root'], mechanisms: ['SCRAM-SHA-1', 'SCRAM-SHA-256']}) - -Step 2 ------- - -For each test user, verify that you can connect and run a command requiring -authentication for the following cases: - -- Explicitly specifying each mechanism the user supports. -- Specifying no mechanism and relying on mechanism negotiation. - -For the example users above, the ``dbstats`` command could be used as a test -command. - -For a test user supporting both SCRAM-SHA-1 and SCRAM-SHA-256, drivers should -verify that negotiation selects SCRAM-SHA-256. This may require monkey -patching, manual log analysis, etc. - -Step 3 ------- - -For test users that support only one mechanism, verify that explicitly specifying -the other mechanism fails. - -For a non-existent username, verify that not specifying a mechanism when -connecting fails with the same error type that would occur with a correct -username but incorrect password or mechanism. (Because negotiation with a -non-existent user name at one point during server development caused a -handshake error, we want to verify this is seen by users as similar to other -authentication errors, not as a network or database command error on the ``hello`` -or legacy hello commands themselves.) - -Step 4 ------- - -To test SASLprep behavior, create two users: - -#. username: "IX", password "IX" -#. username: "\\u2168" (ROMAN NUMERAL NINE), password "\\u2163" (ROMAN NUMERAL FOUR) - -To create the users, use the exact bytes for username and password without -SASLprep or other normalization and specify SCRAM-SHA-256 credentials:: - - db.runCommand({createUser: 'IX', pwd: 'IX', roles: ['root'], mechanisms: ['SCRAM-SHA-256']}) - db.runCommand({createUser: '\\u2168', pwd: '\\u2163', roles: ['root'], mechanisms: ['SCRAM-SHA-256']}) - -For each user, verify that the driver can authenticate with the password in -both SASLprep normalized and non-normalized forms: - -- User "IX": use password forms "IX" and "I\\u00ADX" -- User "\\u2168": use password forms "IV" and "I\\u00ADV" - -As a URI, those have to be UTF-8 encoded and URL-escaped, e.g.: - -- mongodb://IX:IX@mongodb.example.com/admin -- mongodb://IX:I%C2%ADX@mongodb.example.com/admin -- mongodb://%E2%85%A8:IV@mongodb.example.com/admin -- mongodb://%E2%85%A8:I%C2%ADV@mongodb.example.com/admin - --------------------------- -Speculative Authentication --------------------------- - -See the speculative authentication section in the `MongoDB Handshake spec `_. - ------------------------ -Minimum iteration count ------------------------ - -For SCRAM-SHA-1 and SCRAM-SHA-256, test that the minimum iteration count -is respected. This may be done via unit testing of an underlying SCRAM -library. - -Backwards Compatibility -======================= - -Drivers may need to remove support for association of more than one credential with a MongoClient, including - -* Deprecation and removal of MongoClient constructors that take as an argument more than a single credential -* Deprecation and removal of methods that allow lazy authentication (i.e post-MongoClient construction) - -Drivers need to support both the shorter and longer SCRAM-SHA-1 and SCRAM-SHA-256 conversations over MongoDB's SASL implementation. Earlier versions of the server required an extra round trip due to an implementation decision. This was accomplished by sending no bytes back to the server, as seen in the following conversation (extra round trip emphasized): - -| C: :javascript:`{saslStart: 1, mechanism: "SCRAM-SHA-1", payload: BinData(0, "biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM"), options: {skipEmptyExchange: true}}` -| S: :javascript:`{conversationId : 1, payload: BinData(0,"cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0xIbytWZ2s3cXZVT0tVd3VXTElXZzRsLzlTcmFHTUhFRSxzPXJROVpZM01udEJldVAzRTFURFZDNHc9PSxpPTEwMDAw"), done: false, ok: 1}` -| C: :javascript:`{saslContinue: 1, conversationId: 1, payload: BinData(0, "Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMSG8rVmdrN3F2VU9LVXd1V0xJV2c0bC85U3JhR01IRUUscD1NQzJUOEJ2Ym1XUmNrRHc4b1dsNUlWZ2h3Q1k9")}` -| S: :javascript:`{conversationId: 1, payload: BinData(0,"dj1VTVdlSTI1SkQxeU5ZWlJNcFo0Vkh2aFo5ZTA9"), done: false, ok: 1}` -| **C**: :javascript:`{saslContinue: 1, conversationId: 1, payload: BinData(0, "")}` -| **S**: :javascript:`{conversationId: 1, payload: BinData(0,""), done: true, ok: 1}` - -The extra round trip will be removed in server version 4.4 when ``options: { skipEmptyExchange: true }`` is specified during ``saslStart``. - -Reference Implementation -======================== - -The Java and .NET drivers currently uses eager authentication and abide by this specification. - -Q & A -===== - -Q: According to `Authentication Handshake`_, we are calling ``hello`` or legacy hello 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: 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. - -Q: Should a driver support multiple credentials? - No. - - Historically, the MongoDB server and drivers have supported multiple credentials, one per authSource, on a single connection. It was necessary because early versions of MongoDB allowed a user to be granted privileges - to access the database in which the user was defined (or all databases in the special case of the "admin" database). But with the introduction of role-based access control in MongoDB 2.6, that restriction was - removed and it became possible to create applications that access multiple databases with a single authenticated user. - - Role-based access control also introduces the potential for accidental privilege escalation. An application may, for example, authenticate user A from authSource X, and user B from authSource Y, thinking that - user A has privileges only on collections in X and user B has privileges only on collections in Y. But with role-based access control that restriction no longer exists, and it's possible that user B has, for example, - more privileges on collections in X than user A does. Due to this risk it's generally safer to create a single user with only the privileges required for a given application, and authenticate only that one user - in the application. - - In addition, since only a single credential is supported per authSource, certain mechanisms are restricted to a single credential and some credentials cannot be used in conjunction (GSSAPI and X509 both use the "$external" database). - - Finally, MongoDB 3.6 introduces sessions, and allows at most a single authenticated user on any connection which makes use of one. Therefore any application that requires multiple authenticated users will not be able to make use of any feature that builds on sessions (e.g. retryable writes). - - Drivers should therefore guide application creators in the right direction by supporting the association of at most one credential with a MongoClient instance. - -Q: Should a driver support lazy authentication? - No, for the same reasons as given in the previous section, as lazy authentication is another mechanism for allowing multiple credentials to be associated with a single MongoClient instance. - -Q: Why does SCRAM sometimes SASLprep and sometimes not? - When MongoDB implemented SCRAM-SHA-1, it required drivers to *NOT* SASLprep - usernames and passwords. The primary reason for this was to allow a smooth - upgrade path from MongoDB-CR using existing usernames and passwords. - Also, because MongoDB's SCRAM-SHA-1 passwords are hex characters of a digest, - SASLprep of passwords was irrelevant. - - With the introduction of SCRAM-SHA-256, MongoDB requires users to - explicitly create new SCRAM-SHA-256 credentials distinct from those used - for MONGODB-CR and SCRAM-SHA-1. This means SCRAM-SHA-256 passwords are not - digested and any Unicode character could now appear in a password. - Therefore, the SCRAM-SHA-256 mechanism requires passwords to be normalized - with SASLprep, in accordance with the SCRAM RFC. - - However, usernames must be unique, which creates a similar upgrade path - problem. SASLprep maps multiple byte representations to a single - normalized one. An existing database could have multiple existing users - that map to the same SASLprep form, which makes it impossible to find the - correct user document for SCRAM authentication given only a SASLprep - username. After considering various options to address or workaround this - problem, MongoDB decided that the best user experience on upgrade and - lowest technical risk of implementation is to require drivers to continue - to not SASLprep usernames in SCRAM-SHA-256. - -Q: Should drivers support accessing Amazon EC2 instance metadata in Amazon ECS? - No. While it's possible to allow access to EC2 instance metadata in ECS, for security reasons, Amazon states it's best practice to avoid this. (See `accessing EC2 metadata in ECS `_ and `IAM Roles for Tasks `_) - - -Changelog -========= - -:2024-01-17: Added MONGODB-OIDC machine auth flow spec and combine with human - auth flow specs. -:2023-04-28: Added MONGODB-OIDC auth mechanism -:2022-11-02: Require environment variables to be read dynamically. -:2022-10-28: Recommend the use of AWS SDKs where available. -:2022-10-07: Require caching of AWS credentials fetched by the driver. -:2022-10-05: Remove spec front matter and convert version history to changelog. -:2022-09-07: Add support for AWS AssumeRoleWithWebIdentity. -:2022-01-20: Require that timeouts be applied per the client-side operations timeout spec. -:2022-01-14: Clarify that ``OP_MSG`` must be used for authentication when it is supported. -:2021-04-23: Updated to use hello and legacy hello. -:2021-03-04: Note that errors encountered during auth are handled by SDAM. -:2020-03-06: Add reference to the speculative authentication section of the handshake spec. -:2020-02-15: Rename MONGODB-IAM to MONGODB-AWS -:2020-02-04: Support shorter SCRAM conversation starting in version 4.4 of the server. -:2020-01-31: Clarify that drivers must raise an error when a connection string - has an empty value for authSource. -:2020-01-23: Clarify when authentication will occur. -:2020-01-22: Clarify that authSource in URI is not treated as a user configuring - auth credentials. -:2019-12-05: Added MONGODB-IAM auth mechanism -:2019-07-13: Clarify database to use for auth mechanism negotiation. -:2019-04-26: * Test format changed to improve specificity of behavior assertions. - * Clarify that database name in URI is not treated as a user configuring auth credentials. -:2018-08-08: Unknown users don't cause handshake errors. This was changed before - server 4.0 GA in SERVER-34421, so the auth spec no longer refers to - such a possibility. -:2018-04-17: * Clarify authSource defaults - * Fix PLAIN authSource rule to allow user provided values - * Change SCRAM-SHA-256 rules such that usernames are *NOT* - normalized; this follows a change in the server design and should - be available in server 4.0-rc0. -:2018-03-29: Clarify auth handshake and that it only applies to non-monitoring sockets. -:2018-03-15: Describe CANONICALIZE_HOST_NAME algorithm. -:2018-03-02: * Added SCRAM-SHA-256 and mechanism negotiation as provided by server 4.0 - * Updated default mechanism determination - * Clarified SCRAM-SHA-1 rules around SASLprep - * Require SCRAM-SHA-1 and SCRAM-SHA-256 to enforce a minimum iteration count -:2017-11-10: * Updated minimum server version to 2.6 - * Updated the Q & A to recommend support for at most a single credential per MongoClient - * Removed lazy authentication section - * Changed the list of server types requiring authentication - * Made providing username for X509 authentication optional -:2015-02-04: * 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 - * Added MONGODB-X509 - * Added PLAIN sasl mechanism - * Added support for GSSAPI mechanism property gssapiServiceName - ----- - -.. Section for links. - -.. _SDAM Monitoring Specification: /source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#why-mark-a-server-unknown-after-an-auth-error -.. _Why mark a server Unknown after an auth error: /source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#why-mark-a-server-unknown-after-an-auth-error diff --git a/source/auth/tests/README.md b/source/auth/tests/README.md new file mode 100644 index 0000000000..f7b1392fea --- /dev/null +++ b/source/auth/tests/README.md @@ -0,0 +1,47 @@ +# Auth Tests + +## Introduction + +This document describes the format of the driver spec tests included in the JSON and YAML files included in the `legacy` +sub-directory. Tests in the `unified` directory are written using the +[Unified Test Format](../../unified-test-format/unified-test-format.rst). + +The YAML and JSON files in the `legacy` directory tree are platform-independent tests that drivers can use to prove +their conformance to the Auth Spec at least with respect to connection string URI input. + +Drivers should do additional unit testing if there are alternate ways of configuring credentials on a client. + +Driver must also conduct the prose tests in the Auth Spec test plan section. + +## Format + +Each YAML file contains an object with a single `tests` key. This key is an array of test case objects, each of which +have the following keys: + +- `description`: A string describing the test. +- `uri`: A string containing the URI to be parsed. +- `valid:` A boolean indicating if the URI should be considered valid. +- `credential`: If null, the credential must not be considered configured for the the purpose of deciding if the driver + should authenticate to the topology. If non-null, it is an object containing one or more of the following properties + of a credential: + - `username`: A string containing the username. For auth mechanisms that do not utilize a password, this may be the + entire `userinfo` token from the connection string. + - `password`: A string containing the password. + - `source`: A string containing the authentication database. + - `mechanism`: A string containing the authentication mechanism. A null value for this key is used to indicate that a + mechanism wasn't specified and that mechanism negotiation is required. Test harnesses should modify the mechanism + test as needed to assert this condition. + - `mechanism_properties`: A document containing mechanism-specific properties. It specifies a subset of properties + that must match. If a key exists in the test data, it must exist with the corresponding value in the credential. + Other values may exist in the credential without failing the test. + +If any key is missing, no assertion about that key is necessary. Except as specified explicitly above, if a key is +present, but the test value is null, the observed value for that key must be uninitialized (whatever that means for a +given driver and data type). + +## Implementation notes + +Testing whether a URI is valid or not should simply be a matter of checking whether URI parsing (or MongoClient +construction) raises an error or exception. + +If a credential is configured, its properties must be compared to the `credential` field. diff --git a/source/auth/tests/README.rst b/source/auth/tests/README.rst deleted file mode 100644 index 8671f42816..0000000000 --- a/source/auth/tests/README.rst +++ /dev/null @@ -1,61 +0,0 @@ -========== -Auth Tests -========== - -Introduction -============ - -This document describes the format of the driver spec tests included in the -JSON and YAML files included in the ``legacy`` sub-directory. Tests in the -``unified`` directory are written using the `Unified Test Format -<../../unified-test-format/unified-test-format.rst>`_. - -The YAML and JSON files in the ``legacy`` directory tree are -platform-independent tests that drivers can use to prove their conformance to -the Auth Spec at least with respect to connection string URI input. - -Drivers should do additional unit testing if there are alternate ways of -configuring credentials on a client. - -Driver must also conduct the prose tests in the Auth Spec test plan section. - -Format -====== - -Each YAML file contains an object with a single ``tests`` key. This key is an -array of test case objects, each of which have the following keys: - -- ``description``: A string describing the test. -- ``uri``: A string containing the URI to be parsed. -- ``valid:`` A boolean indicating if the URI should be considered valid. -- ``credential``: If null, the credential must not be considered configured for the - the purpose of deciding if the driver should authenticate to the topology. If non-null, - it is an object containing one or more of the following properties of a credential: - - - ``username``: A string containing the username. For auth mechanisms - that do not utilize a password, this may be the entire ``userinfo`` token - from the connection string. - - ``password``: A string containing the password. - - ``source``: A string containing the authentication database. - - ``mechanism``: A string containing the authentication mechanism. A null value for - this key is used to indicate that a mechanism wasn't specified and that mechanism - negotiation is required. Test harnesses should modify the mechanism test as needed - to assert this condition. - - ``mechanism_properties``: A document containing mechanism-specific properties. It - specifies a subset of properties that must match. If a key exists in the test data, - it must exist with the corresponding value in the credential. Other values may - exist in the credential without failing the test. - -If any key is missing, no assertion about that key is necessary. Except as -specified explicitly above, if a key is present, but the test value is null, -the observed value for that key must be uninitialized (whatever that means for -a given driver and data type). - -Implementation notes -==================== - -Testing whether a URI is valid or not should simply be a matter of checking -whether URI parsing (or MongoClient construction) raises an error or exception. - -If a credential is configured, its properties must be compared to the -``credential`` field. diff --git a/source/auth/tests/mongodb-aws.md b/source/auth/tests/mongodb-aws.md new file mode 100644 index 0000000000..0f95a52ebf --- /dev/null +++ b/source/auth/tests/mongodb-aws.md @@ -0,0 +1,173 @@ +# MongoDB AWS + +Drivers MUST test the following scenarios: + +1. `Regular Credentials`: Auth via an `ACCESS_KEY_ID` and `SECRET_ACCESS_KEY` pair +2. `EC2 Credentials`: Auth from an EC2 instance via temporary credentials assigned to the machine +3. `ECS Credentials`: Auth from an ECS instance via temporary credentials assigned to the task +4. `Assume Role`: Auth via temporary credentials obtained from an STS AssumeRole request +5. `Assume Role with Web Identity`: Auth via temporary credentials obtained from an STS AssumeRoleWithWebIdentity + request +6. `AWS Lambda`: Auth via environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN`. +7. Caching of AWS credentials fetched by the driver. + +For brevity, this section gives the values ``, `` and `` in place of a valid access +key ID, secret access key and session token (also known as a security token). Note that if these values are passed into +the URI they MUST be URL encoded. Sample values are below. + +``` +AccessKeyId=AKIAI44QH8DHBEXAMPLE +SecretAccessKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY +Token=AQoDYXdzEJr... +``` + +## Regular credentials + +Drivers MUST be able to authenticate by providing a valid access key id and secret access key pair as the username and +password, respectively, in the MongoDB URI. An example of a valid URI would be: + +``` +mongodb://:@localhost/?authMechanism=MONGODB-AWS +``` + +## EC2 Credentials + +Drivers MUST be able to authenticate from an EC2 instance via temporary credentials assigned to the machine. A sample +URI on an EC2 machine would be: + +``` +mongodb://localhost/?authMechanism=MONGODB-AWS +``` + +> \[!NOTE\] +> +> No username, password or session token is passed into the URI. Drivers MUST query the EC2 instance endpoint to obtain +> these credentials. + +## ECS instance + +Drivers MUST be able to authenticate from an ECS container via temporary credentials. A sample URI in an ECS container +would be: + +``` +mongodb://localhost/?authMechanism=MONGODB-AWS +``` + +> \[!NOTE\] +> +> No username, password or session token is passed into the URI. Drivers MUST query the ECS container endpoint to obtain +> these credentials. + +## AssumeRole + +Drivers MUST be able to authenticate using temporary credentials returned from an assume role request. These temporary +credentials consist of an access key ID, a secret access key, and a security token passed into the URI. A sample URI +would be: + +``` +mongodb://:@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN: +``` + +## Assume Role with Web Identity + +Drivers MUST be able to authentiate using a valid OIDC token and associated role ARN taken from environment variables, +respectively: + +``` +AWS_WEB_IDENTITY_TOKEN_FILE +AWS_ROLE_ARN +AWS_ROLE_SESSION_NAME (optional) +``` + +A sample URI in for a web identity test would be: + +``` +mongodb://localhost/?authMechanism=MONGODB-AWS +``` + +Drivers MUST test with and without AWS_ROLE_SESSION_NAME set. + +> \[!NOTE\] +> +> No username, password or session token is passed into the URI. + +Drivers MUST check the environment variables listed above and make an +[AssumeRoleWithWebIdentity request](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html) +to obtain credentials. + +## AWS Lambda + +Drivers MUST be able to authenticate via an access key ID, secret access key and optional session token taken from the +environment variables, respectively: + +``` +AWS_ACCESS_KEY_ID +AWS_SECRET_ACCESS_KEY +AWS_SESSION_TOKEN +``` + +Sample URIs both with and without optional session tokens set are shown below. Drivers MUST test both cases. + +```bash +# without a session token +export AWS_ACCESS_KEY_ID="" +export AWS_SECRET_ACCESS_KEY="" + +URI="mongodb://localhost/?authMechanism=MONGODB-AWS" +``` + +```bash +# with a session token +export AWS_ACCESS_KEY_ID="" +export AWS_SECRET_ACCESS_KEY="" +export AWS_SESSION_TOKEN="" + +URI="mongodb://localhost/?authMechanism=MONGODB-AWS" +``` + +> \[!NOTE\] +> +> No username, password or session token is passed into the URI. Drivers MUST check the environment variables listed +> above for these values. If the session token is set Drivers MUST use it. + +## Cached Credentials + +Drivers MUST ensure that they are testing the ability to cache credentials. Drivers will need to be able to query and +override the cached credentials to verify usage. To determine whether to run the cache tests, the driver can check for +the absence of the AWS_ACCESS_KEY_ID environment variable and of credentials in the URI. + +01. Clear the cache. +02. Create a new client. +03. Ensure that a `find` operation adds credentials to the cache. +04. Override the cached credentials with an "Expiration" that is within one minute of the current UTC time. +05. Create a new client. +06. Ensure that a `find` operation updates the credentials in the cache. +07. Poison the cache with an invalid access key id. +08. Create a new client. +09. Ensure that a `find` operation results in an error. +10. Ensure that the cache has been cleared. +11. Ensure that a subsequent `find` operation succeeds. +12. Ensure that the cache has been set. + +If the drivers's language supports dynamically setting environment variables, add the following tests. Note that if +integration tests are run in parallel for the driver, then these tests must be run as unit tests interacting with the +auth provider directly instead of using a client. + +01. Clear the cache. +02. Create a new client. +03. Ensure that a `find` operation adds credentials to the cache. +04. Set the AWS environment variables based on the cached credentials. +05. Clear the cache. +06. Create a new client. +07. Ensure that a `find` operation succeeds and does not add credentials to the cache. +08. Set the AWS environment variables to invalid values. +09. Create a new client. +10. Ensure that a `find` operation results in an error. +11. Clear the AWS environment variables. +12. Clear the cache. +13. Create a new client. +14. Ensure that a `find` operation adds credentials to the cache. +15. Set the AWS environment variables to invalid values. +16. Create a new client. +17. Ensure that a `find` operation succeeds. +18. Clear the AWS environment variables. diff --git a/source/auth/tests/mongodb-aws.rst b/source/auth/tests/mongodb-aws.rst deleted file mode 100644 index 5240195e55..0000000000 --- a/source/auth/tests/mongodb-aws.rst +++ /dev/null @@ -1,171 +0,0 @@ -=========== -MongoDB AWS -=========== - -There are 6 scenarios drivers MUST test: - -#. ``Regular Credentials``: Auth via an ``ACCESS_KEY_ID`` and ``SECRET_ACCESS_KEY`` pair -#. ``EC2 Credentials``: Auth from an EC2 instance via temporary credentials assigned to the machine -#. ``ECS Credentials``: Auth from an ECS instance via temporary credentials assigned to the task -#. ``Assume Role``: Auth via temporary credentials obtained from an STS AssumeRole request -#. ``Assume Role with Web Identity``: Auth via temporary credentials obtained from an STS AssumeRoleWithWebIdentity request -#. ``AWS Lambda``: Auth via environment variables ``AWS_ACCESS_KEY_ID``, ``AWS_SECRET_ACCESS_KEY``, and ``AWS_SESSION_TOKEN``. -#. Caching of AWS credentials fetched by the driver. - -For brevity, this section gives the values ````, ```` and ```` in place of a valid access key ID, secret access key and session token (also known as a security token). Note that if these values are passed into the URI they MUST be URL encoded. Sample values are below. - -.. code-block:: - - AccessKeyId=AKIAI44QH8DHBEXAMPLE - SecretAccessKey=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY - Token=AQoDYXdzEJr... - -.. sectnum:: - -Regular credentials -====================== - -Drivers MUST be able to authenticate by providing a valid access key id and secret access key pair as the username and password, respectively, in the MongoDB URI. An example of a valid URI would be: - -.. code-block:: - - mongodb://:@localhost/?authMechanism=MONGODB-AWS - -EC2 Credentials -=============== - -Drivers MUST be able to authenticate from an EC2 instance via temporary credentials assigned to the machine. A sample URI on an EC2 machine would be: - -.. code-block:: - - mongodb://localhost/?authMechanism=MONGODB-AWS - -.. note:: No username, password or session token is passed into the URI. Drivers MUST query the EC2 instance endpoint to obtain these credentials. - -ECS instance -============ - -Drivers MUST be able to authenticate from an ECS container via temporary credentials. A sample URI in an ECS container would be: - -.. code-block:: - - mongodb://localhost/?authMechanism=MONGODB-AWS - -.. note:: No username, password or session token is passed into the URI. Drivers MUST query the ECS container endpoint to obtain these credentials. - -AssumeRole -========== - -Drivers MUST be able to authenticate using temporary credentials returned from an assume role request. These temporary credentials consist of an access key ID, a secret access key, and a security token passed into the URI. A sample URI would be: - -.. code-block:: - - mongodb://:@localhost/?authMechanism=MONGODB-AWS&authMechanismProperties=AWS_SESSION_TOKEN: - -Assume Role with Web Identity -============================= - -Drivers MUST be able to authentiate using a valid OIDC token and associated -role ARN taken from environment variables, respectively: - -.. code-block:: - - AWS_WEB_IDENTITY_TOKEN_FILE - AWS_ROLE_ARN - AWS_ROLE_SESSION_NAME (optional) - -A sample URI in for a web identity test would be: - -.. code-block:: - - mongodb://localhost/?authMechanism=MONGODB-AWS - -Drivers MUST test with and without AWS_ROLE_SESSION_NAME set. - -.. note:: No username, password or session token is passed into the URI. - -Drivers MUST check the environment variables listed above and make an `AssumeRoleWithWebIdentity request `_ to obtain -credentials. - -AWS Lambda -========== - -Drivers MUST be able to authenticate via an access key ID, secret access key and optional session token taken from the environment variables, respectively: - -.. code-block:: - - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY - AWS_SESSION_TOKEN - -Sample URIs both with and without optional session tokens set are shown below. Drivers MUST test both cases. - -.. code-block:: bash - - # without a session token - export AWS_ACCESS_KEY_ID="" - export AWS_SECRET_ACCESS_KEY="" - - URI="mongodb://localhost/?authMechanism=MONGODB-AWS" - -.. code-block:: bash - - # with a session token - export AWS_ACCESS_KEY_ID="" - export AWS_SECRET_ACCESS_KEY="" - export AWS_SESSION_TOKEN="" - - URI="mongodb://localhost/?authMechanism=MONGODB-AWS" - -.. note:: No username, password or session token is passed into the URI. Drivers MUST check the environment variables listed above for these values. If the session token is set Drivers MUST use it. - - -Cached Credentials -================== - -Drivers MUST ensure that they are testing the ability to cache credentials. -Drivers will need to be able to query and override the cached credentials to -verify usage. To determine whether to run the cache tests, the driver can -check for the absence of the AWS_ACCESS_KEY_ID environment variable and of -credentials in the URI. - -#. Clear the cache. -#. Create a new client. -#. Ensure that a ``find`` operation adds credentials to the cache. -#. Override the cached credentials with an "Expiration" that is within one - minute of the current UTC time. -#. Create a new client. -#. Ensure that a ``find`` operation updates the credentials in the cache. - -#. Poison the cache with an invalid access key id. -#. Create a new client. -#. Ensure that a ``find`` operation results in an error. -#. Ensure that the cache has been cleared. -#. Ensure that a subsequent ``find`` operation succeeds. -#. Ensure that the cache has been set. - -If the drivers's language supports dynamically setting environment variables, -add the following tests. Note that if integration tests are run in -parallel for the driver, then these tests must be run as unit tests interacting -with the auth provider directly instead of using a client. - -#. Clear the cache. -#. Create a new client. -#. Ensure that a ``find`` operation adds credentials to the cache. -#. Set the AWS environment variables based on the cached credentials. -#. Clear the cache. -#. Create a new client. -#. Ensure that a ``find`` operation succeeds and does not add credentials to - the cache. -#. Set the AWS environment variables to invalid values. -#. Create a new client. -#. Ensure that a ``find`` operation results in an error. -#. Clear the AWS environment variables. - -#. Clear the cache. -#. Create a new client. -#. Ensure that a ``find`` operation adds credentials to the cache. -#. Set the AWS environment variables to invalid values. -#. Create a new client. -#. Ensure that a ``find`` operation succeeds. -#. Clear the AWS environment variables. diff --git a/source/auth/tests/mongodb-oidc.md b/source/auth/tests/mongodb-oidc.md new file mode 100644 index 0000000000..e61cdfd464 --- /dev/null +++ b/source/auth/tests/mongodb-oidc.md @@ -0,0 +1,390 @@ +# MongoDB OIDC + +## Local Testing + +To test locally, use the +[oidc_get_tokens.sh](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/auth_oidc/oidc_get_tokens.sh) +script from [drivers-evergreen-tools](https://github.com/mongodb-labs/drivers-evergreen-tools/) to download a set of +OIDC tokens, including `test_user1` and `test_user1_expires`. You first have to install the AWS CLI and login using the +SSO flow. + +For example, if the selected AWS profile ID is "drivers-test", run: + +```shell +aws configure sso +export OIDC_TOKEN_DIR=/tmp/tokens +AWS_PROFILE="drivers-test" oidc_get_tokens.sh +AWS_WEB_IDENTITY_TOKEN_FILE="$OIDC_TOKEN_DIR/test_user1" /my/test/command +``` + +______________________________________________________________________ + +## Prose Tests + +Drivers MUST implement all prose tests in this section. Unless otherwise noted, all `MongoClient` instances MUST be +configured with `retryReads=false`. + +> \[!NOTE\] +> +> For test cases that create fail points, drivers MUST either use a unique `appName` or explicitly remove the fail point +> after the test to prevent interaction between test cases. + +Note that typically the preconfigured Atlas Dev clusters are used for testing, in Evergreen and locally. The URIs can be +fetched from the `drivers/oidc` Secrets vault, see +[vault instructions](https://wiki.corp.mongodb.com/display/DRIVERS/Using+AWS+Secrets+Manager+to+Store+Testing+Secrets). +Use `OIDC_ATLAS_URI_SINGLE` for the `MONGODB_URI`. If using local servers is preferred, using the +[Local Testing](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/auth_oidc/README.md#local-testing) +method, use `mongodb://localhost/?authMechanism=MONGODB-OIDC` for `MONGODB_URI`. + +### (1) OIDC Callback Authentication + +**1.1 Callback is called during authentication** + +- Create a `MongoClient` configured with an OIDC callback that implements the AWS provider logic. +- Perform a `find` operation that succeeds. +- Assert that the callback was called 1 time. +- Close the client. + +**1.2 Callback is called once for multiple connections** + +- Create a `MongoClient` configured with an OIDC callback that implements the AWS provider logic. +- Start 10 threads and run 100 `find` operations in each thread that all succeed. +- Assert that the callback was called 1 time. +- Close the client. + +### (2) OIDC Callback Validation + +**2.1 Valid Callback Inputs** + +- Create a `MongoClient` configured with an OIDC callback that validates its inputs and returns a valid access token. +- Perform a `find` operation that succeeds. +- Assert that the OIDC callback was called with the appropriate inputs, including the timeout parameter if possible. +- Close the client. + +**2.2 OIDC Callback Returns Null** + +- Create a `MongoClient` configured with an OIDC callback that returns `null`. +- Perform a `find` operation that fails. +- Close the client. + +**2.3 OIDC Callback Returns Missing Data** + +- Create a `MongoClient` configured with an OIDC callback that returns data not conforming to the `OIDCCredential` with + missing fields. +- Perform a `find` operation that fails. +- Close the client. + +**2.4 Invalid Client Configuration with Callback** + +- Create a `MongoClient` configured with an OIDC callback and auth mechanism property `PROVIDER_NAME:aws`. +- Assert it returns a client configuration error. + +### (3) Authentication Failure + +**3.1 Authentication failure with cached tokens fetch a new token and retry auth** + +- Create a `MongoClient` configured with an OIDC callback that implements the AWS provider logic. +- Poison the *Client Cache* with an invalid access token. +- Perform a `find` operation that succeeds. +- Assert that the callback was called 1 time. +- Close the client. + +**3.2 Authentication failures without cached tokens return an error** + +- Create a `MongoClient` configured with an OIDC callback that always returns invalid access tokens. +- Perform a `find` operation that fails. +- Assert that the callback was called 1 time. +- Close the client. + +### (4) Reauthentication + +- Create a `MongoClient` configured with an OIDC callback that implements the AWS provider logic. +- Set a fail point for `find` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform a `find` operation that succeeds. +- Assert that the callback was called 2 times (once during the connection handshake, and again during reauthentication). +- Close the client. + +______________________________________________________________________ + +## Human Authentication Flow Prose Tests + +Drivers that support the [Human Authentication Flow](../auth.md#human-authentication-flow) MUST implement all prose +tests in this section. Unless otherwise noted, all `MongoClient` instances MUST be configured with `retryReads=false`. + +> \[!NOTE\] +> +> For test cases that create fail points, drivers MUST either use a unique `appName` or explicitly remove the fail point +> after the test to prevent interaction between test cases. + +Drivers MUST be able to authenticate against a server configured with either one or two configured identity providers. + +Note that typically the preconfigured Atlas Dev clusters are used for testing, in Evergreen and locally. The URIs can be +fetched from the `drivers/oidc` Secrets vault, see +[vault instructions](https://wiki.corp.mongodb.com/display/DRIVERS/Using+AWS+Secrets+Manager+to+Store+Testing+Secrets). +Use `OIDC_ATLAS_URI_SINGLE` for `MONGODB_URI_SINGLE` and `OIDC_ATLAS_URI_MULTI` for `MONGODB_URI_MULTI`. Currently the +`OIDC_ATLAS_URI_MULTI` cluster does not work correctly with fail points, so all prose tests that use fail points SHOULD +use `OIDC_ATLAS_URI_SINGLE`. + +If using local servers is preferred, using the +[Local Testing](https://github.com/mongodb-labs/drivers-evergreen-tools/blob/master/.evergreen/auth_oidc/README.md#local-testing) +method, use `mongodb://localhost/?authMechanism=MONGODB-OIDC` for `MONGODB_URI_SINGLE` and +`mongodb://localhost:27018/?authMechanism=MONGODB-OIDC&directConnection=true&readPreference=secondaryPreferred` for +`MONGODB_URI_MULTI` because the other server is a secondary on a replica set, on port `27018`. + +The default OIDC client used in the tests is configured with `MONGODB_URI_SINGLE` and a valid human callback handler +that returns the `test_user1` local token in `OIDC_TOKEN_DIR` as the "access_token", and a dummy "refresh_token". + +### (1) OIDC Human Callback Authentication + +Drivers MUST be able to authenticate using OIDC callback(s) when there is one principal configured. + +**1.1 Single Principal Implicit Username** + +- Create default OIDC client with `authMechanism=MONGODB-OIDC`. +- Perform a `find` operation that succeeds. +- Close the client. + +**1.2 Single Principal Explicit Username** + +- Create a client with `MONGODB_URI_SINGLE`, a username of `test_user1`, `authMechanism=MONGODB-OIDC`, and the OIDC + human callback. +- Perform a `find` operation that succeeds. +- Close the client. + +**1.3 Multiple Principal User 1** + +- Create a client with `MONGODB_URI_MULTI`, a username of `test_user1`, `authMechanism=MONGODB-OIDC`, and the OIDC human + callback. +- Perform a `find` operation that succeeds. +- Close the client. + +**1.4 Multiple Principal User 2** + +- Create a human callback that reads in the generated `test_user2` token file. +- Create a client with `MONGODB_URI_MULTI`, a username of `test_user2`, `authMechanism=MONGODB-OIDC`, and the OIDC human + callback. +- Perform a `find` operation that succeeds. +- Close the client. + +**1.5 Multiple Principal No User** + +- Create a client with `MONGODB_URI_MULTI`, no username, `authMechanism=MONGODB-OIDC`, and the OIDC human callback. +- Assert that a `find` operation fails. +- Close the client. + +**1.6 Allowed Hosts Blocked** + +- Create a default OIDC client, with an `ALLOWED_HOSTS` that is an empty list. +- Assert that a `find` operation fails with a client-side error. +- Close the client. +- Create a client that uses the URL `mongodb://localhost/?authMechanism=MONGODB-OIDC&ignored=example.com`, a human + callback, and an `ALLOWED_HOSTS` that contains `["example.com"]`. +- Assert that a `find` operation fails with a client-side error. +- Close the client. + +### (2) OIDC Human Callback Validation + +**2.1 Valid Callback Inputs** + +- Create a `MongoClient` with a human callback that validates its inputs and returns a valid access token. +- Perform a `find` operation that succeeds. Verify that the human callback was called with the appropriate inputs, + including the timeout parameter if possible. +- Close the client. + +**2.3 Human Callback Returns Missing Data** + +- Create a `MongoClient` with a human callback that returns data not conforming to the `OIDCCredential` with missing + fields. +- Perform a `find` operation that fails. +- Close the client. + +### (3) Speculative Authentication + +**3.1 Uses speculative authentication if there is a cached token** + +- Create a `MongoClient` with a human callback that returns a valid token. +- Set a fail point for `find` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find" + ], + closeConnection: true + } +} +``` + +- Perform a `find` operation that fails. +- Set a fail point for `saslStart` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: "alwaysOn", + data: { + failCommands: [ + "saslStart" + ], + errorCode: 20 + } +} +``` + +- Perform a `find` operation that succeeds. +- Close the client. + +**3.2 Does not use speculative authentication if there is no cached token** + +- Create a `MongoClient` with a human callback that returns a valid token. +- Set a fail point for `saslStart` commands of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: "alwaysOn", + data: { + failCommands: [ + "saslStart" + ], + errorCode: 20 // IllegalOperation + } +} +``` + +- Perform a `find` operation that fails. +- Close the client. + +### (4) Reauthentication + +**4.1 Succeeds** + +- Create a default OIDC client and add an event listener. The following assumes that the driver does not emit + `saslStart` or `saslContinue` events. If the driver does emit those events, ignore/filter them for the purposes of + this test. +- Perform a `find` operation that succeeds. +- Assert that the human callback has been called once. +- Clear the listener state if possible. +- Force a reauthenication using a fail point of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform another find operation that succeeds. +- Assert that the human callback has been called twice. +- Assert that the ordering of list started events is \[`find`\], , `find`. Note that if the listener stat could not be + cleared then there will and be extra `find` command. +- Assert that the list of command succeeded events is \[`find`\]. +- Assert that a `find` operation failed once during the command execution. +- Close the client. + +**4.2 Succeeds no refresh** + +- Create a default OIDC client with a human callback that does not return a refresh token. +- Perform a `find` operation that succeeds. +- Assert that the human callback has been called once. +- Force a reauthenication using a fail point of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 1 + }, + data: { + failCommands: [ + "find" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform a `find` operation that succeeds. +- Assert that the human callback has been called twice. +- Close the client. + +**4.3 Succeeds after refresh fails** + +- Create a default OIDC client. +- Perform a `find` operation that succeeds. +- Assert that the human callback has been called once. +- Force a reauthenication using a fail point of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 2 + }, + data: { + failCommands: [ + "find", "saslStart" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform a `find` operation that succeeds. +- Assert that the human callback has been called 3 times. +- Close the client. + +**4.4 Fails** + +- Create a default OIDC client. +- Perform a find operation that succeeds (to force a speculative auth). +- Assert that the human callback has been called once. +- Force a reauthenication using a failCommand of the form: + +```javascript +{ + configureFailPoint: "failCommand", + mode: { + times: 3 + }, + data: { + failCommands: [ + "find", "saslStart" + ], + errorCode: 391 // ReauthenticationRequired + } +} +``` + +- Perform a find operation that fails. +- Assert that the human callback has been called twice. +- Close the client. diff --git a/source/auth/tests/mongodb-oidc.rst b/source/auth/tests/mongodb-oidc.rst index 6c345ec8cf..682f9c82aa 100644 --- a/source/auth/tests/mongodb-oidc.rst +++ b/source/auth/tests/mongodb-oidc.rst @@ -148,7 +148,7 @@ Human Authentication Flow Prose Tests ===================================== Drivers that support the `Human Authentication Flow -<../auth/auth.rst#human-authentication-flow>`_ MUST implement all prose tests in +<../auth.md#human-authentication-flow>`_ MUST implement all prose tests in this section. Unless otherwise noted, all ``MongoClient`` instances MUST be configured with ``retryReads=false``. diff --git a/source/client-side-encryption/client-side-encryption.rst b/source/client-side-encryption/client-side-encryption.rst index 166e4bdf2d..c215a905ce 100644 --- a/source/client-side-encryption/client-side-encryption.rst +++ b/source/client-side-encryption/client-side-encryption.rst @@ -650,7 +650,7 @@ following process: 6. Return `P` as the additional KMS providers to libmongocrypt_. -__ ../auth/auth.rst#obtaining-credentials +__ ../auth/auth.md#obtaining-credentials .. default-role:: literal diff --git a/source/client-side-operations-timeout/client-side-operations-timeout.md b/source/client-side-operations-timeout/client-side-operations-timeout.md index a4836a5222..0082bad9e8 100644 --- a/source/client-side-operations-timeout/client-side-operations-timeout.md +++ b/source/client-side-operations-timeout/client-side-operations-timeout.md @@ -152,29 +152,29 @@ The following pieces of operation execution are considered blocking: 1. Implicit session acquisition if an explicit session was not provided for the operation. This is only considered blocking for drivers that perform server selection to determine session support when acquiring implicit sessions. -1. Server selection -1. Connection checkout - If `maxPoolSize` has already been reached for the selected server, this is the amount of time +2. Server selection +3. Connection checkout - If `maxPoolSize` has already been reached for the selected server, this is the amount of time spent waiting for a connection to be available. -1. Connection establishment - If the pool for the selected server is empty and a new connection is needed, the following +4. Connection establishment - If the pool for the selected server is empty and a new connection is needed, the following pieces of connection establishment are considered blocking: 1. TCP socket establishment - 1. TLS handshake + 2. TLS handshake 1. All messages sent over the socket as part of the TLS handshake - 1. OCSP verification - HTTP requests sent to OCSP responders. - 1. MongoDB handshake (i.e. initial connection `hello`) - 1. Authentication + 2. OCSP verification - HTTP requests sent to OCSP responders. + 3. MongoDB handshake (i.e. initial connection `hello`) + 4. Authentication 1. SCRAM-SHA-1, SCRAM-SHA-256, PLAIN: Execution of the command required for the SASL conversation. - 1. GSSAPI: Execution of the commands required for the SASL conversation and requests to the KDC and TGS. - 1. MONGODB-AWS: Execution of the commands required for the SASL conversation and all HTTP requests to ECS and EC2 + 2. GSSAPI: Execution of the commands required for the SASL conversation and requests to the KDC and TGS. + 3. MONGODB-AWS: Execution of the commands required for the SASL conversation and all HTTP requests to ECS and EC2 endpoints. - 1. MONGODB-X509: Execution of the commands required for the authentication conversation. -1. Client-side encryption + 4. MONGODB-X509: Execution of the commands required for the authentication conversation. +5. Client-side encryption 1. Execution of `listCollections` commands to get collection schemas. - 1. Execution of `find` commands against the key vault collection to get encrypted data keys. - 1. Requests to non-local key management servers (e.g. AWS KMS) to decrypt data keys. - 1. Requests to mongocryptd servers. -1. Socket write to send a command to the server -1. Socket read to receive the server’s response + 2. Execution of `find` commands against the key vault collection to get encrypted data keys. + 3. Requests to non-local key management servers (e.g. AWS KMS) to decrypt data keys. + 4. Requests to mongocryptd servers. +6. Socket write to send a command to the server +7. Socket read to receive the server’s response The `timeoutMS` option MUST apply to all blocking sections. Drivers MUST document any exceptions. For example, drivers that do not have full control over OCSP verification might not be able to set timeouts for HTTP requests to responders @@ -368,9 +368,9 @@ The [SessionOptions](../sessions/driver-sessions.rst#mongoclient-changes) used t which specifies the `timeoutMS` value for the following operations executed on the session: 1. commitTransaction -1. abortTransaction -1. withTransaction -1. endSession +2. abortTransaction +3. withTransaction +4. endSession If this option is not specified for a `ClientSession`, it MUST inherit the `timeoutMS` of its parent MongoClient. diff --git a/source/connection-monitoring-and-pooling/connection-monitoring-and-pooling.md b/source/connection-monitoring-and-pooling/connection-monitoring-and-pooling.md index f58fd97cef..44aed99cb1 100644 --- a/source/connection-monitoring-and-pooling/connection-monitoring-and-pooling.md +++ b/source/connection-monitoring-and-pooling/connection-monitoring-and-pooling.md @@ -183,7 +183,7 @@ properties: MongoDB Handshake and Authentication as specified in the [Handshake](https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.rst), [OP_COMPRESSED](https://github.com/mongodb/specifications/blob/master/source/compression/OP_COMPRESSED.rst), and - [Authentication](https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst) specifications. + [Authentication](../auth/auth.md) specifications. - **Perishable**: it is possible for a [Connection](#connection-1) to become **Perished**. A [Connection](#connection-1) is considered perished if any of the following are true: - **Stale:** The [Connection](#connection-1) 's generation does not match the generation of the parent pool @@ -1304,7 +1304,7 @@ endpoint it is associated with is available or not. This enables the following b Without the "paused" state, the pool would have no way of determining when to begin establishing background connections again, so it would just continually attempt, and often fail, to create connections until minPoolSize was satisfied, even after repeated failures. This could unnecessarily waste resources both server and driver side. -1. The pool can evict requests that enter the WaitQueue after the pool was cleared but before the server was in a known +2. The pool can evict requests that enter the WaitQueue after the pool was cleared but before the server was in a known state again. Such requests can occur when a server is selected at the same time as it becomes marked as Unknown in highly concurrent workloads. Without the "paused" state, the pool would attempt to service these requests, since it would assume they were routed to the pool because its endpoint was available, not because of a race between SDAM and diff --git a/source/connection-monitoring-and-pooling/tests/README.md b/source/connection-monitoring-and-pooling/tests/README.md index 5d19b8ef13..578f5d9208 100644 --- a/source/connection-monitoring-and-pooling/tests/README.md +++ b/source/connection-monitoring-and-pooling/tests/README.md @@ -15,10 +15,10 @@ Drivers MUST implement all of the following types of CMAP tests: The following tests have not yet been automated, but MUST still be tested: 1. All ConnectionPoolOptions MUST be specified at the MongoClient level -1. All ConnectionPoolOptions MUST be the same for all pools created by a MongoClient -1. A user MUST be able to specify all ConnectionPoolOptions via a URI string -1. A user MUST be able to subscribe to Connection Monitoring Events in a manner idiomatic to their language and driver -1. When a check out attempt fails because connection set up throws an error, assert that a ConnectionCheckOutFailedEvent +2. All ConnectionPoolOptions MUST be the same for all pools created by a MongoClient +3. A user MUST be able to specify all ConnectionPoolOptions via a URI string +4. A user MUST be able to subscribe to Connection Monitoring Events in a manner idiomatic to their language and driver +5. When a check out attempt fails because connection set up throws an error, assert that a ConnectionCheckOutFailedEvent with reason="connectionError" is emitted. ## Logging Tests diff --git a/source/mongodb-handshake/handshake.rst b/source/mongodb-handshake/handshake.rst index 6c012a6259..217d78a37c 100644 --- a/source/mongodb-handshake/handshake.rst +++ b/source/mongodb-handshake/handshake.rst @@ -402,17 +402,17 @@ the initial handshake reply. When the mechanism is ``MONGODB-X509``, ``speculativeAuthenticate`` has the same structure as seen in the MONGODB-X509 conversation section in the -`Driver Authentication spec `_. +`Driver Authentication spec <../auth/auth.md#supported-authentication-methods>`_. When the mechanism is ``SCRAM-SHA-1`` or ``SCRAM-SHA-256``, ``speculativeAuthenticate`` has the same fields as seen in the conversation subsection of the SCRAM-SHA-1 and -SCRAM-SHA-256 sections in the `Driver Authentication spec `_ +SCRAM-SHA-256 sections in the `Driver Authentication spec <../auth/auth.md#supported-authentication-methods>`_ with an additional ``db`` field to specify the name of the authentication database. When the mechanism is ``MONGODB-OIDC``, ``speculativeAuthenticate`` has the same structure as seen in the MONGODB-OIDC conversation section in the `Driver Authentication spec -`_. +<../auth/auth.md#supported-authentication-methods>`_. If the initial handshake command with a ``speculativeAuthenticate`` argument succeeds, the client should proceed with the next step of the exchange. If the initial handshake @@ -422,7 +422,7 @@ authentication handshake. The ``speculativeAuthenticate`` reply has the same fields, except for the ``ok`` field, as seen in the conversation sections for MONGODB-X509, SCRAM-SHA-1 and SCRAM-SHA-256 -in the `Driver Authentication spec `_. +in the `Driver Authentication spec <../auth/auth.md#supported-authentication-methods>`_. If an authentication mechanism is not provided either via connection string or code, but a credential is provided, drivers MUST use the SCRAM-SHA-256 mechanism for speculative diff --git a/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst b/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst index e349fabeb2..ad1804fb2a 100644 --- a/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst +++ b/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst @@ -1665,7 +1665,7 @@ are prone to several classes of race, for example: * Authentication fails, the server requires SCRAM-SHA-1. Better to call hello or legacy hello for each new socket, as required by the `Auth Spec -`_, +<../auth/auth.md>`_, and use the hello or legacy hello response associated with that socket for maxWireVersion, maxBsonObjectSize, etc.: all the fields required to correctly communicate with the server. @@ -2559,5 +2559,5 @@ Changelog .. _single-threaded monitoring: server-monitoring.rst#single-threaded-monitoring .. _Connection Monitoring and Pooling spec: ../connection-monitoring-and-pooling/connection-monitoring-and-pooling.md .. _CMAP spec: ../connection-monitoring-and-pooling/connection-monitoring-and-pooling.md -.. _Authentication spec: /source/auth/auth.rst +.. _Authentication spec: ../auth/auth.md .. _Server Monitoring (Measuring RTT): server-monitoring.rst#measuring-rtt diff --git a/source/uri-options/uri-options.rst b/source/uri-options/uri-options.rst index d5a88703c1..d165947ad5 100644 --- a/source/uri-options/uri-options.rst +++ b/source/uri-options/uri-options.rst @@ -129,9 +129,9 @@ pertaining to URI options apply here. * - authMechanism - any string; valid values are defined in the `auth spec - `_ + <../auth/auth.md#supported-authentication-methods>`_ - None; default values for authentication exist for constructing authentication credentials per the - `auth spec `_, + `auth spec <../auth/auth.md#supported-authentication-methods>`_, but there is no default for the URI option itself. - no - The authentication mechanism method to use for connection to the @@ -146,7 +146,7 @@ pertaining to URI options apply here. * - authSource - any string - None; default values for authentication exist for constructing authentication credentials per the - `auth spec `_, + `auth spec <../auth/auth.md#supported-authentication-methods>`_, but there is no default for the URI option itself. - no - The database that connections should authenticate against