SERVER-126468: Support client authentication in NetworkInterfaceTL (#53705)

GitOrigin-RevId: 5fe2a8c85ef5238b26a909a2d254813abfe574fa
This commit is contained in:
Matt Broadstone 2026-05-20 18:09:11 -04:00 committed by MongoDB Bot
parent 4617bedb9a
commit 0f2f1ffc39
44 changed files with 1577 additions and 494 deletions

View File

@ -12,6 +12,20 @@ exports_files(
# Contains only the core ConnectionString functionality, *not* the ability to
# call connect() and return a DBClientBase* back. For that you need to link
# against the 'clientdriver_network' library.
mongo_cc_library(
name = "credential",
srcs = [
"credential.cpp",
],
hdrs = [
"credential.h",
],
deps = [
"//src/mongo/bson/util:bson_extract",
"//src/mongo/db/auth:auth_mechanism",
],
)
mongo_cc_library(
name = "connection_string",
srcs = [
@ -19,6 +33,7 @@ mongo_cc_library(
"mongo_uri.cpp",
],
deps = [
":credential",
"//src/mongo/util:dns_query",
"//src/mongo/util/net:network",
"//src/mongo/util/options_parser",
@ -127,6 +142,7 @@ mongo_cc_library(
],
deps = [
":connection_string",
":credential",
":internal_auth",
":native_sasl_client",
"//src/mongo/bson/util:bson_extract",
@ -492,6 +508,7 @@ mongo_cc_unit_test(
"authenticate_test.cpp",
"backoff_with_jitter_test.cpp",
"connection_string_test.cpp",
"credential_test.cpp",
"dbclient_cursor_test.cpp",
"fetcher_test.cpp",
"index_spec_test.cpp",
@ -670,3 +687,15 @@ mongo_cc_library(
":backoff_with_jitter",
],
)
mongo_cc_unit_test(
name = "sasl_client_authenticate_impl_test",
srcs = [
"sasl_client_authenticate_impl_test.cpp",
],
tags = ["mongo_unittest_fourth_group"],
deps = [
":native_sasl_client",
"//src/mongo/db:server_base",
],
)

View File

@ -196,7 +196,7 @@ auth::RunCommandHook AsyncDBClient::_makeAuthRunCommandHook() {
};
}
Future<void> AsyncDBClient::authenticate(const BSONObj& params) {
Future<void> AsyncDBClient::authenticate(const auth::Credential& credential) {
// We will only have a valid clientName if SSL is enabled.
std::string clientName;
#ifdef MONGO_CONFIG_SSL
@ -205,7 +205,7 @@ Future<void> AsyncDBClient::authenticate(const BSONObj& params) {
}
#endif
return auth::authenticateClient(params, remote(), clientName, _makeAuthRunCommandHook());
return auth::authenticateClient(credential, remote(), clientName, _makeAuthRunCommandHook());
}
Future<void> AsyncDBClient::authenticateInternal(

View File

@ -117,7 +117,7 @@ public:
const BatonHandle& baton = nullptr,
const CancellationToken& token = CancellationToken::uncancelable());
Future<void> authenticate(const BSONObj& params);
Future<void> authenticate(const auth::Credential& credential);
Future<void> authenticateInternal(
boost::optional<std::string> mechanismHint,

View File

@ -34,13 +34,10 @@
#include "mongo/base/status.h"
#include "mongo/base/status_with.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/bson/util/bson_extract.h"
#include "mongo/bson/util/builder.h"
#include "mongo/bson/util/builder_fwd.h"
#include "mongo/client/internal_auth.h"
#include "mongo/client/mongo_uri.h"
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/config.h" // IWYU pragma: keep
#include "mongo/db/auth/authorization_manager.h"
@ -77,85 +74,55 @@ using AuthRequest = StatusWith<RemoteCommandRequest>;
namespace {
const char* const kUserSourceFieldName = "userSource";
const BSONObj kGetNonceCmd = BSON("getnonce" << 1);
StatusWith<std::string> extractDBField(const BSONObj& params) {
std::string db;
if (params.hasField(kUserSourceFieldName)) {
if (!bsonExtractStringField(params, kUserSourceFieldName, &db).isOK()) {
return {ErrorCodes::AuthenticationFailed, "userSource field must contain a string"};
}
} else {
if (!bsonExtractStringField(params, saslCommandUserDBFieldName, &db).isOK()) {
return {ErrorCodes::AuthenticationFailed, "db field must contain a string"};
}
}
} // namespace
return std::move(db);
}
namespace {
//
// X-509
//
StatusWith<OpMsgRequest> createX509AuthCmd(const BSONObj& params, StringData clientName) {
StatusWith<OpMsgRequest> createX509AuthCmd(const Credential& cred, StringData clientName) {
if (clientName.empty()) {
return {ErrorCodes::AuthenticationFailed,
"Please enable SSL on the client-side to use the MONGODB-X509 authentication "
"mechanism."};
}
auto db = extractDBField(params);
if (!db.isOK())
return db.getStatus();
std::string username;
auto response = bsonExtractStringFieldWithDefault(
params, saslCommandUserFieldName, std::string{clientName}, &username);
if (!response.isOK()) {
return response;
}
const std::string& username =
(cred.username && !cred.username->empty()) ? *cred.username : std::string{clientName};
if (username != std::string{clientName}) {
StringBuilder message;
message << "Username \"";
message << params[saslCommandUserFieldName].valueStringData();
message << "\" does not match the provided client certificate user \"";
message << std::string{clientName} << "\"";
return {ErrorCodes::AuthenticationFailed, message.str()};
return {ErrorCodes::AuthenticationFailed,
str::stream() << "Username \"" << username
<< "\" does not match the provided client certificate user \""
<< clientName << "\""};
}
return OpMsgRequestBuilder::create(
auth::ValidatedTenancyScope::kNotRequired /* db is not tenanted */,
AuthDatabaseNameUtil::deserialize(db.getValue()),
BSON("authenticate" << 1 << "mechanism"
<< "MONGODB-X509"
<< "user" << username));
AuthDatabaseNameUtil::deserialize(cred.db.value_or("$external")),
BSON("authenticate" << 1 << "mechanism" << kMechanismMongoX509 << "user" << username));
}
// Use the MONGODB-X509 protocol to authenticate as "username." The certificate details
// have already been communicated automatically as part of the connect call.
Future<void> authX509(RunCommandHook runCommand,
const HostAndPort& hostname,
const BSONObj& params,
const Credential& cred,
StringData clientName) {
invariant(runCommand);
// Just 1 step: send authenticate command, receive response
auto swAuthRequest = createX509AuthCmd(params, clientName);
auto swAuthRequest = createX509AuthCmd(cred, clientName);
if (!swAuthRequest.isOK())
return swAuthRequest.getStatus();
auto swTargetDb = extractDBField(params);
if (!swTargetDb.isOK()) {
return swTargetDb.getStatus();
}
auto targetDb = std::move(swTargetDb.getValue());
auto mechCounter = authCounter.getEgressMechanismCounter(kMechanismMongoX509);
mechCounter.incAuthenticateSent();
auto argsBlock =
std::make_tuple(hostname, params, std::string(clientName), targetDb, mechCounter);
auto argsBlock = std::make_tuple(
hostname, cred, std::string(clientName), cred.db.value_or("$external"), mechCounter);
auto sharedBlock = std::make_shared<decltype(argsBlock)>(std::move(argsBlock));
auto metricsRecorder = std::make_shared<AuthMetricsRecorder>();
@ -166,13 +133,13 @@ Future<void> authX509(RunCommandHook runCommand,
return runCommand(swAuthRequest.getValue())
.then([metricsRecorder, sharedBlock](const BSONObj& obj) {
BSONObj metrics = metricsRecorder->captureEgress();
auto [hostname, params, clientName, targetDb, mechCounter] = *sharedBlock.get();
auto [hostname, cred, clientName, targetDb, mechCounter] = *sharedBlock.get();
mechCounter.incEgressAuthenticateSuccessful();
if (gEnableDetailedConnectionHealthMetricLogLines.load()) {
LOGV2(10748708,
"Authentication to remote host succeeded using MONGODB-X509",
"hostname"_attr = hostname,
"params"_attr = params,
"username"_attr = cred.username.value_or(""),
"subjectName"_attr = clientName,
"targetDatabase"_attr = targetDb,
"mechanism"_attr = kMechanismMongoX509,
@ -182,12 +149,12 @@ Future<void> authX509(RunCommandHook runCommand,
})
.onError([metricsRecorder, sharedBlock](const Status& status) {
BSONObj metrics = metricsRecorder->captureEgress();
auto [hostname, params, clientName, targetDb, _] = *sharedBlock.get();
auto [hostname, cred, clientName, targetDb, _] = *sharedBlock.get();
if (gEnableDetailedConnectionHealthMetricLogLines.load()) {
LOGV2(10748707,
"Authentication to remote host failed using MONGODB-X509",
"hostname"_attr = hostname,
"params"_attr = params,
"username"_attr = cred.username.value_or(""),
"subjectName"_attr = clientName,
"targetDatabase"_attr = targetDb,
"mechanism"_attr = kMechanismMongoX509,
@ -204,7 +171,7 @@ class DefaultInternalAuthParametersProvider : public InternalAuthParametersProvi
public:
~DefaultInternalAuthParametersProvider() override = default;
BSONObj get(size_t index, StringData mechanism) final {
boost::optional<Credential> get(size_t index, StringData mechanism) final {
return getInternalAuthParams(index, mechanism);
}
};
@ -220,7 +187,7 @@ std::shared_ptr<InternalAuthParametersProvider> createDefaultInternalAuthProvide
// General Auth
//
Future<void> authenticateClient(const BSONObj& params,
Future<void> authenticateClient(const Credential& credential,
const HostAndPort& hostname,
const std::string& clientName,
RunCommandHook runCommand) {
@ -237,26 +204,17 @@ Future<void> authenticateClient(const BSONObj& params,
return status;
};
std::string mechanism;
auto response = bsonExtractStringField(params, saslCommandMechanismFieldName, &mechanism);
if (!response.isOK())
return response;
if (params.hasField(saslCommandUserDBFieldName) && params.hasField(kUserSourceFieldName)) {
return Status(ErrorCodes::AuthenticationFailed,
"You cannot specify both 'db' and 'userSource'. Please use only 'db'.");
}
#ifdef MONGO_CONFIG_SSL
else if (mechanism == kMechanismMongoX509)
return authX509(runCommand, hostname, params, clientName).onError(errorHandler);
if (credential.mechanism == AuthMechanism::kMongoX509)
return authX509(runCommand, hostname, credential, clientName).onError(errorHandler);
#endif
else if (saslClientAuthenticate != nullptr)
return saslClientAuthenticate(runCommand, hostname, params).onError(errorHandler);
if (saslClientAuthenticate != nullptr)
return saslClientAuthenticate(runCommand, hostname, credential).onError(errorHandler);
return Status(ErrorCodes::AuthenticationFailed,
mechanism + " mechanism support not compiled into client library.");
str::stream() << toString(credential.mechanism)
<< " mechanism support not compiled into client library.");
};
Future<std::string> negotiateSaslMechanism(RunCommandHook runCommand,
@ -316,21 +274,19 @@ Future<void> authenticateInternalClient(
runCommand, (*systemUser)->getName(), mechanismHint, stepDownBehavior)
.then([runCommand, clientSubjectName, remote, internalParamsProvider](
std::string mechanism) -> Future<void> {
auto params = internalParamsProvider->get(0, mechanism);
if (params.isEmpty()) {
auto cred = internalParamsProvider->get(0, mechanism);
if (!cred) {
return Status(ErrorCodes::BadValue,
"Missing authentication parameters for internal user auth");
}
return authenticateClient(params, remote, clientSubjectName, runCommand)
return authenticateClient(*cred, remote, clientSubjectName, runCommand)
.onError<ErrorCodes::AuthenticationFailed>(
[runCommand, clientSubjectName, remote, mechanism, internalParamsProvider](
Status status) -> Future<void> {
auto altCreds = internalParamsProvider->get(1, mechanism);
if (!altCreds.isEmpty()) {
return authenticateClient(
altCreds, remote, clientSubjectName, runCommand);
}
return status;
auto altCred = internalParamsProvider->get(1, mechanism);
if (!altCred)
return status;
return authenticateClient(*altCred, remote, clientSubjectName, runCommand);
});
});
}
@ -361,30 +317,22 @@ StringData getSaslCommandUserFieldName() {
namespace {
StatusWith<std::shared_ptr<SaslClientSession>> _speculateSaslStart(
BSONObjBuilder* helloRequestBuilder,
const std::string& mechanism,
const HostAndPort& host,
StringData authDB,
BSONObj params) {
if (mechanism == kMechanismSaslPlain) {
BSONObjBuilder* helloRequestBuilder, const Credential& credential, const HostAndPort& host) {
if (credential.mechanism == AuthMechanism::kSaslPlain) {
return {ErrorCodes::BadValue, "PLAIN mechanism not supported with speculativeSaslStart"};
}
std::shared_ptr<SaslClientSession> session(SaslClientSession::create(mechanism));
auto status = saslConfigureSession(session.get(), host, authDB, params);
if (!status.isOK()) {
return status;
}
std::string username;
status = bsonExtractStringFieldWithDefault(params, saslCommandUserFieldName, ""_sd, &username);
const auto mechStr = toString(credential.mechanism);
const auto authDB = credential.db.value_or(std::string{saslDefaultDBName});
std::shared_ptr<SaslClientSession> session(SaslClientSession::create(std::string{mechStr}));
auto status = saslConfigureSession(session.get(), host, credential);
if (!status.isOK()) {
return status;
}
std::string payload;
auto mechCounter = authCounter.getEgressMechanismCounter(mechanism);
auto mechCounter = authCounter.getEgressMechanismCounter(mechStr);
mechCounter.incAuthenticateSent();
mechCounter.incSpeculativeAuthenticateSent();
@ -396,10 +344,9 @@ StatusWith<std::shared_ptr<SaslClientSession>> _speculateSaslStart(
LOGV2(10748709,
"Speculative authentication to remote host failed",
"hostname"_attr = host,
"saslParameters"_attr = params,
"username"_attr = username,
"username"_attr = credential.username.value_or(""),
"targetDatabase"_attr = authDB,
"mechanism"_attr = mechanism,
"mechanism"_attr = mechStr,
"error"_attr = redact(status),
"result"_attr = status.code(),
"metrics"_attr = metrics);
@ -413,17 +360,16 @@ StatusWith<std::shared_ptr<SaslClientSession>> _speculateSaslStart(
LOGV2(10748710,
"Speculative authentication to remote host succeeded",
"hostname"_attr = host,
"saslParameters"_attr = params,
"username"_attr = username,
"username"_attr = credential.username.value_or(""),
"targetDatabase"_attr = authDB,
"mechanism"_attr = mechanism,
"mechanism"_attr = mechStr,
"result"_attr = Status::OK().code(),
"metrics"_attr = metrics);
}
BSONObjBuilder saslStart;
saslStart.append("saslStart", 1);
saslStart.append("mechanism", mechanism);
saslStart.append("mechanism", mechStr);
saslStart.appendBinData("payload", int(payload.size()), BinDataGeneral, payload.c_str());
saslStart.append("db", authDB);
helloRequestBuilder->append(kSpeculativeAuthenticate, saslStart.obj());
@ -433,25 +379,23 @@ StatusWith<std::shared_ptr<SaslClientSession>> _speculateSaslStart(
StatusWith<SpeculativeAuthType> _speculateAuth(
BSONObjBuilder* helloRequestBuilder,
const std::string& mechanism,
const Credential& credential,
const HostAndPort& host,
StringData authDB,
BSONObj params,
std::shared_ptr<SaslClientSession>* saslClientSession) {
if (mechanism == kMechanismMongoX509) {
if (credential.mechanism == AuthMechanism::kMongoX509) {
// MONGODB-X509
helloRequestBuilder->append(kSpeculativeAuthenticate,
BSON(kAuthenticateCommand
<< "1" << saslCommandMechanismFieldName << mechanism
<< saslCommandUserDBFieldName << "$external"));
helloRequestBuilder->append(
kSpeculativeAuthenticate,
BSON(kAuthenticateCommand
<< "1" << saslCommandMechanismFieldName << toString(credential.mechanism)
<< saslCommandUserDBFieldName << credential.db.value_or("$external")));
return SpeculativeAuthType::kAuthenticate;
}
// Proceed as if this is a SASL mech and we either have a password,
// or we don't need one (e.g. MONGODB-AWS).
// Failure is absolutely an option.
auto swSaslClientSession =
_speculateSaslStart(helloRequestBuilder, mechanism, host, authDB, params);
auto swSaslClientSession = _speculateSaslStart(helloRequestBuilder, credential, host);
if (!swSaslClientSession.isOK()) {
return swSaslClientSession.getStatus();
}
@ -461,34 +405,25 @@ StatusWith<SpeculativeAuthType> _speculateAuth(
return SpeculativeAuthType::kSaslStart;
}
std::string getBSONString(BSONObj container, StringData field) {
auto elem = container[field];
uassert(ErrorCodes::BadValue,
str::stream() << "Field '" << field << "' must be of type string",
elem.type() == BSONType::string);
return elem.String();
}
} // namespace
SpeculativeAuthType speculateAuth(BSONObjBuilder* helloRequestBuilder,
const MongoURI& uri,
std::shared_ptr<SaslClientSession>* saslClientSession) {
auto mechanism =
uri.getOption("authMechanism").get_value_or(std::string{kMechanismScramSha256});
auto mechStr = uri.getOption("authMechanism").get_value_or(std::string{kMechanismScramSha256});
auto optParams = uri.makeAuthObjFromOptions(LATEST_WIRE_VERSION, {mechanism});
auto optParams = uri.makeAuthObjFromOptions(LATEST_WIRE_VERSION, {mechStr});
if (!optParams) {
return SpeculativeAuthType::kNone;
}
auto params = std::move(optParams.value());
auto swCred = Credential::fromBSON(optParams.value());
if (!swCred.isOK()) {
return SpeculativeAuthType::kNone;
}
auto ret = _speculateAuth(helloRequestBuilder,
mechanism,
uri.getServers().front(),
uri.getAuthenticationDatabase(),
params,
saslClientSession);
auto ret = _speculateAuth(
helloRequestBuilder, swCred.getValue(), uri.getServers().front(), saslClientSession);
if (!ret.isOK()) {
// Ignore error, fallback on explicit auth.
return SpeculativeAuthType::kNone;
@ -501,16 +436,12 @@ SpeculativeAuthType speculateInternalAuth(
const HostAndPort& remoteHost,
BSONObjBuilder* helloRequestBuilder,
std::shared_ptr<SaslClientSession>* saslClientSession) try {
auto params = getInternalAuthParams(0, std::string{kMechanismScramSha256});
if (params.isEmpty()) {
auto cred = getInternalAuthParams(0, std::string{kMechanismScramSha256});
if (!cred) {
return SpeculativeAuthType::kNone;
}
auto mechanism = getBSONString(params, saslCommandMechanismFieldName);
auto authDB = getBSONString(params, saslCommandUserDBFieldName);
auto ret = _speculateAuth(
helloRequestBuilder, mechanism, remoteHost, authDB, params, saslClientSession);
auto ret = _speculateAuth(helloRequestBuilder, *cred, remoteHost, saslClientSession);
if (!ret.isOK()) {
return SpeculativeAuthType::kNone;
}
@ -521,5 +452,6 @@ SpeculativeAuthType speculateInternalAuth(
return SpeculativeAuthType::kNone;
}
} // namespace auth
} // namespace mongo

View File

@ -33,12 +33,11 @@
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/client/credential.h"
#include "mongo/client/internal_auth.h"
#include "mongo/client/mongo_uri.h"
#include "mongo/client/sasl_client_session.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/database_name.h"
#include "mongo/executor/remote_command_response.h"
#include "mongo/rpc/op_msg.h"
#include "mongo/util/future.h"
#include "mongo/util/modules.h"
@ -55,24 +54,12 @@
namespace mongo {
class BSONObj;
class MongoURI;
namespace MONGO_MOD_PUBLIC auth {
using RunCommandHook = std::function<Future<BSONObj>(OpMsgRequest request)>;
/**
* Names for supported authentication mechanisms.
*/
constexpr auto kMechanismMongoX509 = "MONGODB-X509"_sd;
constexpr auto kMechanismSaslPlain = "PLAIN"_sd;
constexpr auto kMechanismGSSAPI = "GSSAPI"_sd;
constexpr auto kMechanismScramSha1 = "SCRAM-SHA-1"_sd;
constexpr auto kMechanismScramSha256 = "SCRAM-SHA-256"_sd;
constexpr auto kMechanismMongoAWS = "MONGODB-AWS"_sd;
constexpr auto kMechanismMongoOIDC = "MONGODB-OIDC"_sd;
constexpr auto kInternalAuthFallbackMechanism = kMechanismScramSha1;
constexpr auto kSaslSupportedMechanisms = "saslSupportedMechs"_sd;
constexpr auto kSpeculativeAuthenticate = "speculativeAuthenticate"_sd;
constexpr auto kClusterAuthenticate = "clusterAuthenticate"_sd;
@ -91,12 +78,12 @@ public:
virtual ~InternalAuthParametersProvider() = default;
/**
* Get the information for a given SASL mechanism.
* Get the credential for a given SASL mechanism.
*
* If there are multiple entries for a mechanism, suppots retrieval by index. Used when rotating
* the security key.
* If there are multiple entries for a mechanism, supports retrieval by index. Used when
* rotating the security key. Returns boost::none if no credential is available at that index.
*/
virtual BSONObj get(size_t index, StringData mechanism) = 0;
virtual boost::optional<Credential> get(size_t index, StringData mechanism) = 0;
};
std::shared_ptr<InternalAuthParametersProvider> createDefaultInternalAuthProvider();
@ -108,27 +95,17 @@ std::shared_ptr<InternalAuthParametersProvider> createDefaultInternalAuthProvide
* there is a stored client subject name, pass that through the "clientSubjectName" parameter.
* Otherwise, "clientSubjectName" will be silently ignored, pass in any string.
*
* The "params" BSONObj should be initialized with some of the fields below. Which fields
* are required depends on the mechanism, which is mandatory.
*
* "mechanism": The std::string name of the sasl mechanism to use. Mandatory.
* "user": The std::string name of the user to authenticate. Mandatory.
* "db": The database target of the auth command, which identifies the location
* of the credential information for the user. May be "$external" if
* credential information is stored outside of the mongo cluster. Mandatory.
* "pwd": The password data.
* "digestPassword": Boolean, set to true if the "pwd" is undigested (default).
* "serviceName": The GSSAPI service name to use. Defaults to "mongodb".
* "serviceHostname": The GSSAPI hostname to use. Defaults to the name of the remote
* host.
*
* Other fields in "params" are silently ignored. A "params" object can be constructed
* using the buildAuthParams() method.
* The "credential" struct must have "mechanism" set. Other fields are mechanism-dependent:
* - "username": required for SCRAM, PLAIN, GSSAPI; omitted for X.509, AWS, OIDC.
* - "db": auth-source database; uses mechanism default ($external or admin) when absent.
* - "password": required for SCRAM and PLAIN; absent for other mechanisms.
* - "mechanismProperties": mechanism-specific options such as serviceName, serviceHostname,
* awsIamSessionToken, oidcAccessToken, digestPassword.
*
* This function will return a future that will be filled with the final result of the
* authentication command on success or a Status on error.
*/
Future<void> authenticateClient(const BSONObj& params,
Future<void> authenticateClient(const Credential& credential,
const HostAndPort& hostname,
const std::string& clientSubjectName,
RunCommandHook runCommand);
@ -212,5 +189,6 @@ SpeculativeAuthType speculateInternalAuth(const HostAndPort& remoteHost,
BSONObjBuilder* helloRequestBuilder,
std::shared_ptr<SaslClientSession>* saslClientSession);
} // namespace MONGO_MOD_PUBLIC auth
} // namespace mongo

View File

@ -150,12 +150,19 @@ public:
#ifdef MONGO_CONFIG_SSL
TEST_F(AuthClientTest, X509) {
auto params = loadX509Conversation();
auth::authenticateClient(params, HostAndPort(), _username, _runCommandCallback).get();
auth::authenticateClient(uassertStatusOK(auth::Credential::fromBSON(params)),
HostAndPort(),
_username,
_runCommandCallback)
.get();
}
TEST_F(AuthClientTest, asyncX509) {
auto params = loadX509Conversation();
ASSERT_OK(auth::authenticateClient(params, HostAndPort(), _username, _runCommandCallback)
ASSERT_OK(auth::authenticateClient(uassertStatusOK(auth::Credential::fromBSON(params)),
HostAndPort(),
_username,
_runCommandCallback)
.getNoThrow());
}
#endif

View File

@ -0,0 +1,120 @@
/**
* Copyright (C) 2026-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/client/credential.h"
#include "mongo/base/error_codes.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/util/bson_extract.h"
#include "mongo/stdx/unordered_set.h"
#include <string>
#include <boost/optional/optional.hpp>
namespace mongo {
namespace auth {
namespace {
// BSON field names used in auth parameter documents.
constexpr StringData kMechField = "mechanism"_sd;
constexpr StringData kUserDBField = "db"_sd;
constexpr StringData kUserSourceField = "userSource"_sd;
constexpr StringData kUserField = "user"_sd;
constexpr StringData kPasswordField = "pwd"_sd;
} // namespace
StatusWith<Credential> Credential::fromBSON(const BSONObj& params) {
if (params.hasField(kUserDBField) && params.hasField(kUserSourceField)) {
return Status(ErrorCodes::AuthenticationFailed,
"You cannot specify both 'db' and 'userSource'. Please use only 'db'.");
}
std::string mechStr;
if (auto s = bsonExtractStringField(params, kMechField, &mechStr); !s.isOK())
return s;
auto swMech = authMechanismFromString(mechStr);
if (!swMech.isOK())
return swMech.getStatus();
boost::optional<std::string> db;
{
std::string dbStr;
if (params.hasField(kUserSourceField)) {
if (auto s = bsonExtractStringField(params, kUserSourceField, &dbStr); !s.isOK())
return s;
if (!dbStr.empty())
db = std::move(dbStr);
} else if (params.hasField(kUserDBField)) {
if (auto s = bsonExtractStringField(params, kUserDBField, &dbStr); !s.isOK())
return s;
if (!dbStr.empty())
db = std::move(dbStr);
}
}
boost::optional<std::string> username;
{
std::string usernameStr;
if (params.hasField(kUserField)) {
if (auto s = bsonExtractStringField(params, kUserField, &usernameStr); !s.isOK())
return s;
if (!usernameStr.empty())
username = std::move(usernameStr);
}
}
boost::optional<std::string> password;
{
std::string passwordStr;
if (params.hasField(kPasswordField)) {
if (auto s = bsonExtractStringField(params, kPasswordField, &passwordStr); !s.isOK())
return s;
if (!passwordStr.empty())
password = std::move(passwordStr);
}
}
// Collect mechanism-specific properties (everything except the 5 core fields).
static const stdx::unordered_set<StringData> kCoreFields = {
kMechField, kUserDBField, kUserSourceField, kUserField, kPasswordField};
BSONObjBuilder props;
for (const auto& elem : params) {
if (!kCoreFields.count(elem.fieldNameStringData()))
props.append(elem);
}
return Credential{
swMech.getValue(), std::move(db), std::move(username), std::move(password), props.obj()};
}
} // namespace auth
} // namespace mongo

View File

@ -26,34 +26,43 @@
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include "mongo/base/status.h"
#include "mongo/client/authenticate.h"
#include "mongo/base/status_with.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/db/auth/auth_mechanism.h"
#include "mongo/util/modules.h"
#include <algorithm>
#include <string>
#include <boost/optional/optional.hpp>
namespace mongo {
namespace MONGO_MOD_PUBLIC auth {
inline Status validateAuthMechanism(const std::string& value) {
static constexpr std::array<StringData, 7> kValidMechanisms = {
"MONGODB-X509"_sd,
"PLAIN"_sd,
"GSSAPI"_sd,
"SCRAM-SHA-1"_sd,
"SCRAM-SHA-256"_sd,
"MONGODB-AWS"_sd,
"MONGODB-OIDC"_sd,
};
if (std::find(kValidMechanisms.begin(), kValidMechanisms.end(), value) ==
kValidMechanisms.end()) {
return {ErrorCodes::BadValue,
str::stream() << "Unknown authentication mechanism '" << value
<< "'. Supported mechanisms: MONGODB-X509, PLAIN, GSSAPI, "
"SCRAM-SHA-1, SCRAM-SHA-256, MONGODB-AWS, MONGODB-OIDC"};
}
return Status::OK();
}
/** Typed representation of client authentication credentials. */
struct Credential {
AuthMechanism mechanism;
/** Auth-source database. Uses mechanism default ($external or admin) when absent. */
boost::optional<std::string> db;
/** Required for SCRAM, PLAIN, GSSAPI; absent for X.509 / AWS / OIDC. */
boost::optional<std::string> username;
/** Required for SCRAM and PLAIN; absent for others. */
boost::optional<std::string> password;
/** Mechanism-specific options. Not required, but not marked as optional for ergonomics. */
BSONObj mechanismProperties;
/**
* Parse and validate BSON into a Credential. Returns an error if a required field is missing or
* if both "db" and the legacy "userSource" field are present.
*/
static StatusWith<Credential> fromBSON(const BSONObj& params);
};
} // namespace MONGO_MOD_PUBLIC auth
} // namespace mongo

View File

@ -0,0 +1,324 @@
/**
* Copyright (C) 2026-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/client/credential.h"
#include "mongo/base/error_codes.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bson_matcher.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/db/auth/auth_mechanism.h"
#include "mongo/unittest/unittest.h"
#include <vector>
#include <boost/optional/optional.hpp>
namespace mongo {
namespace auth {
namespace {
struct ParseSuccessCase {
StringData name;
BSONObj params;
Credential expected;
};
struct ParseFailureCase {
StringData name;
BSONObj params;
boost::optional<ErrorCodes::Error> expectedCode;
};
void assertCredentialEq(StringData name, const Credential& actual, const Credential& expected) {
ASSERT_EQ(actual.mechanism, expected.mechanism) << name;
ASSERT_EQ(actual.db, expected.db) << name << ": db";
ASSERT_EQ(actual.username, expected.username) << name << ": username";
ASSERT_EQ(actual.password, expected.password) << name << ": password";
ASSERT_BSONOBJ_EQ_UNORDERED(actual.mechanismProperties, expected.mechanismProperties);
}
TEST(CredentialTest, ParseSuccessCases) {
const std::vector<ParseSuccessCase> cases = {
{.name = "ParseValidScramSha256Credential",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "user"
<< "testuser"
<< "db"
<< "admin"
<< "pwd"
<< "secret"),
.expected = {.mechanism = AuthMechanism::kScramSha256,
.db = std::string{"admin"},
.username = std::string{"testuser"},
.password = std::string{"secret"}}},
{.name = "ParseX509Credential",
.params = BSON("mechanism" << "MONGODB-X509"
<< "db"
<< "$external"
<< "user"
<< "CN=test"),
.expected = {.mechanism = AuthMechanism::kMongoX509,
.db = std::string{"$external"},
.username = std::string{"CN=test"}}},
{.name = "ParseLegacyUserSourceField",
.params = BSON("mechanism" << "SCRAM-SHA-1"
<< "user"
<< "testuser"
<< "userSource"
<< "admin"
<< "pwd"
<< "secret"),
.expected = {.mechanism = AuthMechanism::kScramSha1,
.db = std::string{"admin"},
.username = std::string{"testuser"},
.password = std::string{"secret"}}},
{.name = "ParseMechanismProperties",
.params = BSON("mechanism" << "GSSAPI"
<< "user"
<< "testuser"
<< "db"
<< "$external"
<< "serviceName"
<< "mongodb"
<< "serviceHostname"
<< "localhost"),
.expected = {.mechanism = AuthMechanism::kGSSAPI,
.db = std::string{"$external"},
.username = std::string{"testuser"},
.mechanismProperties = BSON("serviceName" << "mongodb"
<< "serviceHostname"
<< "localhost")}},
{.name = "ParseMinimalCredential",
.params = BSON("mechanism" << "MONGODB-AWS"),
.expected = {.mechanism = AuthMechanism::kMongoAWS}},
{.name = "ParseEmptyStringFields",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "user"
<< ""
<< "pwd"
<< ""
<< "db"
<< ""),
.expected = {.mechanism = AuthMechanism::kScramSha256}},
{.name = "DatabaseFieldPrecedence_UseDbWhenPresent",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "user"
<< "testuser"
<< "db"
<< "mydb"
<< "pwd"
<< "secret"),
.expected = {.mechanism = AuthMechanism::kScramSha256,
.db = std::string{"mydb"},
.username = std::string{"testuser"},
.password = std::string{"secret"}}},
{.name = "DatabaseFieldPrecedence_UseUserSourceWhenDbMissing",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "user"
<< "testuser"
<< "userSource"
<< "sourcedb"
<< "pwd"
<< "secret"),
.expected = {.mechanism = AuthMechanism::kScramSha256,
.db = std::string{"sourcedb"},
.username = std::string{"testuser"},
.password = std::string{"secret"}}},
{.name = "DatabaseFieldPrecedence_NoDbOrUserSource",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "user"
<< "testuser"
<< "pwd"
<< "secret"),
.expected = {.mechanism = AuthMechanism::kScramSha256,
.username = std::string{"testuser"},
.password = std::string{"secret"}}},
{.name = "EmptyDatabaseField",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "user"
<< "testuser"
<< "db"
<< ""
<< "pwd"
<< "secret"),
.expected = {.mechanism = AuthMechanism::kScramSha256,
.username = std::string{"testuser"},
.password = std::string{"secret"}}},
{.name = "SpecialCharactersInDatabase",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "user"
<< "testuser"
<< "db"
<< "my@db"
<< "pwd"
<< "secret"),
.expected = {.mechanism = AuthMechanism::kScramSha256,
.db = std::string{"my@db"},
.username = std::string{"testuser"},
.password = std::string{"secret"}}},
{.name = "ExternalDatabaseForX509",
.params = BSON("mechanism" << "MONGODB-X509"
<< "user"
<< "CN=test"
<< "db"
<< "$external"),
.expected = {.mechanism = AuthMechanism::kMongoX509,
.db = std::string{"$external"},
.username = std::string{"CN=test"}}},
{.name = "ParsePlainCredential",
.params = BSON("mechanism" << "PLAIN"
<< "user"
<< "testuser"
<< "db"
<< "$external"
<< "pwd"
<< "secret"),
.expected = {.mechanism = AuthMechanism::kSaslPlain,
.db = std::string{"$external"},
.username = std::string{"testuser"},
.password = std::string{"secret"}}},
{.name = "ParseOidcCredential",
.params = BSON("mechanism" << "MONGODB-OIDC"
<< "db"
<< "$external"
<< "user"
<< "testuser"),
.expected = {.mechanism = AuthMechanism::kMongoOIDC,
.db = std::string{"$external"},
.username = std::string{"testuser"}}},
{.name = "ParseMongoCrCredential",
.params = BSON("mechanism" << "MONGODB-CR"
<< "user"
<< "testuser"
<< "db"
<< "admin"
<< "pwd"
<< "secret"),
.expected = {.mechanism = AuthMechanism::kMongoDbCr,
.db = std::string{"admin"},
.username = std::string{"testuser"},
.password = std::string{"secret"}}},
};
for (const auto& tc : cases) {
auto swCred = Credential::fromBSON(tc.params);
ASSERT_OK(swCred.getStatus()) << tc.name;
assertCredentialEq(tc.name, swCred.getValue(), tc.expected);
}
}
TEST(CredentialTest, ParseFailureCases) {
const std::vector<ParseFailureCase> cases = {
{.name = "RejectBothDbAndUserSource",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "user"
<< "testuser"
<< "db"
<< "test"
<< "userSource"
<< "admin"
<< "pwd"
<< "secret"),
.expectedCode = ErrorCodes::AuthenticationFailed},
{.name = "RejectMissingMechanism",
.params = BSON("user" << "testuser"
<< "pwd"
<< "secret")},
{.name = "RejectInvalidMechanism",
.params = BSON("mechanism" << "INVALID-MECHANISM"
<< "user"
<< "testuser"),
.expectedCode = ErrorCodes::InvalidOptions},
{.name = "RejectEmptyMechanismString",
.params = BSON("mechanism" << ""
<< "user"
<< "testuser"),
.expectedCode = ErrorCodes::InvalidOptions},
{.name = "RejectNonStringMechanism",
.params = BSON("mechanism" << 42 << "user"
<< "testuser"),
.expectedCode = ErrorCodes::TypeMismatch},
{.name = "RejectNonStringDbField",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "db" << 42),
.expectedCode = ErrorCodes::TypeMismatch},
{.name = "RejectNonStringUserSourceField",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "userSource" << 42),
.expectedCode = ErrorCodes::TypeMismatch},
{.name = "RejectNonStringUserField",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "user" << 42),
.expectedCode = ErrorCodes::TypeMismatch},
{.name = "RejectNonStringPasswordField",
.params = BSON("mechanism" << "SCRAM-SHA-256"
<< "pwd" << 42),
.expectedCode = ErrorCodes::TypeMismatch},
};
for (const auto& tc : cases) {
auto swCred = Credential::fromBSON(tc.params);
ASSERT_NOT_OK(swCred.getStatus()) << tc.name;
if (tc.expectedCode.has_value()) {
ASSERT_EQ(swCred.getStatus().code(), *tc.expectedCode) << tc.name;
}
}
}
TEST(CredentialTest, ParseMechanismMappingCases) {
struct TestCase {
StringData name;
StringData mechanism;
AuthMechanism expected;
};
const std::vector<TestCase> cases = {
{"ScramSha1", "SCRAM-SHA-1", AuthMechanism::kScramSha1},
{"ScramSha256", "SCRAM-SHA-256", AuthMechanism::kScramSha256},
{"MongoX509", "MONGODB-X509", AuthMechanism::kMongoX509},
{"Plain", "PLAIN", AuthMechanism::kSaslPlain},
{"Gssapi", "GSSAPI", AuthMechanism::kGSSAPI},
{"MongoAws", "MONGODB-AWS", AuthMechanism::kMongoAWS},
{"MongoOidc", "MONGODB-OIDC", AuthMechanism::kMongoOIDC},
{"MongoCr", "MONGODB-CR", AuthMechanism::kMongoDbCr},
};
for (const auto& tc : cases) {
auto swCred = Credential::fromBSON(BSON("mechanism" << tc.mechanism));
ASSERT_OK(swCred.getStatus()) << tc.name;
ASSERT_EQ(swCred.getValue().mechanism, tc.expected) << tc.name;
}
}
} // namespace
} // namespace auth
} // namespace mongo

View File

@ -408,7 +408,8 @@ void DBClientBase::_auth(const BSONObj& params) {
#endif
HostAndPort remote(getServerAddress());
auth::authenticateClient(params, remote, clientName, _makeAuthRunCommandHook()).get();
auto credential = uassertStatusOK(auth::Credential::fromBSON(params));
auth::authenticateClient(credential, remote, clientName, _makeAuthRunCommandHook()).get();
_isClientAuthenticated.store(true);
}
@ -468,7 +469,8 @@ void DBClientBase::auth(const DatabaseName& dbname, StringData username, StringD
// To prevent unexpected behavior for existing clients, default to SCRAM-SHA-1 if the SASL
// negotiation does not succeeed for some reason.
StringData mech = mechResult.isOK() ? mechResult.getValue() : "SCRAM-SHA-1"_sd;
StringData mech =
mechResult.isOK() ? mechResult.getValue() : auth::kInternalAuthFallbackMechanism;
const auto authParams = auth::buildAuthParams(dbname, username, password_text, mech);
auth(authParams);

View File

@ -188,8 +188,8 @@ executor::RemoteCommandResponse initWireVersion(
}
*speculativeAuthType = auth::speculateAuth(&bob, uri, saslClientSession);
if (!uri.getUser().empty()) {
UserName user(uri.getUser(), uri.getAuthenticationDatabase());
if (auto& cred = uri.getCredential(); cred && cred->username) {
UserName user(*cred->username, uri.getAuthenticationDatabase());
bob.append("saslSupportedMechs", user.getUnambiguousName());
}

View File

@ -29,12 +29,8 @@
#include "mongo/client/internal_auth.h"
#include "mongo/base/error_codes.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/client/authenticate.h"
#include "mongo/config.h" // IWYU pragma: keep
#include "mongo/db/auth/authorization_manager.h"
#include "mongo/db/auth/sasl_command_constants.h"
@ -43,12 +39,9 @@
#include "mongo/util/assert_util.h"
#include "mongo/util/password_digest.h"
#include "mongo/util/read_through_cache.h"
#include "mongo/util/str.h"
#include <memory>
#include <mutex>
#include <boost/move/utility_core.hpp>
#include <boost/optional/optional.hpp>
namespace mongo {
@ -57,19 +50,18 @@ namespace auth {
static std::mutex internalAuthKeysMutex;
static bool internalAuthSet = false;
static std::vector<std::string> internalAuthKeys;
static BSONObj internalAuthParams;
static boost::optional<Credential> internalAuthCredential;
void setInternalAuthKeys(const std::vector<std::string>& keys) {
std::lock_guard<std::mutex> lk(internalAuthKeysMutex);
internalAuthKeys = keys;
fassert(50996, internalAuthKeys.size() > 0);
internalAuthSet = true;
}
void setInternalUserAuthParams(BSONObj obj) {
void setInternalUserAuthParams(Credential credential) {
std::lock_guard<std::mutex> lk(internalAuthKeysMutex);
internalAuthParams = obj.getOwned();
internalAuthCredential = std::move(credential);
internalAuthKeys.clear();
internalAuthSet = true;
}
@ -84,63 +76,52 @@ bool isInternalAuthSet() {
return internalAuthSet;
}
BSONObj createInternalX509AuthDocument(boost::optional<StringData> userName) {
BSONObjBuilder builder;
builder.append(saslCommandMechanismFieldName, "MONGODB-X509");
builder.append(saslCommandUserDBFieldName, "$external");
if (userName) {
builder.append(saslCommandUserFieldName, userName.value());
}
return builder.obj();
Credential createInternalX509AuthCredential(boost::optional<StringData> userName) {
return Credential{
.mechanism = AuthMechanism::kMongoX509,
.db = std::string{"$external"},
.username = userName ? boost::make_optional(std::string{*userName}) : boost::none,
};
}
BSONObj getInternalAuthParams(size_t idx, StringData mechanism) {
boost::optional<Credential> getInternalAuthParams(size_t idx, StringData mechanism) {
std::lock_guard<std::mutex> lk(internalAuthKeysMutex);
if (!internalAuthSet) {
return BSONObj();
return boost::none;
}
// If we've set a specific BSONObj as the internal auth pararms, return it if the index
// is zero (there are no alternate credentials if we've set a BSONObj explicitly).
if (!internalAuthParams.isEmpty()) {
return idx == 0 ? internalAuthParams : BSONObj();
// Explicit credential (e.g. X.509): only one entry, no alternates.
if (internalAuthCredential) {
return idx == 0 ? internalAuthCredential : boost::none;
}
// If the index is larger than the number of keys we know about then return an empty
// BSONObj.
if (idx + 1 > internalAuthKeys.size()) {
return BSONObj();
return boost::none;
}
auto swMech = authMechanismFromString(mechanism);
if (!swMech.isOK())
return boost::none;
auto password = internalAuthKeys.at(idx);
auto systemUser = internalSecurity.getUser();
if (mechanism == kMechanismScramSha1) {
if (swMech.getValue() == AuthMechanism::kScramSha1) {
password = mongo::createPasswordDigest((*systemUser)->getName().getUser(), password);
}
return BSON(saslCommandMechanismFieldName
<< mechanism << saslCommandUserDBFieldName << (*systemUser)->getName().getDB()
<< saslCommandUserFieldName << (*systemUser)->getName().getUser()
<< saslCommandPasswordFieldName << password << saslCommandDigestPasswordFieldName
<< false);
// digestPassword: false because the password is already in its final form above.
return Credential{swMech.getValue(),
std::string{(*systemUser)->getName().getDB()},
std::string{(*systemUser)->getName().getUser()},
std::move(password),
BSON(saslCommandDigestPasswordFieldName << false)};
}
std::string getBSONString(const BSONObj& container, StringData field) {
auto elem = container[field];
uassert(ErrorCodes::BadValue,
str::stream() << "Field '" << field << "' must be of type string",
elem.type() == BSONType::string);
return elem.String();
}
std::string getInternalAuthDB() {
std::lock_guard<std::mutex> lk(internalAuthKeysMutex);
if (!internalAuthParams.isEmpty()) {
return getBSONString(internalAuthParams, saslCommandUserDBFieldName);
if (internalAuthCredential && internalAuthCredential->db) {
return *internalAuthCredential->db;
}
auto systemUser = internalSecurity.getUser();

View File

@ -30,7 +30,7 @@
#pragma once
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/client/credential.h"
#include "mongo/util/modules.h"
#include <cstddef>
@ -39,13 +39,9 @@
#include <string>
#include <vector>
#include <boost/none.hpp>
#include <boost/optional/optional.hpp>
namespace mongo {
class BSONObj;
namespace MONGO_MOD_PUBLIC auth {
/**
@ -58,7 +54,7 @@ void setInternalAuthKeys(const std::vector<std::string>& keys);
/**
* Sets the parameters for non-password based internal authentication.
*/
void setInternalUserAuthParams(BSONObj obj);
void setInternalUserAuthParams(Credential credential);
/**
* Returns whether there are multiple keys that will be tried while authenticating an internal
@ -77,14 +73,15 @@ bool isInternalAuthSet();
std::string getInternalAuthDB();
/**
* Returns the internal auth sasl parameters.
* Returns the internal auth credential for the given index and mechanism.
* Returns boost::none if no internal auth has been configured or index is out of range.
*/
BSONObj getInternalAuthParams(size_t idx, StringData mechanism);
boost::optional<Credential> getInternalAuthParams(size_t idx, StringData mechanism);
/**
* Create a BSON document for internal authentication.
* Create a Credential for internal X.509 authentication.
*/
BSONObj createInternalX509AuthDocument(boost::optional<StringData> userName = boost::none);
Credential createInternalX509AuthCredential(boost::optional<StringData> userName = boost::none);
} // namespace MONGO_MOD_PUBLIC auth
} // namespace mongo

View File

@ -316,6 +316,53 @@ URIParts::URIParts(StringData uri) {
database = databaseAndOptions.first;
options = databaseAndOptions.second;
}
// Resolves the auth mechanism with the following precedence:
// 1. Explicit authMechanism URI option.
// 2. SCRAM mechanism advertised by the server for the selected auth source (prefer SHA-256).
// 3. Default to SCRAM-SHA-256 when server mechanisms are unavailable.
StatusWith<auth::AuthMechanism> resolveMechanism(
const MongoURI::OptionsMap& options,
boost::optional<std::vector<std::string>> saslMechsForAuth = boost::none) {
if (auto it = options.find("authMechanism"); it != options.end())
return auth::authMechanismFromString(it->second);
if (saslMechsForAuth) {
return std::find(saslMechsForAuth->begin(),
saslMechsForAuth->end(),
auth::kMechanismScramSha256) != saslMechsForAuth->end()
? auth::AuthMechanism::kScramSha256
: auth::AuthMechanism::kScramSha1;
}
return auth::AuthMechanism::kScramSha256;
}
bool mechanismRequiresUsername(auth::AuthMechanism mechanism) {
return mechanism != auth::AuthMechanism::kMongoX509 &&
mechanism != auth::AuthMechanism::kMongoAWS && mechanism != auth::AuthMechanism::kMongoOIDC;
}
// Resolves the authentication database with the following precedence:
// 1. Explicit non-empty authSource URI option.
// 2. For X.509/AWS/OIDC/GSSAPI, default to $external.
// 3. For PLAIN, use the URI database when present, otherwise $external.
// 4. For all other mechanisms, use the URI database when present, otherwise admin.
std::string resolveAuthSource(auth::AuthMechanism mechanism,
const MongoURI::OptionsMap& options,
StringData database) {
const bool isExternalDefaultDbMech = mechanism == auth::AuthMechanism::kMongoX509 ||
mechanism == auth::AuthMechanism::kMongoAWS ||
mechanism == auth::AuthMechanism::kMongoOIDC || mechanism == auth::AuthMechanism::kGSSAPI;
if (auto it = options.find("authSource"); it != options.end() && !it->second.empty())
return it->second;
if (isExternalDefaultDbMech)
return std::string{DatabaseName::kExternal.db(omitTenant)};
if (mechanism == auth::AuthMechanism::kSaslPlain)
return !database.empty() ? std::string{database}
: std::string{DatabaseName::kExternal.db(omitTenant)};
return !database.empty() ? std::string{database}
: std::string{DatabaseName::kAdmin.db(omitTenant)};
}
} // namespace
MongoURI::CaseInsensitiveString::CaseInsensitiveString(std::string str)
@ -523,12 +570,32 @@ MongoURI MongoURI::parseImpl(StringData url) {
: transport::ConnectSSLMode::kDisableSSL;
}
// Build a Credential if the URI carries authentication data.
boost::optional<auth::Credential> credential;
{
auto swMech = resolveMechanism(options);
uassertStatusOK(swMech);
const auto mechanism = swMech.getValue();
// Build credentials when either:
// - a username is present, or
// - the selected mechanism allows username omission (for example X509/AWS/OIDC).
// In the latter case, a non-empty username is still valid and is forwarded when building
// the auth command.
if (!username.empty() || !mechanismRequiresUsername(mechanism)) {
credential = auth::Credential{
.mechanism = mechanism,
.db = resolveAuthSource(mechanism, options, database),
.username = username.empty() ? boost::none : boost::optional<std::string>{username},
.password =
password.empty() ? boost::none : boost::optional<std::string>{password}};
}
}
auto cs = replicaSetName.empty()
? ConnectionString::forStandalones(std::move(servers))
: ConnectionString::forReplicaSet(replicaSetName, std::move(servers));
return MongoURI(std::move(cs),
username,
password,
std::move(credential),
database,
retryWrites,
tlsMode,
@ -556,10 +623,10 @@ boost::optional<std::string> MongoURI::getAppName() const {
std::string MongoURI::canonicalizeURIAsString() const {
StringBuilder uri;
uri << kURIPrefix;
if (!_user.empty()) {
uri << uriEncode(_user);
if (!_password.empty()) {
uri << ":" << uriEncode(_password);
if (_credential && _credential->username && !_credential->username->empty()) {
uri << uriEncode(*_credential->username);
if (_credential->password && !_credential->password->empty()) {
uri << ":" << uriEncode(*_credential->password);
}
uri << "@";
}
@ -640,42 +707,28 @@ boost::optional<BSONObj> MongoURI::makeAuthObjFromOptions(
// and OIDC, which infers it from the access token.
bool usernameRequired = true;
const auto credUser =
(_credential && _credential->username) ? *_credential->username : std::string{};
const auto credPassword =
(_credential && _credential->password) ? *_credential->password : std::string{};
BSONObjBuilder bob;
if (!_password.empty()) {
bob.append(saslCommandPasswordFieldName, _password);
if (!credPassword.empty()) {
bob.append(saslCommandPasswordFieldName, credPassword);
}
auto it = _options.find("authSource");
if (it != _options.end()) {
bob.append(saslCommandUserDBFieldName, it->second);
} else if (!_database.empty()) {
bob.append(saslCommandUserDBFieldName, _database);
} else {
bob.append(saslCommandUserDBFieldName, "admin");
}
const auto mechanism = resolveMechanism(_options, saslMechsForAuth).getValue();
usernameRequired = mechanismRequiresUsername(mechanism);
bob.append(saslCommandUserDBFieldName, resolveAuthSource(mechanism, _options, _database));
bob.append(saslCommandMechanismFieldName, auth::toString(mechanism));
it = _options.find("authMechanism");
if (it != _options.end()) {
bob.append(saslCommandMechanismFieldName, it->second);
if (it->second == auth::kMechanismMongoX509 || it->second == auth::kMechanismMongoAWS ||
it->second == auth::kMechanismMongoOIDC) {
usernameRequired = false;
}
} else if (std::find(saslMechsForAuth.begin(),
saslMechsForAuth.end(),
auth::kMechanismScramSha256) != saslMechsForAuth.end()) {
bob.append(saslCommandMechanismFieldName, auth::kMechanismScramSha256);
} else {
bob.append(saslCommandMechanismFieldName, auth::kMechanismScramSha1);
}
if (usernameRequired && _user.empty()) {
if (usernameRequired && credUser.empty()) {
return boost::none;
}
std::string username(_user); // may have to tack on service realm before we append
std::string username = credUser; // may have to tack on service realm before we append
it = _options.find("authMechanismProperties");
auto it = _options.find("authMechanismProperties");
if (it != _options.end()) {
BSONObj parsed(parseAuthMechanismProperties(it->second));

View File

@ -35,6 +35,7 @@
#include "mongo/bson/util/builder.h"
#include "mongo/bson/util/builder_fwd.h"
#include "mongo/client/connection_string.h"
#include "mongo/client/credential.h"
#include "mongo/transport/transport_layer.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/modules.h"
@ -167,20 +168,32 @@ public:
const boost::optional<TransientSSLParams>& transientSSLParams = boost::none,
ErrorCodes::Error* errcode = nullptr) const;
const std::string& getUser() const {
return _user;
}
void setUser(std::string newUsername) {
_user = std::move(newUsername);
}
const std::string& getPassword() const {
return _password;
if (_credential) {
_credential->username = std::move(newUsername);
} else {
_credential = auth::Credential{auth::AuthMechanism::kScramSha256,
/* db= */ boost::none,
std::move(newUsername),
/* password= */ boost::none,
BSONObj{}};
}
}
void setPassword(std::string newPassword) {
_password = std::move(newPassword);
if (_credential) {
_credential->password = std::move(newPassword);
} else {
_credential = auth::Credential{auth::AuthMechanism::kScramSha256,
/* db= */ boost::none,
/* username= */ boost::none,
std::move(newPassword),
BSONObj{}};
}
}
const boost::optional<auth::Credential>& getCredential() const {
return _credential;
}
const OptionsMap& getOptions() const {
@ -310,16 +323,14 @@ public:
private:
MongoURI(ConnectionString connectString,
const std::string& user,
const std::string& password,
boost::optional<auth::Credential> credential,
const std::string& database,
boost::optional<bool> retryWrites,
transport::ConnectSSLMode sslMode,
boost::optional<bool> helloOk,
OptionsMap options)
: _connectString(std::move(connectString)),
_user(user),
_password(password),
_credential(std::move(credential)),
_database(database),
_retryWrites(std::move(retryWrites)),
_sslMode(sslMode),
@ -328,8 +339,7 @@ private:
#ifdef MONGO_CONFIG_GRPC
MongoURI(ConnectionString connectString,
const std::string& user,
const std::string& password,
boost::optional<auth::Credential> credential,
const std::string& database,
boost::optional<bool> retryWrites,
transport::ConnectSSLMode sslMode,
@ -337,8 +347,7 @@ private:
boost::optional<bool> grpc,
OptionsMap options)
: _connectString(std::move(connectString)),
_user(user),
_password(password),
_credential(std::move(credential)),
_database(database),
_retryWrites(std::move(retryWrites)),
_sslMode(sslMode),
@ -350,8 +359,7 @@ private:
static MongoURI parseImpl(StringData url);
ConnectionString _connectString;
std::string _user;
std::string _password;
boost::optional<auth::Credential> _credential;
std::string _database;
boost::optional<bool> _retryWrites;
transport::ConnectSSLMode _sslMode = transport::kGlobalSSLMode;

View File

@ -38,6 +38,8 @@
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/bson/json.h"
#include "mongo/db/auth/sasl_command_constants.h"
#include "mongo/db/database_name.h"
#include "mongo/db/service_context_test_fixture.h"
#include "mongo/logv2/log.h"
#include "mongo/unittest/unittest.h"
@ -645,8 +647,9 @@ void testValidURIFormat(URITestCase testCase) {
const auto cs_status = MongoURI::parse(testCase.URI);
ASSERT_OK(cs_status);
auto result = cs_status.getValue();
ASSERT_EQ(testCase.uname, result.getUser());
ASSERT_EQ(testCase.password, result.getPassword());
auto& cred = result.getCredential();
ASSERT_EQ(testCase.uname, cred ? cred->username.value_or("") : "");
ASSERT_EQ(testCase.password, cred ? cred->password.value_or("") : "");
ASSERT_EQ(testCase.type, result.type());
ASSERT_EQ(testCase.setname, result.getReplicaSetName());
ASSERT_EQ(testCase.numservers, result.getServers().size());
@ -1044,9 +1047,10 @@ TEST(MongoURI, srvRecordTest) {
ASSERT_OK(rs.getStatus()) << "Failed on URI: " << test.uri
<< " data on line: " << test.lineNumber;
auto rv = rs.getValue();
ASSERT_EQ(rv.getUser(), test.user)
auto& cred = rv.getCredential();
ASSERT_EQ(cred ? cred->username.value_or("") : "", test.user)
<< "Failed on URI: " << test.uri << " data on line: " << test.lineNumber;
ASSERT_EQ(rv.getPassword(), test.password)
ASSERT_EQ(cred ? cred->password.value_or("") : "", test.password)
<< "Failed on URI: " << test.uri << " data on line : " << test.lineNumber;
ASSERT_EQ(rv.getDatabase(), test.database)
<< "Failed on URI: " << test.uri << " data on line : " << test.lineNumber;
@ -1098,5 +1102,402 @@ TEST(MongoURI, Redact) {
ASSERT_EQ(MongoURI::redact(toRedactSRV), redactedSRV);
}
// MONGODB-CR is a deprecated mechanism. It must be recognized at URI parse time so that
// username and password are preserved in the credential (they are needed to produce a useful
// error at authentication time). Completely unknown mechanisms must fail at parse time.
TEST(MongoURI, DeprecatedAndUnknownMechanisms) {
// MONGODB-CR: parse succeeds; username/password are preserved.
{
auto rs = MongoURI::parse("mongodb://user:pwd@localhost/db?authMechanism=MONGODB-CR");
ASSERT_OK(rs.getStatus());
const auto& cred = rs.getValue().getCredential();
ASSERT_TRUE(cred.has_value());
ASSERT_EQ(cred->mechanism, auth::AuthMechanism::kMongoDbCr);
ASSERT_EQ(cred->username.value_or(""), "user");
ASSERT_EQ(cred->password.value_or(""), "pwd");
ASSERT_EQ(cred->db.value_or(""), "db");
}
// Truly unknown mechanism: parse must fail.
{
auto rs =
MongoURI::parse("mongodb://user:pwd@localhost/db?authMechanism=COMPLETELY-UNKNOWN");
ASSERT_NOT_OK(rs.getStatus());
}
}
// External auth mechanisms (X.509, AWS, OIDC) must use "$external" as the auth db even when
// the URI carries a database path (e.g. /admin). Without an explicit authSource, picking up the
// URI path database breaks speculative authentication because the saslStart lands on "admin"
// instead of "$external".
TEST(MongoURI, ExternalMechanismDefaultsToExternalDb) {
struct TestCase {
std::string uri;
auth::AuthMechanism expectedMech;
std::string expectedDb;
};
const TestCase cases[] = {
{.uri = "mongodb://localhost/admin?authMechanism=MONGODB-X509",
.expectedMech = auth::AuthMechanism::kMongoX509,
.expectedDb = "$external"},
{.uri = "mongodb://localhost/?authMechanism=MONGODB-X509",
.expectedMech = auth::AuthMechanism::kMongoX509,
.expectedDb = "$external"},
{.uri = "mongodb://localhost/admin?authMechanism=MONGODB-AWS",
.expectedMech = auth::AuthMechanism::kMongoAWS,
.expectedDb = "$external"},
{.uri = "mongodb://localhost/admin?authMechanism=MONGODB-OIDC",
.expectedMech = auth::AuthMechanism::kMongoOIDC,
.expectedDb = "$external"},
{.uri = "mongodb://user@localhost/admin?authMechanism=GSSAPI",
.expectedMech = auth::AuthMechanism::kGSSAPI,
.expectedDb = "$external"},
// Explicit authSource overrides the external default.
{.uri = "mongodb://localhost/admin?authMechanism=MONGODB-X509&authSource=$external",
.expectedMech = auth::AuthMechanism::kMongoX509,
.expectedDb = "$external"},
{.uri = "mongodb://user:pwd@localhost/admin?authMechanism=SCRAM-SHA-256",
.expectedMech = auth::AuthMechanism::kScramSha256,
.expectedDb = "admin"},
{.uri = "mongodb://user:pwd@localhost/admin?authMechanism=SCRAM-SHA-1",
.expectedMech = auth::AuthMechanism::kScramSha1,
.expectedDb = "admin"},
{.uri = "mongodb://user:pwd@localhost/?authMechanism=PLAIN",
.expectedMech = auth::AuthMechanism::kSaslPlain,
.expectedDb = "$external"},
{.uri = "mongodb://user:pwd@localhost/admin?authMechanism=MONGODB-CR",
.expectedMech = auth::AuthMechanism::kMongoDbCr,
.expectedDb = "admin"},
// No authMechanism specified: URI parsing defaults to SCRAM-SHA-256.
{.uri = "mongodb://user:pwd@localhost/admin",
.expectedMech = auth::AuthMechanism::kScramSha256,
.expectedDb = "admin"},
};
for (const auto& tc : cases) {
auto rs = MongoURI::parse(tc.uri);
ASSERT_OK(rs.getStatus()) << "URI: " << tc.uri;
const auto& cred = rs.getValue().getCredential();
ASSERT_TRUE(cred.has_value()) << "URI: " << tc.uri;
ASSERT_EQ(cred->mechanism, tc.expectedMech) << "URI: " << tc.uri;
ASSERT_EQ(cred->db.value_or(""), tc.expectedDb) << "URI: " << tc.uri;
}
}
// Test authSource precedence according to the MongoDB driver specification:
// 1. Explicit authSource option (highest priority)
// 2. Database path in URI
// 3. Mechanism-specific default is used (e.g. "admin" for SCRAM).
TEST(MongoURI, AuthSourcePrecedence) {
struct TestCase {
std::string uri;
std::string expectedDb;
std::string description;
};
const TestCase cases[] = {
// Explicit authSource takes precedence over database path
{"mongodb://user:pwd@localhost/mydb?authSource=authdb",
"authdb",
"Explicit authSource overrides database path"},
// Database path is used when no authSource specified
{"mongodb://user:pwd@localhost/mydb", "mydb", "Database path used as authSource"},
// Empty database path with explicit authSource
{"mongodb://user:pwd@localhost/?authSource=customdb",
"customdb",
"Explicit authSource with no database path"},
// No database path and no authSource uses mechanism-specific default.
{"mongodb://user:pwd@localhost/?authMechanism=SCRAM-SHA-256",
"admin",
"No authSource and no database path default to admin for SCRAM"},
// Explicit authSource can override to non-standard database
{"mongodb://user:pwd@localhost/prod?authSource=test",
"test",
"authSource overrides to different database"},
// Database path with special characters (URL encoded)
{"mongodb://user:pwd@localhost/my%40db?authMechanism=SCRAM-SHA-256",
"my@db",
"Database path with special characters"},
// Explicit authSource with special characters
{"mongodb://user:pwd@localhost/?authSource=my%40db",
"my@db",
"authSource with special characters"},
};
for (const auto& tc : cases) {
auto rs = MongoURI::parse(tc.uri);
ASSERT_OK(rs.getStatus()) << "Failed to parse URI: " << tc.uri
<< " (test: " << tc.description << ")";
const auto& cred = rs.getValue().getCredential();
ASSERT_TRUE(cred.has_value())
<< "No credential created for URI: " << tc.uri << " (test: " << tc.description << ")";
ASSERT_TRUE(cred->db.has_value()) << "No authSource in credential for URI: " << tc.uri
<< " (test: " << tc.description << ")";
ASSERT_EQ(*cred->db, tc.expectedDb)
<< "Wrong authSource for URI: " << tc.uri << " (test: " << tc.description << ")";
}
}
// For non-external mechanisms at URI parse time:
// 1. Database path is used when present.
// 2. Explicit authSource overrides the database path.
// 3. If both are absent, mechanism-specific default is used.
TEST(MongoURI, NonExternalMechanismAuthSourceDefaults) {
struct TestCase {
std::string uri;
std::string expectedDb;
std::string description;
};
const TestCase cases[] = {
// SCRAM-SHA-256 with database path
{"mongodb://user:pwd@localhost/mydb?authMechanism=SCRAM-SHA-256",
"mydb",
"SCRAM-SHA-256 uses database path"},
// SCRAM-SHA-1 with database path
{"mongodb://user:pwd@localhost/testdb?authMechanism=SCRAM-SHA-1",
"testdb",
"SCRAM-SHA-1 uses database path"},
// PLAIN with explicit authSource (PLAIN typically requires $external)
{"mongodb://user:pwd@localhost/?authMechanism=PLAIN&authSource=$external",
"$external",
"PLAIN with explicit $external authSource"},
// PLAIN with database path defaults to that database.
{"mongodb://user:pwd@localhost/mydb?authMechanism=PLAIN",
"mydb",
"PLAIN with database path defaults to that database"},
// SCRAM with no database path and no authSource defaults to admin.
{"mongodb://user:pwd@localhost/?authMechanism=SCRAM-SHA-256",
"admin",
"SCRAM-SHA-256 without authSource or database path defaults to admin"},
// PLAIN with no database path and no authSource defaults to $external.
{"mongodb://user:pwd@localhost/?authMechanism=PLAIN",
"$external",
"PLAIN without authSource or database path defaults to $external"},
};
for (const auto& tc : cases) {
auto rs = MongoURI::parse(tc.uri);
ASSERT_OK(rs.getStatus()) << "Failed to parse URI: " << tc.uri
<< " (test: " << tc.description << ")";
const auto& cred = rs.getValue().getCredential();
ASSERT_TRUE(cred.has_value())
<< "No credential created for URI: " << tc.uri << " (test: " << tc.description << ")";
ASSERT_TRUE(cred->db.has_value()) << "No authSource in credential for URI: " << tc.uri
<< " (test: " << tc.description << ")";
ASSERT_EQ(*cred->db, tc.expectedDb)
<< "Wrong authSource for URI: " << tc.uri << " (test: " << tc.description << ")";
}
}
// Validate authSource defaulting in MongoURI::makeAuthObjFromOptions so auth command generation
// matches parse-time defaults.
TEST(MongoURI, MakeAuthObjAuthSourceDefaults) {
struct TestCase {
std::string uri;
std::vector<std::string> saslMechsForAuth;
std::string expectedDb;
std::string description;
};
const TestCase cases[] = {
{"mongodb://user:pwd@localhost/mydb?authSource=authdb&authMechanism=SCRAM-SHA-256",
{},
"authdb",
"Explicit authSource overrides database path"},
{"mongodb://localhost/admin?authMechanism=MONGODB-AWS",
{},
"$external",
"External mechanism defaults to $external"},
{"mongodb://user:pwd@localhost/mydb?authMechanism=PLAIN",
{},
"mydb",
"PLAIN defaults to database path when present"},
{"mongodb://user:pwd@localhost/?authMechanism=PLAIN",
{},
"$external",
"PLAIN defaults to $external when database path is absent"},
{"mongodb://user:pwd@localhost/mydb?authMechanism=SCRAM-SHA-256",
{},
"mydb",
"SCRAM defaults to database path when present"},
{"mongodb://user:pwd@localhost/?authMechanism=SCRAM-SHA-256",
{},
"admin",
"SCRAM defaults to admin when database path is absent"},
{"mongodb://user:pwd@localhost/",
{std::string{auth::kMechanismScramSha1}},
"admin",
"Negotiated SCRAM-SHA-1 defaults to admin when database path is absent"},
};
for (const auto& tc : cases) {
auto rs = MongoURI::parse(tc.uri);
ASSERT_OK(rs.getStatus()) << "Failed to parse URI: " << tc.uri
<< " (test: " << tc.description << ")";
auto authObj =
rs.getValue().makeAuthObjFromOptions(/*maxWireVersion*/ 0, tc.saslMechsForAuth);
ASSERT_TRUE(authObj.has_value())
<< "No auth object created for URI: " << tc.uri << " (test: " << tc.description << ")";
const auto sourceField = authObj->getField(saslCommandUserDBFieldName);
ASSERT_FALSE(sourceField.eoo()) << "No auth db in auth object for URI: " << tc.uri
<< " (test: " << tc.description << ")";
ASSERT_EQ(sourceField.type(), BSONType::string)
<< "Auth db has wrong type for URI: " << tc.uri << " (test: " << tc.description << ")";
ASSERT_EQ(sourceField.String(), tc.expectedDb)
<< "Wrong auth db in auth object for URI: " << tc.uri << " (test: " << tc.description
<< ")";
}
}
// Test that explicit authSource can override the external mechanism default
TEST(MongoURI, ExplicitAuthSourceOverridesExternalDefault) {
struct TestCase {
std::string uri;
std::string expectedDb;
std::string description;
};
const TestCase cases[] = {
// X.509 with explicit non-$external authSource (unusual but should be allowed)
{"mongodb://CN=test@localhost/?authMechanism=MONGODB-X509&authSource=admin",
"admin",
"X.509 with explicit admin authSource"},
// AWS with explicit authSource
{"mongodb://localhost/?authMechanism=MONGODB-AWS&authSource=custom",
"custom",
"MONGODB-AWS with explicit custom authSource"},
// OIDC with explicit authSource
{"mongodb://localhost/?authMechanism=MONGODB-OIDC&authSource=mydb",
"mydb",
"MONGODB-OIDC with explicit custom authSource"},
// GSSAPI with explicit authSource
{"mongodb://user@localhost/?authMechanism=GSSAPI&authSource=test",
"test",
"GSSAPI with explicit test authSource"},
};
for (const auto& tc : cases) {
auto rs = MongoURI::parse(tc.uri);
ASSERT_OK(rs.getStatus()) << "Failed to parse URI: " << tc.uri
<< " (test: " << tc.description << ")";
const auto& cred = rs.getValue().getCredential();
ASSERT_TRUE(cred.has_value())
<< "No credential created for URI: " << tc.uri << " (test: " << tc.description << ")";
ASSERT_TRUE(cred->db.has_value()) << "No authSource in credential for URI: " << tc.uri
<< " (test: " << tc.description << ")";
ASSERT_EQ(*cred->db, tc.expectedDb)
<< "Wrong authSource for URI: " << tc.uri << " (test: " << tc.description << ")";
}
}
// Test URIs without credentials don't create credentials
TEST(MongoURI, NoCredentialsWithoutAuthInfo) {
struct TestCase {
std::string uri;
std::string description;
};
const TestCase cases[] = {
{"mongodb://localhost/", "No username, no database"},
{"mongodb://localhost/mydb", "Database path but no username"},
{"mongodb://localhost/?authSource=test", "authSource but no username"},
};
for (const auto& tc : cases) {
auto rs = MongoURI::parse(tc.uri);
ASSERT_OK(rs.getStatus()) << "Failed to parse URI: " << tc.uri
<< " (test: " << tc.description << ")";
const auto& cred = rs.getValue().getCredential();
ASSERT_FALSE(cred.has_value()) << "Unexpected credential created for URI: " << tc.uri
<< " (test: " << tc.description << ")";
}
}
TEST(MongoURI, MakeAuthObjMechanismNegotiation) {
struct TestCase {
std::string uri;
std::vector<std::string> saslMechsForAuth;
std::string expectedMechanism;
std::string description;
};
const std::string base = "mongodb://user:pwd@localhost/";
const TestCase cases[] = {
{.uri = base,
.saslMechsForAuth = {std::string{auth::kMechanismScramSha256},
std::string{auth::kMechanismScramSha1}},
.expectedMechanism = std::string{auth::kMechanismScramSha256},
.description = "SHA-256 preferred when both advertised"},
{.uri = base,
.saslMechsForAuth = {std::string{auth::kMechanismScramSha1}},
.expectedMechanism = std::string{auth::kMechanismScramSha1},
.description = "SHA-1 chosen when only SHA-1 advertised"},
{.uri = base,
.saslMechsForAuth = {std::string{auth::kMechanismScramSha256}},
.expectedMechanism = std::string{auth::kMechanismScramSha256},
.description = "SHA-256 chosen when only SHA-256 advertised"},
{.uri = base,
.saslMechsForAuth = {},
.expectedMechanism = std::string{auth::kMechanismScramSha1},
.description = "SHA-1 chosen when server advertises no mechanisms"},
{.uri = base + "?authMechanism=SCRAM-SHA-256",
.saslMechsForAuth = {std::string{auth::kMechanismScramSha1}},
.expectedMechanism = std::string{auth::kMechanismScramSha256},
.description = "Explicit URI mechanism overrides saslMechsForAuth"},
};
for (const auto& tc : cases) {
auto rs = MongoURI::parse(tc.uri);
ASSERT_OK(rs.getStatus()) << tc.description;
auto authObj = rs.getValue().makeAuthObjFromOptions(0, tc.saslMechsForAuth);
ASSERT_TRUE(authObj.has_value()) << tc.description;
const auto mechField = authObj->getField(saslCommandMechanismFieldName);
ASSERT_FALSE(mechField.eoo()) << tc.description;
ASSERT_EQ(mechField.String(), tc.expectedMechanism) << tc.description;
}
}
TEST(MongoURI, EmptyAuthSourceRejectedByParser) {
// The URI parser rejects authSource= (empty value); it does not silently ignore it.
const std::string uri = "mongodb://user:pwd@localhost/?authSource=&authMechanism=SCRAM-SHA-256";
auto rs = MongoURI::parse(uri);
ASSERT_NOT_OK(rs.getStatus());
ASSERT_EQ(rs.getStatus().code(), ErrorCodes::FailedToParse);
}
TEST(MongoURI, MissingAuthSourceDefaultsToAdmin) {
// SCRAM-SHA-256 with no authSource specified should default the auth database to "admin".
const std::string uri = "mongodb://user:pwd@localhost/?authMechanism=SCRAM-SHA-256";
auto rs = MongoURI::parse(uri);
ASSERT_OK(rs.getStatus());
auto authObj = rs.getValue().makeAuthObjFromOptions(0, {});
ASSERT_TRUE(authObj.has_value());
const auto sourceField = authObj->getField(saslCommandUserDBFieldName);
ASSERT_FALSE(sourceField.eoo());
ASSERT_EQ(sourceField.String(), DatabaseName::kAdmin.db(omitTenant));
}
} // namespace
} // namespace mongo

View File

@ -97,8 +97,8 @@ public:
BSONObj generateSection(OperationContext* opCtx,
const BSONElement& configElement) const override {
BSONObjBuilder builder;
cacheToBSON(scramsha1ClientCache, "SCRAM-SHA-1", &builder);
cacheToBSON(scramsha256ClientCache, "SCRAM-SHA-256", &builder);
cacheToBSON(scramsha1ClientCache, auth::kMechanismScramSha1, &builder);
cacheToBSON(scramsha256ClientCache, auth::kMechanismScramSha256, &builder);
return builder.obj();
}
};

View File

@ -45,7 +45,7 @@ namespace mongo {
Future<void> (*saslClientAuthenticate)(auth::RunCommandHook runCommand,
const HostAndPort& hostname,
const BSONObj& saslParameters) = nullptr;
const auth::Credential& credential) = nullptr;
Status saslExtractPayload(const BSONObj& cmdObj, std::string* payload, BSONType* type) {
BSONElement payloadElement;

View File

@ -60,23 +60,12 @@ class SaslClientSession;
* client application must have successfully executed mongo::runGlobalInitializersOrDie() or its
* ilk to make this functionality available.
*
* The "saslParameters" BSONObj should be initialized with zero or more of the
* fields below. Which fields are required depends on the mechanism. Consult the
* relevant IETF standards.
*
* "mechanism": The std::string name of the sasl mechanism to use. Mandatory.
* "autoAuthorize": Truthy values tell the server to automatically acquire privileges on
* all resources after successful authentication, which is the default. Falsey values
* instruct the server to await separate privilege-acquisition commands.
* "user": The std::string name of the user to authenticate.
* "db": The database target of the auth command, which identifies the location
* of the credential information for the user. May be "$external" if credential
* information is stored outside of the mongo cluster.
* "pwd": The password.
* "serviceName": The GSSAPI service name to use. Defaults to "mongodb".
* "serviceHostname": The GSSAPI hostname to use. Defaults to the name of the remote host.
*
* Other fields in saslParameters are silently ignored.
* The "credential" struct must have "mechanism" set. Other fields are mechanism-dependent:
* - "username": required for SCRAM, PLAIN, GSSAPI; omitted for X.509, AWS, OIDC.
* - "db": auth-source database; uses mechanism default ($external or admin) when absent.
* - "password": required for SCRAM and PLAIN; absent for other mechanisms.
* - "mechanismProperties": mechanism-specific options such as serviceName, serviceHostname,
* awsIamSessionToken, oidcAccessToken, digestPassword.
*
* Returns an OK status on success, and ErrorCodes::AuthenticationFailed if authentication is
* rejected. Other failures, all of which are tantamount to authentication failure, may also be
@ -84,7 +73,7 @@ class SaslClientSession;
*/
extern Future<void> (*saslClientAuthenticate)(auth::RunCommandHook runCommand,
const HostAndPort& hostname,
const BSONObj& saslParameters);
const auth::Credential& credential);
/**
* Extracts the payload field from "cmdObj", and store it into "*payload".
@ -101,17 +90,15 @@ Status saslExtractPayload(const BSONObj& cmdObj, std::string* payload, BSONType*
constexpr int kSaslClientLogLevelDefault = 4;
/**
* Configures and initializes "session" to perform the client side of a
* SASL conversation over connection "client".
* Configures and initializes "session" to perform the client side of a SASL conversation.
*
* "saslParameters" is a BSON document providing the necessary configuration information.
* Reads mechanism, username, password, and mechanism-specific properties from "credential".
*
* Returns Status::OK() on success.
*/
Status saslConfigureSession(SaslClientSession* session,
const HostAndPort& hostname,
StringData targetDatabase,
const BSONObj& saslParameters);
const auth::Credential& credential);
/**
* Continue a previously started sasl session and proceed until completion.

View File

@ -41,7 +41,6 @@
#include "mongo/base/status.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/bsontypes.h"
@ -65,6 +64,7 @@
#include "mongo/util/future_impl.h"
#include "mongo/util/net/hostandport.h"
#include "mongo/util/password_digest.h"
#include "mongo/util/str.h"
#include <functional>
#include <memory>
@ -83,9 +83,9 @@ namespace {
constexpr auto saslClientLogFieldName = "clientLogLevel"_sd;
int getSaslClientLogLevel(const BSONObj& saslParameters) {
int getSaslClientLogLevel(const auth::Credential& credential) {
int saslLogLevel = kSaslClientLogLevelDefault;
BSONElement saslLogElement = saslParameters[saslClientLogFieldName];
BSONElement saslLogElement = credential.mechanismProperties[saslClientLogFieldName];
if (saslLogElement.trueValue()) {
saslLogLevel = 1;
@ -98,100 +98,68 @@ int getSaslClientLogLevel(const BSONObj& saslParameters) {
return saslLogLevel;
}
/**
* Gets the password data from "saslParameters" and stores it to "outPassword".
*
* If "digestPassword" indicates that the password needs to be "digested" via
* mongo::createPasswordDigest(), this method takes care of that.
* On success, the value of "*outPassword" is always the correct value to set
* as the password on the SaslClientSession.
*
* Returns Status::OK() on success, and ErrorCodes::NoSuchKey if the password data is not
* present in "saslParameters". Other ErrorCodes returned indicate other errors.
*/
Status extractPassword(const BSONObj& saslParameters,
bool digestPassword,
std::string* outPassword) {
std::string rawPassword;
Status status =
bsonExtractStringField(saslParameters, saslCommandPasswordFieldName, &rawPassword);
if (!status.isOK())
return status;
if (digestPassword) {
std::string user;
status = bsonExtractStringField(saslParameters, saslCommandUserFieldName, &user);
if (!status.isOK())
return status;
*outPassword = mongo::createPasswordDigest(user, rawPassword);
} else {
*outPassword = rawPassword;
}
return Status::OK();
}
} // namespace
Status saslConfigureSession(SaslClientSession* session,
const HostAndPort& hostname,
StringData targetDatabase,
const BSONObj& saslParameters) {
const auth::Credential& credential) {
// SERVER-59876 Ensure hostname is never empty. If it is empty, the client-side SCRAM cache will
// not be used which creates performance problems.
dassert(!hostname.empty());
std::string mechanism;
Status status =
bsonExtractStringField(saslParameters, saslCommandMechanismFieldName, &mechanism);
if (!status.isOK())
return status;
session->setParameter(SaslClientSession::parameterMechanism, mechanism);
const auto mechStr = toString(credential.mechanism);
session->setParameter(SaslClientSession::parameterMechanism, mechStr);
const auto& props = credential.mechanismProperties;
std::string value;
status = bsonExtractStringFieldWithDefault(
saslParameters, saslCommandServiceNameFieldName, saslDefaultServiceName, &value);
Status status = bsonExtractStringFieldWithDefault(
props, saslCommandServiceNameFieldName, saslDefaultServiceName, &value);
if (!status.isOK())
return status;
session->setParameter(SaslClientSession::parameterServiceName, value);
status = bsonExtractStringFieldWithDefault(
saslParameters, saslCommandServiceHostnameFieldName, hostname.host(), &value);
props, saslCommandServiceHostnameFieldName, hostname.host(), &value);
if (!status.isOK())
return status;
session->setParameter(SaslClientSession::parameterServiceHostname, value);
session->setParameter(SaslClientSession::parameterServiceHostAndPort, hostname.toString());
status = bsonExtractStringField(saslParameters, saslCommandUserFieldName, &value);
if (status.isOK()) {
session->setParameter(SaslClientSession::parameterUser, value);
const auto targetDatabase = credential.db.value_or(std::string{saslDefaultDBName});
if (credential.username) {
session->setParameter(SaslClientSession::parameterUser, *credential.username);
} else if ((targetDatabase != DatabaseName::kExternal.db(omitTenant)) ||
((mechanism != auth::kMechanismMongoAWS) &&
(mechanism != auth::kMechanismMongoOIDC))) {
return status;
(credential.mechanism != auth::AuthMechanism::kMongoAWS &&
credential.mechanism != auth::AuthMechanism::kMongoOIDC)) {
return Status(ErrorCodes::AuthenticationFailed,
str::stream() << "Username required for mechanism " << mechStr);
}
const bool digestPasswordDefault = (mechanism == auth::kMechanismScramSha1);
bool digestPassword;
status = bsonExtractBooleanFieldWithDefault(
saslParameters, saslCommandDigestPasswordFieldName, digestPasswordDefault, &digestPassword);
if (!status.isOK())
return status;
if (credential.password) {
const bool digestPasswordDefault =
(credential.mechanism == auth::AuthMechanism::kScramSha1);
bool digestPassword = digestPasswordDefault;
status = bsonExtractBooleanFieldWithDefault(
props, saslCommandDigestPasswordFieldName, digestPasswordDefault, &digestPassword);
if (!status.isOK())
return status;
status = extractPassword(saslParameters, digestPassword, &value);
if (status.isOK()) {
session->setParameter(SaslClientSession::parameterPassword, value);
} else if (!(status == ErrorCodes::NoSuchKey &&
targetDatabase == DatabaseName::kExternal.db(omitTenant))) {
// $external users do not have passwords, hence NoSuchKey is expected
return status;
std::string processedPassword = *credential.password;
if (digestPassword && credential.username) {
processedPassword =
mongo::createPasswordDigest(*credential.username, *credential.password);
}
session->setParameter(SaslClientSession::parameterPassword, processedPassword);
} else if (targetDatabase != DatabaseName::kExternal.db(omitTenant)) {
return Status(ErrorCodes::AuthenticationFailed, "Password required");
}
status = bsonExtractStringField(saslParameters, saslCommandIamSessionToken, &value);
status = bsonExtractStringField(props, saslCommandIamSessionToken, &value);
if (status.isOK()) {
session->setParameter(SaslClientSession::parameterAWSSessionToken, value);
}
status = bsonExtractStringField(saslParameters, saslCommandOIDCAccessToken, &value);
status = bsonExtractStringField(props, saslCommandOIDCAccessToken, &value);
if (status.isOK()) {
session->setParameter(SaslClientSession::parameterOIDCAccessToken, value);
}
@ -304,36 +272,21 @@ namespace {
*/
Future<void> saslClientAuthenticateImpl(auth::RunCommandHook runCommand,
const HostAndPort& hostname,
const BSONObj& saslParameters) {
int saslLogLevel = getSaslClientLogLevel(saslParameters);
std::string targetDatabase;
try {
Status status = bsonExtractStringFieldWithDefault(
saslParameters, saslCommandUserDBFieldName, saslDefaultDBName, &targetDatabase);
if (!status.isOK())
return status;
} catch (const DBException& ex) {
return ex.toStatus();
}
const auth::Credential& credential) {
if (credential.mechanism == auth::AuthMechanism::kMongoDbCr)
return Status{ErrorCodes::AuthenticationFailed,
"MONGODB-CR is deprecated and no longer supported. Use SCRAM for "
"password-based authentication instead."};
std::string username;
Status status = bsonExtractStringFieldWithDefault(
saslParameters, saslCommandUserFieldName, ""_sd, &username);
if (!status.isOK()) {
return status;
}
std::string mechanism;
status = bsonExtractStringField(saslParameters, saslCommandMechanismFieldName, &mechanism);
if (!status.isOK()) {
return status;
}
int saslLogLevel = getSaslClientLogLevel(credential);
const auto targetDatabase = credential.db.value_or(std::string{saslDefaultDBName});
const auto mechStr = toString(credential.mechanism);
// NOTE: this must be a shared_ptr so that we can capture it in a lambda later on.
// Come C++14, we should be able to do this in a nicer way.
std::shared_ptr<SaslClientSession> session(SaslClientSession::create(mechanism));
std::shared_ptr<SaslClientSession> session(SaslClientSession::create(std::string{mechStr}));
status = saslConfigureSession(session.get(), hostname, targetDatabase, saslParameters);
auto status = saslConfigureSession(session.get(), hostname, credential);
if (!status.isOK())
return status;
@ -344,11 +297,11 @@ Future<void> saslClientAuthenticateImpl(auth::RunCommandHook runCommand,
BSONObj inputObj = BSON(saslCommandPayloadFieldName << "");
auto mechCounter = authCounter.getEgressMechanismCounter(mechanism);
auto mechCounter = authCounter.getEgressMechanismCounter(mechStr);
mechCounter.incAuthenticateSent();
auto argsBlock =
std::make_tuple(hostname, saslParameters, username, targetDatabase, mechanism, mechCounter);
const auto username = credential.username.value_or("");
auto argsBlock = std::make_tuple(hostname, username, targetDatabase, mechStr, mechCounter);
auto sharedBlock = std::make_shared<decltype(argsBlock)>(std::move(argsBlock));
session->metrics()->restart();
@ -357,13 +310,11 @@ Future<void> saslClientAuthenticateImpl(auth::RunCommandHook runCommand,
runCommand, session, saslFirstCommandPrefix, inputObj, targetDatabase, saslLogLevel)
.onError([session, sharedBlock](Status status) {
BSONObj metrics = session->metrics()->captureEgress();
auto [hostname, saslParameters, username, targetDatabase, mechanism, _] =
*sharedBlock.get();
auto [hostname, username, targetDatabase, mechanism, _] = *sharedBlock.get();
if (gEnableDetailedConnectionHealthMetricLogLines.load()) {
LOGV2(10748700,
"Authentication to remote host failed using SASL",
"hostname"_attr = hostname,
"saslParameters"_attr = saslParameters,
"username"_attr = username,
"targetDatabase"_attr = targetDatabase,
"mechanism"_attr = mechanism,
@ -375,14 +326,12 @@ Future<void> saslClientAuthenticateImpl(auth::RunCommandHook runCommand,
})
.then([session, sharedBlock]() {
BSONObj metrics = session->metrics()->captureEgress();
auto [hostname, saslParameters, username, targetDatabase, mechanism, mechCounter] =
*sharedBlock.get();
auto [hostname, username, targetDatabase, mechanism, mechCounter] = *sharedBlock.get();
mechCounter.incEgressAuthenticateSuccessful();
if (gEnableDetailedConnectionHealthMetricLogLines.load()) {
LOGV2(10748701,
"Authentication to remote host succeeded using SASL",
"hostname"_attr = hostname,
"saslParameters"_attr = saslParameters,
"username"_attr = username,
"targetDatabase"_attr = targetDatabase,
"mechanism"_attr = mechanism,
@ -390,7 +339,6 @@ Future<void> saslClientAuthenticateImpl(auth::RunCommandHook runCommand,
"metrics"_attr = metrics);
}
});
;
}
MONGO_INITIALIZER(SaslClientAuthenticateFunction)(InitializerContext* context) {

View File

@ -0,0 +1,88 @@
/**
* Copyright (C) 2026-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/base/error_codes.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/client/credential.h"
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/client/sasl_client_session.h"
#include "mongo/db/auth/auth_mechanism.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/net/hostandport.h"
#include <string>
namespace mongo {
namespace {
class StubSaslClientSession : public SaslClientSession {
public:
Status initialize() override {
return Status::OK();
}
Status step(StringData, std::string*) override {
return Status::OK();
}
bool isSuccess() const override {
return true;
}
};
const HostAndPort kHost{"localhost", 27017};
TEST(SaslConfigureSessionTest, DigestPasswordWrongTypeReturnsError) {
StubSaslClientSession session;
auth::Credential cred;
cred.mechanism = auth::AuthMechanism::kScramSha1;
cred.username = std::string{"alice"};
cred.password = std::string{"secret"};
cred.mechanismProperties = BSON("digestPassword" << "notabool");
auto status = saslConfigureSession(&session, kHost, cred);
ASSERT_NOT_OK(status);
ASSERT_EQ(status.code(), ErrorCodes::TypeMismatch);
}
TEST(SaslConfigureSessionTest, MissingPasswordForNonExternalUserReturnsAuthenticationFailed) {
StubSaslClientSession session;
auth::Credential cred;
cred.mechanism = auth::AuthMechanism::kScramSha256;
cred.username = std::string{"alice"};
cred.db = std::string{"admin"};
auto status = saslConfigureSession(&session, kHost, cred);
ASSERT_NOT_OK(status);
ASSERT_EQ(status.code(), ErrorCodes::AuthenticationFailed);
}
} // namespace
} // namespace mongo

View File

@ -288,6 +288,19 @@ idl_generator(
],
)
mongo_cc_library(
name = "auth_mechanism",
srcs = [
"auth_mechanism.cpp",
],
hdrs = [
"auth_mechanism.h",
],
deps = [
"//src/mongo:base",
],
)
mongo_cc_library(
name = "auth_options",
srcs = [
@ -350,6 +363,7 @@ mongo_cc_library(
"sasl_options.cpp",
],
deps = [
":auth_mechanism",
"//src/mongo/db:server_base",
"//src/mongo/db/exec/document_value",
"//src/mongo/db/stats:counters",
@ -552,6 +566,7 @@ mongo_cc_library(
deps = [
"auth",
"authprivilege",
":auth_mechanism",
"//src/mongo/crypto:sha_block",
"//src/mongo/db:server_base",
],

View File

@ -0,0 +1,96 @@
/**
* Copyright (C) 2018-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/auth/auth_mechanism.h"
#include "mongo/base/error_codes.h"
#include "mongo/util/str.h"
namespace mongo {
namespace auth {
StringData toString(AuthMechanism mechanism) {
switch (mechanism) {
case AuthMechanism::kMongoX509:
return kMechanismMongoX509;
case AuthMechanism::kSaslPlain:
return kMechanismSaslPlain;
case AuthMechanism::kGSSAPI:
return kMechanismGSSAPI;
case AuthMechanism::kScramSha1:
return kMechanismScramSha1;
case AuthMechanism::kScramSha256:
return kMechanismScramSha256;
case AuthMechanism::kMongoAWS:
return kMechanismMongoAWS;
case AuthMechanism::kMongoOIDC:
return kMechanismMongoOIDC;
case AuthMechanism::kMongoDbCr:
return kMechanismMongoDbCr;
}
MONGO_UNREACHABLE;
}
StatusWith<AuthMechanism> authMechanismFromString(StringData s) {
if (s == kMechanismMongoX509)
return AuthMechanism::kMongoX509;
if (s == kMechanismSaslPlain)
return AuthMechanism::kSaslPlain;
if (s == kMechanismGSSAPI)
return AuthMechanism::kGSSAPI;
if (s == kMechanismScramSha1)
return AuthMechanism::kScramSha1;
if (s == kMechanismScramSha256)
return AuthMechanism::kScramSha256;
if (s == kMechanismMongoAWS)
return AuthMechanism::kMongoAWS;
if (s == kMechanismMongoOIDC)
return AuthMechanism::kMongoOIDC;
if (s == kMechanismMongoDbCr)
return AuthMechanism::kMongoDbCr;
return Status{ErrorCodes::InvalidOptions,
str::stream() << "Unsupported authentication mechanism: " << s};
}
Status validateAuthMechanism(StringData value) {
auto sw = authMechanismFromString(value);
if (!sw.isOK())
return {ErrorCodes::BadValue,
str::stream() << "Unknown authentication mechanism '" << value
<< "'. Supported mechanisms: MONGODB-X509, PLAIN, GSSAPI, "
"SCRAM-SHA-1, SCRAM-SHA-256, MONGODB-AWS, MONGODB-OIDC"};
if (sw.getValue() == AuthMechanism::kMongoDbCr)
return Status{ErrorCodes::BadValue,
"MONGODB-CR is deprecated and no longer supported. Use SCRAM for "
"password-based authentication instead."};
return Status::OK();
}
} // namespace auth
} // namespace mongo

View File

@ -0,0 +1,72 @@
/**
* Copyright (C) 2018-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
#include "mongo/util/modules.h"
namespace mongo {
namespace MONGO_MOD_PUBLIC auth {
/** Wire-protocol names for supported authentication mechanisms. */
constexpr auto kMechanismMongoX509 = "MONGODB-X509"_sd;
constexpr auto kMechanismSaslPlain = "PLAIN"_sd;
constexpr auto kMechanismGSSAPI = "GSSAPI"_sd;
constexpr auto kMechanismScramSha1 = "SCRAM-SHA-1"_sd;
constexpr auto kMechanismScramSha256 = "SCRAM-SHA-256"_sd;
constexpr auto kMechanismMongoAWS = "MONGODB-AWS"_sd;
constexpr auto kMechanismMongoOIDC = "MONGODB-OIDC"_sd;
constexpr auto kMechanismMongoDbCr = "MONGODB-CR"_sd;
constexpr auto kInternalAuthFallbackMechanism = kMechanismScramSha1;
/** Typed enum of supported authentication mechanisms. */
enum class AuthMechanism {
kMongoX509,
kSaslPlain,
kGSSAPI,
kScramSha1,
kScramSha256,
kMongoAWS,
kMongoOIDC,
kMongoDbCr, // deprecated; recognized at parse time, rejected at auth time
};
/** Return the wire-protocol string for a mechanism (e.g. "SCRAM-SHA-256"). */
StringData toString(AuthMechanism mechanism);
/** Parse a wire-protocol string into an AuthMechanism, or return InvalidOptions. */
StatusWith<AuthMechanism> authMechanismFromString(StringData s);
/** Validate that a string names a supported mechanism; returns BadValue on failure. */
Status validateAuthMechanism(StringData value);
} // namespace MONGO_MOD_PUBLIC auth
} // namespace mongo

View File

@ -103,7 +103,7 @@ Service::ConstructorActionRegisterer createAuthorizationManager(
if (clusterAuthMode.sendsX509()) {
// Send x509 authentication if we can.
#ifdef MONGO_CONFIG_SSL
auth::setInternalUserAuthParams(auth::createInternalX509AuthDocument(
auth::setInternalUserAuthParams(auth::createInternalX509AuthCredential(
boost::optional<StringData>{SSLManagerCoordinator::get()
->getSSLManager()
->getSSLConfiguration()

View File

@ -31,6 +31,7 @@
#include "mongo/base/string_data.h"
#include "mongo/crypto/hash_block.h"
#include "mongo/db/auth/auth_mechanism.h"
#include "mongo/db/auth/sasl_mechanism_registry.h"
#include "mongo/util/modules.h"
@ -38,7 +39,7 @@ namespace mongo {
struct AWSIAMPolicy {
static constexpr StringData getName() {
return "MONGODB-AWS"_sd;
return auth::kMechanismMongoAWS;
}
static SecurityPropertySet getProperties() {
return SecurityPropertySet{SecurityProperty::kNoPlainText};
@ -53,7 +54,7 @@ struct AWSIAMPolicy {
struct PLAINPolicy {
static constexpr StringData getName() {
return "PLAIN"_sd;
return auth::kMechanismSaslPlain;
}
static SecurityPropertySet getProperties() {
return SecurityPropertySet{};
@ -70,7 +71,7 @@ struct SCRAMSHA1Policy {
using HashBlock = SHA1Block;
static constexpr StringData getName() {
return "SCRAM-SHA-1"_sd;
return auth::kMechanismScramSha1;
}
static SecurityPropertySet getProperties() {
return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth};
@ -87,7 +88,7 @@ struct SCRAMSHA256Policy {
using HashBlock = SHA256Block;
static constexpr StringData getName() {
return "SCRAM-SHA-256"_sd;
return auth::kMechanismScramSha256;
}
static SecurityPropertySet getProperties() {
return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth};
@ -102,7 +103,7 @@ struct SCRAMSHA256Policy {
struct GSSAPIPolicy {
static constexpr StringData getName() {
return "GSSAPI"_sd;
return auth::kMechanismGSSAPI;
}
static SecurityPropertySet getProperties() {
return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth};
@ -117,7 +118,7 @@ struct GSSAPIPolicy {
struct X509Policy {
static constexpr StringData getName() {
return "MONGODB-X509"_sd;
return auth::kMechanismMongoX509;
}
static SecurityPropertySet getProperties() {

View File

@ -31,6 +31,7 @@
#include "mongo/base/init.h" // IWYU pragma: keep
#include "mongo/base/initializer.h"
#include "mongo/db/auth/auth_mechanism.h"
#include "mongo/db/auth/sasl_options_gen.h"
#include "mongo/db/stats/counters.h"
#include "mongo/util/text.h" // IWYU pragma: keep
@ -38,7 +39,9 @@
namespace mongo {
const std::vector<std::string> SASLGlobalParams::kDefaultAuthenticationMechanisms =
std::vector<std::string>{"MONGODB-X509", "SCRAM-SHA-1", "SCRAM-SHA-256"};
std::vector<std::string>{std::string{auth::kMechanismMongoX509},
std::string{auth::kMechanismScramSha1},
std::string{auth::kMechanismScramSha256}};
SASLGlobalParams saslGlobalParams;
SASLGlobalParams::SASLGlobalParams() {

View File

@ -35,6 +35,7 @@
#include "mongo/crypto/sha1_block.h"
#include "mongo/crypto/sha256_block.h"
#include "mongo/db/auth/action_set.h"
#include "mongo/db/auth/auth_mechanism.h"
#include "mongo/db/auth/privilege.h"
#include "mongo/db/auth/resource_pattern.h"
#include "mongo/db/auth/restriction_set.h"
@ -241,8 +242,8 @@ class User {
public:
using UserId = std::vector<std::uint8_t>;
constexpr static auto kSHA1FieldName = "SCRAM-SHA-1"_sd;
constexpr static auto kSHA256FieldName = "SCRAM-SHA-256"_sd;
constexpr static auto kSHA1FieldName = auth::kMechanismScramSha1;
constexpr static auto kSHA256FieldName = auth::kMechanismScramSha256;
constexpr static auto kExternalFieldName = "external"_sd;
constexpr static auto kIterationCountFieldName = "iterationCount"_sd;
constexpr static auto kSaltFieldName = "salt"_sd;

View File

@ -74,8 +74,7 @@ constexpr StringData READONLY_FIELD_NAME = "readOnly"_sd;
constexpr StringData CREDENTIALS_FIELD_NAME = "credentials"_sd;
constexpr StringData ROLE_NAME_FIELD_NAME = "role"_sd;
constexpr StringData ROLE_DB_FIELD_NAME = "db"_sd;
constexpr StringData SCRAMSHA1_CREDENTIAL_FIELD_NAME = "SCRAM-SHA-1"_sd;
constexpr StringData SCRAMSHA256_CREDENTIAL_FIELD_NAME = "SCRAM-SHA-256"_sd;
constexpr StringData MONGODB_EXTERNAL_CREDENTIAL_FIELD_NAME = "external"_sd;
constexpr StringData AUTHENTICATION_RESTRICTIONS_FIELD_NAME = "authenticationRestrictions"_sd;
constexpr StringData INHERITED_AUTHENTICATION_RESTRICTIONS_FIELD_NAME =
@ -220,11 +219,11 @@ Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const {
return Status::OK();
};
auto sha1status = validateScram(SCRAMSHA1_CREDENTIAL_FIELD_NAME);
auto sha1status = validateScram(auth::kMechanismScramSha1);
if (!sha1status.isOK() && (sha1status.code() != ErrorCodes::NoSuchKey)) {
return sha1status;
}
auto sha256status = validateScram(SCRAMSHA256_CREDENTIAL_FIELD_NAME);
auto sha256status = validateScram(auth::kMechanismScramSha256);
if (!sha256status.isOK() && (sha256status.code() != ErrorCodes::NoSuchKey)) {
return sha256status;
}
@ -278,9 +277,9 @@ Status V2UserDocumentParser::initializeUserCredentialsFromUserDocument(
}
} else {
const bool haveSha1 = parseSCRAMCredentials(
credentialsElement, credentials.scram_sha1, SCRAMSHA1_CREDENTIAL_FIELD_NAME);
credentialsElement, credentials.scram_sha1, auth::kMechanismScramSha1);
const bool haveSha256 = parseSCRAMCredentials(
credentialsElement, credentials.scram_sha256, SCRAMSHA256_CREDENTIAL_FIELD_NAME);
credentialsElement, credentials.scram_sha256, auth::kMechanismScramSha256);
if (!haveSha1 && !haveSha256) {
return Status(

View File

@ -432,6 +432,7 @@ mongo_cc_library(
"//src/mongo/db:common",
"//src/mongo/db:server_base",
"//src/mongo/db/auth:address_restriction",
"//src/mongo/db/auth:auth_mechanism",
"//src/mongo/db/auth:authprivilege",
"//src/mongo/db/auth:role_name_or_string",
],

View File

@ -87,7 +87,6 @@ namespace {
constexpr auto kDBFieldName = "db"_sd;
constexpr auto kSASLPayloadUsernameField = "username"_sd;
constexpr StringData kX509AuthMechanism = "MONGODB-X509"_sd;
class CmdLogout : public TypedCommand<CmdLogout> {
public:
@ -251,7 +250,7 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session)
const auto& v = saslGlobalParams.authenticationMechanisms;
uassert(ErrorCodes::BadValue,
kX509AuthenticationDisabledMessage,
std::find(v.begin(), v.end(), kX509AuthMechanism) != v.end());
std::find(v.begin(), v.end(), auth::kMechanismMongoX509) != v.end());
uassertStatusOK(
authorizationSession->addAndAuthorizeUser(opCtx, std::move(request), boost::none));

View File

@ -48,6 +48,7 @@
#include "mongo/db/audit.h"
#include "mongo/db/auth/action_set.h"
#include "mongo/db/auth/address_restriction.h"
#include "mongo/db/auth/auth_mechanism.h"
#include "mongo/db/auth/auth_name.h"
#include "mongo/db/auth/auth_options_gen.h"
#include "mongo/db/auth/authorization_backend_interface.h"
@ -589,9 +590,9 @@ void buildCredentials(BSONObjBuilder* builder, const UserName& userName, const T
bool buildSCRAMSHA1 = false, buildSCRAMSHA256 = false;
if (auto mechanisms = cmd.getMechanisms(); mechanisms && !mechanisms->empty()) {
for (const auto& mech : mechanisms.get()) {
if (mech == "SCRAM-SHA-1") {
if (mech == auth::kMechanismScramSha1) {
buildSCRAMSHA1 = true;
} else if (mech == "SCRAM-SHA-256") {
} else if (mech == auth::kMechanismScramSha256) {
buildSCRAMSHA256 = true;
} else {
uasserted(ErrorCodes::BadValue,
@ -604,8 +605,8 @@ void buildCredentials(BSONObjBuilder* builder, const UserName& userName, const T
}
} else {
buildSCRAMSHA1 = haveAuthMechanism("SCRAM-SHA-1");
buildSCRAMSHA256 = haveAuthMechanism("SCRAM-SHA-256");
buildSCRAMSHA1 = haveAuthMechanism(auth::kMechanismScramSha1);
buildSCRAMSHA256 = haveAuthMechanism(auth::kMechanismScramSha256);
}
auto password = cmd.getPwd().get();
@ -621,7 +622,7 @@ void buildCredentials(BSONObjBuilder* builder, const UserName& userName, const T
}
auto sha1Cred = scram::Secrets<SHA1Block>::generateCredentials(
hashedPwd, saslGlobalParams.scramSHA1IterationCount.load());
builder->append("SCRAM-SHA-1", sha1Cred);
builder->append(auth::kMechanismScramSha1, sha1Cred);
}
if (buildSCRAMSHA256) {
@ -632,7 +633,7 @@ void buildCredentials(BSONObjBuilder* builder, const UserName& userName, const T
auto prepPwd = uassertStatusOK(icuSaslPrep(password));
auto sha256Cred = scram::Secrets<SHA256Block>::generateCredentials(
prepPwd, saslGlobalParams.scramSHA256IterationCount.load());
builder->append("SCRAM-SHA-256", sha256Cred);
builder->append(auth::kMechanismScramSha256, sha256Cred);
}
}
@ -1121,9 +1122,9 @@ void trimCredentials(OperationContext* opCtx,
"mechanisms field must be a subset of previously set mechanisms",
creds.hasField(mech));
if (mech == "SCRAM-SHA-1") {
if (mech == auth::kMechanismScramSha1) {
keepSCRAMSHA1 = true;
} else if (mech == "SCRAM-SHA-256") {
} else if (mech == auth::kMechanismScramSha256) {
keepSCRAMSHA256 = true;
}
}
@ -2155,8 +2156,8 @@ void _auditCreateOrUpdateUser(const BSONObj& userObj, bool create) {
authenticationRestrictions = r.getValue();
}
const bool hasPwd = userObj["credentials"].Obj().hasField("SCRAM-SHA-1") ||
userObj["credentials"].Obj().hasField("SCRAM-SHA-256");
const bool hasPwd = userObj["credentials"].Obj().hasField(auth::kMechanismScramSha1) ||
userObj["credentials"].Obj().hasField(auth::kMechanismScramSha256);
if (create) {
audit::logCreateUser(Client::getCurrent(),
userName,

View File

@ -29,7 +29,7 @@ global:
cpp_namespace: "mongo"
mod_visibility: public
cpp_includes:
- "mongo/db/commands/user_management_commands_validation.h"
- "mongo/db/auth/auth_mechanism.h"
imports:
- "mongo/db/basic_types.idl"
@ -373,7 +373,7 @@ commands:
type: string
optional: true
validator:
callback: validateAuthMechanism
callback: auth::validateAuthMechanism
filter:
description: >-
A document that specifies $match stage conditions to return information

View File

@ -201,6 +201,7 @@ mongo_cc_library(
":egress_connection_closer_manager",
":network_interface",
":network_interface_tl",
"//src/mongo/client:authentication",
] + select({
"//bazel/config:build_grpc_enabled": ["//src/mongo/transport/grpc:grpc_async_client_factory"],
"//conditions:default": [],
@ -463,15 +464,18 @@ mongo_cc_unit_test(
"async_rpc",
"async_rpc_util",
"connection_pool_executor",
"connection_pool_tl",
"egress_connection_closer_manager",
"network_interface_mock",
"network_interface_tl",
"network_test_env",
"pinned_connection_task_executor",
"thread_pool_task_executor_test_fixture",
"//src/mongo/client:authentication",
"//src/mongo/client:remote_command_targeter",
"//src/mongo/client:remote_command_targeter_mock",
"//src/mongo/db:service_context_test_fixture",
"//src/mongo/db/auth:auth_mechanism",
"//src/mongo/db/commands:standalone",
"//src/mongo/db/repl:task_executor_mock",
"//src/mongo/db/repl/hello:hello_command",

View File

@ -33,6 +33,7 @@
#include "mongo/base/status.h"
#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
#include "mongo/client/authenticate.h"
#include "mongo/config.h" // IWYU pragma: keep
#include "mongo/executor/connection_pool_state.h"
#include "mongo/executor/connection_pool_stats.h"
@ -178,6 +179,12 @@ public:
*/
bool skipAuthentication = false;
/**
* If set, new connections authenticate as this client credential immediately after setup,
* before being returned to callers.
*/
boost::optional<auth::Credential> credential;
#ifdef MONGO_CONFIG_SSL
/**
* Provides SSL params if the egress cluster connection requires custom SSL certificates

View File

@ -283,7 +283,7 @@ public:
// X.509 auth only means we only want to use a single mechanism regards of what hello says
if (_x509AuthOnly) {
_saslMechsForInternalAuth.clear();
_saslMechsForInternalAuth.push_back("MONGODB-X509");
_saslMechsForInternalAuth.push_back(std::string{auth::kMechanismMongoX509});
} else {
const auto saslMechsElem = reply.getField("saslSupportedMechs");
if (saslMechsElem.type() == BSONType::array) {
@ -359,14 +359,14 @@ public:
~TransientInternalAuthParametersProvider() override = default;
BSONObj get(size_t index, StringData mechanism) final {
boost::optional<auth::Credential> get(size_t index, StringData mechanism) final {
if (_transientSSLContext) {
if (index == 0) {
return auth::createInternalX509AuthDocument(
return auth::createInternalX509AuthCredential(
boost::optional<StringData>{_transientSSLContext->manager->getSSLConfiguration()
.clientSubjectName.toString()});
} else {
return BSONObj();
return boost::none;
}
}
@ -467,6 +467,10 @@ void TLConnection::setup(Milliseconds timeout, SetupCallback cb, std::string ins
return Future<void>::makeReady();
}
if (_credential) {
return _client->authenticate(*_credential);
}
boost::optional<std::string> mechanism;
if (!helloHook->saslMechsForInternalAuth().empty())
mechanism = helloHook->saslMechsForInternalAuth().front();
@ -615,7 +619,8 @@ std::shared_ptr<ConnectionPool::ConnectionInterface> TLTypeFactory::makeConnecti
generation,
_onConnectHook.get(),
_connPoolOptions.skipAuthentication,
_transientSSLContext);
_transientSSLContext,
_connPoolOptions.credential);
fasten(conn.get());
return conn;
}

View File

@ -31,6 +31,7 @@
#include "mongo/base/string_data.h"
#include "mongo/client/async_client.h"
#include "mongo/client/authenticate.h"
#include "mongo/db/service_context.h"
#include "mongo/executor/connection_metrics.h"
#include "mongo/executor/connection_pool.h"
@ -185,7 +186,8 @@ public:
size_t generation,
NetworkConnectionHook* onConnectHook,
bool skipAuth,
std::shared_ptr<const transport::SSLConnectionContext> transientSSLContext = nullptr)
std::shared_ptr<const transport::SSLConnectionContext> transientSSLContext = nullptr,
boost::optional<auth::Credential> credential = boost::none)
: ConnectionInterface(id, generation),
TLTypeFactory::Type(factory),
_reactor(reactor),
@ -197,6 +199,7 @@ public:
_sslMode(sslMode),
_onConnectHook(onConnectHook),
_transientSSLContext(transientSSLContext),
_credential(std::move(credential)),
_connMetrics(serviceContext->getFastClockSource()) {}
~TLConnection() override {
@ -241,6 +244,7 @@ private:
NetworkConnectionHook* const _onConnectHook;
// SSL context to use intead of the default one for this pool.
const std::shared_ptr<const transport::SSLConnectionContext> _transientSSLContext;
boost::optional<auth::Credential> _credential;
// Guards assignment of the _client pointer.
// Do not need to acquire this in contexts where the pointer is known to be valid.

View File

@ -802,7 +802,7 @@ void MongoExternalInfo::construct(JSContext* cx, JS::CallArgs args) {
uassert(ErrorCodes::InvalidOptions,
"Authentication is not currently supported when gRPC mode is enabled",
cs.getUser().empty());
!cs.getCredential() || !cs.getCredential()->username);
}
#endif

View File

@ -101,8 +101,13 @@ void URIInfo::construct(JSContext* cx, JS::CallArgs args) {
ObjectWrapper o(cx, thisv);
o.setValue(InternedString::uri, uriArg);
o.setString(InternedString::user, parsed.getUser());
o.setString(InternedString::password, parsed.getPassword());
auto& cred = parsed.getCredential();
if (cred && cred->username) {
o.setString(InternedString::user, *cred->username);
}
if (cred && cred->password) {
o.setString(InternedString::password, *cred->password);
}
o.setBSON(InternedString::options, optsBuilder.obj(), true);
o.setString(InternedString::database, parsed.getDatabase());
o.setBoolean(InternedString::isValid, parsed.isValid());

View File

@ -745,11 +745,13 @@ int mongo_main(int argc, char* argv[]) {
bool usingPassword = !shellGlobalParams.password.empty();
if (mechanismRequiresPassword(parsedURI) &&
(parsedURI.getUser().size() || shellGlobalParams.username.size())) {
((parsedURI.getCredential() && parsedURI.getCredential()->username) ||
shellGlobalParams.username.size())) {
usingPassword = true;
}
if (usingPassword && parsedURI.getPassword().empty()) {
if (usingPassword &&
(!parsedURI.getCredential() || !parsedURI.getCredential()->password)) {
if (!shellGlobalParams.password.empty()) {
parsedURI.setPassword(std::as_const(shellGlobalParams.password));
} else {
@ -757,7 +759,8 @@ int mongo_main(int argc, char* argv[]) {
}
}
if (parsedURI.getUser().empty() && !shellGlobalParams.username.empty()) {
if ((!parsedURI.getCredential() || !parsedURI.getCredential()->username) &&
!shellGlobalParams.username.empty()) {
parsedURI.setUser(std::as_const(shellGlobalParams.username));
}

View File

@ -283,11 +283,13 @@ Status storeMongoShellOptions(const moe::Environment& params,
StringBuilder sb;
sb << "ERROR: Cannot specify ";
if (!shellGlobalParams.username.empty() && !cs.getUser().empty() &&
shellGlobalParams.username != cs.getUser()) {
if (!shellGlobalParams.username.empty() && cs.getCredential() &&
cs.getCredential()->username &&
shellGlobalParams.username != *cs.getCredential()->username) {
sb << "different usernames";
} else if (!shellGlobalParams.password.empty() && !cs.getPassword().empty() &&
shellGlobalParams.password != cs.getPassword()) {
} else if (!shellGlobalParams.password.empty() && cs.getCredential() &&
cs.getCredential()->password &&
shellGlobalParams.password != *cs.getCredential()->password) {
sb << "different passwords";
} else if (!shellGlobalParams.authenticationMechanism.empty() &&
uriOptions.count("authMechanism") &&

View File

@ -76,7 +76,7 @@ public:
// Set the internal user auth parameters so we auth with X.509 externally
auth::setInternalUserAuthParams(
auth::createInternalX509AuthDocument(boost::optional<StringData>("Ignored")));
auth::createInternalX509AuthCredential(boost::optional<StringData>("Ignored")));
createNet();
net().startup();

View File

@ -377,7 +377,7 @@ void SSLManagerCoordinator::rotate() {
auto svcCtx = getGlobalServiceContext();
const auto clusterAuthMode = ClusterAuthMode::get(svcCtx);
if (clusterAuthMode.sendsX509()) {
auth::setInternalUserAuthParams(auth::createInternalX509AuthDocument(
auth::setInternalUserAuthParams(auth::createInternalX509AuthCredential(
StringData(manager->getSSLConfiguration().clientSubjectName.toString())));
}

View File

@ -33,6 +33,7 @@
#include "mongo/db/auth/cluster_auth_mode.h"
#include "mongo/db/auth/sasl_command_constants.h"
#include "mongo/db/server_options.h"
#include "mongo/db/service_context.h"
#include "mongo/util/net/ssl_options.h"
#include "mongo/util/net/ssl_parameters_auth_gen.h"
@ -61,7 +62,7 @@ Status ClusterAuthModeServerParameter::setFromString(StringData strMode,
// Set our ingress mode, then our egress parameters.
ClusterAuthMode::set(getGlobalServiceContext(), mode);
if (mode.sendsX509()) {
auth::setInternalUserAuthParams(auth::createInternalX509AuthDocument());
auth::setInternalUserAuthParams(auth::createInternalX509AuthCredential());
}
return Status::OK();