diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2020-12-10 19:59:08 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-12-11 03:25:30 +0000 |
commit | 19ed9c958b369bd7e1776a57bd406ebe84cf2bec (patch) | |
tree | 92feec2a6104e28a340d2a8469220dbda4635a9f /src/mongo | |
parent | dc77c3d344443071783d7098e75d2379bc749be3 (diff) | |
download | mongo-19ed9c958b369bd7e1776a57bd406ebe84cf2bec.tar.gz |
SERVER-52945 Make mongod use x509 auth on egress connections if NetworkInterface has SSLConnectionContext override even if other egress connections use keyFile auth
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/client/async_client.cpp | 7 | ||||
-rw-r--r-- | src/mongo/client/async_client.h | 4 | ||||
-rw-r--r-- | src/mongo/client/authenticate.cpp | 37 | ||||
-rw-r--r-- | src/mongo/client/authenticate.h | 28 | ||||
-rw-r--r-- | src/mongo/client/dbclient_base.cpp | 8 | ||||
-rw-r--r-- | src/mongo/client/internal_auth.cpp | 14 | ||||
-rw-r--r-- | src/mongo/client/internal_auth.h | 10 | ||||
-rw-r--r-- | src/mongo/db/initialize_server_security_state.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/mongod_options.cpp | 1 | ||||
-rw-r--r-- | src/mongo/executor/connection_pool_tl.cpp | 78 | ||||
-rw-r--r-- | src/mongo/util/net/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/util/net/network_interface_ssl_test.cpp | 28 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager.cpp | 7 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_openssl.cpp | 173 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_test.cpp | 10 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_parameters_auth.cpp | 4 |
16 files changed, 312 insertions, 111 deletions
diff --git a/src/mongo/client/async_client.cpp b/src/mongo/client/async_client.cpp index f637a3c0f17..5037c621a04 100644 --- a/src/mongo/client/async_client.cpp +++ b/src/mongo/client/async_client.cpp @@ -165,7 +165,9 @@ Future<void> AsyncDBClient::authenticate(const BSONObj& params) { return auth::authenticateClient(params, remote(), clientName, _makeAuthRunCommandHook()); } -Future<void> AsyncDBClient::authenticateInternal(boost::optional<std::string> mechanismHint) { +Future<void> AsyncDBClient::authenticateInternal( + boost::optional<std::string> mechanismHint, + std::shared_ptr<auth::InternalAuthParametersProvider> authProvider) { // If no internal auth information is set, don't bother trying to authenticate. if (!auth::isInternalAuthSet()) { return Future<void>::makeReady(); @@ -182,7 +184,8 @@ Future<void> AsyncDBClient::authenticateInternal(boost::optional<std::string> me return auth::authenticateInternalClient(clientName, mechanismHint, auth::StepDownBehavior::kKillConnection, - _makeAuthRunCommandHook()); + _makeAuthRunCommandHook(), + std::move(authProvider)); } Future<bool> AsyncDBClient::completeSpeculativeAuth(std::shared_ptr<SaslClientSession> session, diff --git a/src/mongo/client/async_client.h b/src/mongo/client/async_client.h index 1bfcdad04bd..f586335949b 100644 --- a/src/mongo/client/async_client.h +++ b/src/mongo/client/async_client.h @@ -77,7 +77,9 @@ public: Future<void> authenticate(const BSONObj& params); - Future<void> authenticateInternal(boost::optional<std::string> mechanismHint); + Future<void> authenticateInternal( + boost::optional<std::string> mechanismHint, + std::shared_ptr<auth::InternalAuthParametersProvider> authProvider); Future<bool> completeSpeculativeAuth(std::shared_ptr<SaslClientSession> session, std::string authDB, diff --git a/src/mongo/client/authenticate.cpp b/src/mongo/client/authenticate.cpp index e280547fa5e..af3b48f73b1 100644 --- a/src/mongo/client/authenticate.cpp +++ b/src/mongo/client/authenticate.cpp @@ -35,8 +35,10 @@ #include "mongo/base/status.h" #include "mongo/base/status_with.h" +#include "mongo/bson/bsonobj.h" #include "mongo/bson/json.h" #include "mongo/bson/util/bson_extract.h" +#include "mongo/client/internal_auth.h" #include "mongo/client/sasl_client_authenticate.h" #include "mongo/config.h" #include "mongo/db/auth/authorization_manager.h" @@ -138,7 +140,22 @@ Future<void> authX509(RunCommandHook runCommand, const BSONObj& params, StringDa return runCommand(authRequest.getValue()).ignoreValue(); } +class DefaultInternalAuthParametersProvider : public InternalAuthParametersProvider { +public: + ~DefaultInternalAuthParametersProvider() = default; + + BSONObj get(size_t index, StringData mechanism) final { + return getInternalAuthParams(index, mechanism); + } +}; + } // namespace + +std::shared_ptr<InternalAuthParametersProvider> createDefaultInternalAuthProvider() { + return std::make_shared<DefaultInternalAuthParametersProvider>(); +} + + // // General Auth // @@ -229,22 +246,26 @@ Future<std::string> negotiateSaslMechanism(RunCommandHook runCommand, }); } -Future<void> authenticateInternalClient(const std::string& clientSubjectName, - boost::optional<std::string> mechanismHint, - StepDownBehavior stepDownBehavior, - RunCommandHook runCommand) { +Future<void> authenticateInternalClient( + const std::string& clientSubjectName, + boost::optional<std::string> mechanismHint, + StepDownBehavior stepDownBehavior, + RunCommandHook runCommand, + std::shared_ptr<InternalAuthParametersProvider> internalParamsProvider) { return negotiateSaslMechanism( runCommand, internalSecurity.user->getName(), mechanismHint, stepDownBehavior) - .then([runCommand, clientSubjectName](std::string mechanism) -> Future<void> { - auto params = getInternalAuthParams(0, mechanism); + .then([runCommand, clientSubjectName, internalParamsProvider]( + std::string mechanism) -> Future<void> { + auto params = internalParamsProvider->get(0, mechanism); if (params.isEmpty()) { return Status(ErrorCodes::BadValue, "Missing authentication parameters for internal user auth"); } return authenticateClient(params, HostAndPort(), clientSubjectName, runCommand) .onError<ErrorCodes::AuthenticationFailed>( - [runCommand, clientSubjectName, mechanism](Status status) -> Future<void> { - auto altCreds = getInternalAuthParams(1, mechanism); + [runCommand, clientSubjectName, mechanism, internalParamsProvider]( + Status status) -> Future<void> { + auto altCreds = internalParamsProvider->get(1, mechanism); if (!altCreds.isEmpty()) { return authenticateClient( altCreds, HostAndPort(), clientSubjectName, runCommand); diff --git a/src/mongo/client/authenticate.h b/src/mongo/client/authenticate.h index 44d90eae612..8a3477da2c1 100644 --- a/src/mongo/client/authenticate.h +++ b/src/mongo/client/authenticate.h @@ -81,6 +81,24 @@ constexpr auto kAuthenticateCommand = "authenticate"_sd; enum class StepDownBehavior { kKillConnection, kKeepConnectionOpen }; /** + * Provider of SASL credentials for internal authentication purposes. + */ +class InternalAuthParametersProvider { +public: + virtual ~InternalAuthParametersProvider() = default; + + /** + * Get the information for a given SASL mechanism. + * + * If there are multiple entries for a mechanism, suppots retrieval by index. Used when rotating + * the security key. + */ + virtual BSONObj get(size_t index, StringData mechanism) = 0; +}; + +std::shared_ptr<InternalAuthParametersProvider> createDefaultInternalAuthProvider(); + +/** * Authenticate a user. * * Pass the default hostname for this client in through "hostname." If SSL is enabled and @@ -126,10 +144,12 @@ Future<void> authenticateClient(const BSONObj& params, * Because this may retry during cluster keyfile rollover, this may call the RunCommandHook more * than once, but will only call the AuthCompletionHandler once. */ -Future<void> authenticateInternalClient(const std::string& clientSubjectName, - boost::optional<std::string> mechanismHint, - StepDownBehavior stepDownBehavior, - RunCommandHook runCommand); +Future<void> authenticateInternalClient( + const std::string& clientSubjectName, + boost::optional<std::string> mechanismHint, + StepDownBehavior stepDownBehavior, + RunCommandHook runCommand, + std::shared_ptr<InternalAuthParametersProvider> internalParamsProvider); /** * Build a BSONObject representing parameters to be passed to authenticateClient(). Takes diff --git a/src/mongo/client/dbclient_base.cpp b/src/mongo/client/dbclient_base.cpp index 5de427f1a83..96f057e118b 100644 --- a/src/mongo/client/dbclient_base.cpp +++ b/src/mongo/client/dbclient_base.cpp @@ -539,9 +539,11 @@ Status DBClientBase::authenticateInternalUser(auth::StepDownBehavior stepDownBeh } #endif - auto status = auth::authenticateInternalClient( - clientName, boost::none, stepDownBehavior, _makeAuthRunCommandHook()) - .getNoThrow(); + auto authProvider = auth::createDefaultInternalAuthProvider(); + auto status = + auth::authenticateInternalClient( + clientName, boost::none, stepDownBehavior, _makeAuthRunCommandHook(), authProvider) + .getNoThrow(); if (status.isOK()) { return status; } diff --git a/src/mongo/client/internal_auth.cpp b/src/mongo/client/internal_auth.cpp index 65fc3d60d5f..1effff0a08a 100644 --- a/src/mongo/client/internal_auth.cpp +++ b/src/mongo/client/internal_auth.cpp @@ -72,7 +72,19 @@ bool isInternalAuthSet() { return internalAuthSet; } -BSONObj getInternalAuthParams(size_t idx, const std::string& mechanism) { +BSONObj createInternalX509AuthDocument(boost::optional<StringData> userName) { + BSONObjBuilder builder; + builder.append(saslCommandMechanismFieldName, "MONGODB-X509"); + builder.append(saslCommandUserDBFieldName, "$external"); + + if (userName) { + builder.append(saslCommandUserFieldName, userName.get()); + } + + return builder.obj(); +} + +BSONObj getInternalAuthParams(size_t idx, StringData mechanism) { stdx::lock_guard<Latch> lk(internalAuthKeysMutex); if (!internalAuthSet) { return BSONObj(); diff --git a/src/mongo/client/internal_auth.h b/src/mongo/client/internal_auth.h index e93b67ddde4..2ba33c9e136 100644 --- a/src/mongo/client/internal_auth.h +++ b/src/mongo/client/internal_auth.h @@ -69,7 +69,15 @@ bool isInternalAuthSet(); */ std::string getInternalAuthDB(); -BSONObj getInternalAuthParams(size_t idx, const std::string& mechanism); +/** + * Returns the internal auth sasl parameters. + */ +BSONObj getInternalAuthParams(size_t idx, StringData mechanism); + +/** + * Create a BSON document for internal authentication. + */ +BSONObj createInternalX509AuthDocument(boost::optional<StringData> userName = boost::none); } // namespace auth } // namespace mongo diff --git a/src/mongo/db/initialize_server_security_state.cpp b/src/mongo/db/initialize_server_security_state.cpp index 93b19abed90..46a08632239 100644 --- a/src/mongo/db/initialize_server_security_state.cpp +++ b/src/mongo/db/initialize_server_security_state.cpp @@ -62,13 +62,11 @@ bool initializeServerSecurityGlobalState(ServiceContext* service) { #ifdef MONGO_CONFIG_SSL if (clusterAuthMode == ServerGlobalParams::ClusterAuthMode_x509 || clusterAuthMode == ServerGlobalParams::ClusterAuthMode_sendX509) { - auth::setInternalUserAuthParams(BSON(saslCommandMechanismFieldName - << "MONGODB-X509" << saslCommandUserDBFieldName - << "$external" << saslCommandUserFieldName - << SSLManagerCoordinator::get() - ->getSSLManager() - ->getSSLConfiguration() - .clientSubjectName.toString())); + auth::setInternalUserAuthParams(auth::createInternalX509AuthDocument( + boost::optional<StringData>{SSLManagerCoordinator::get() + ->getSSLManager() + ->getSSLConfiguration() + .clientSubjectName.toString()})); } #endif diff --git a/src/mongo/db/mongod_options.cpp b/src/mongo/db/mongod_options.cpp index db09e05615f..5b60719af64 100644 --- a/src/mongo/db/mongod_options.cpp +++ b/src/mongo/db/mongod_options.cpp @@ -510,6 +510,7 @@ Status storeMongodOptions(const moe::Environment& params) { if (!replSettings.getReplSetString().empty() && (params.count("security.authorization") && params["security.authorization"].as<std::string>() == "enabled") && + serverGlobalParams.clusterAuthMode.load() != ServerGlobalParams::ClusterAuthMode_x509 && !params.count("security.keyFile")) { return Status( ErrorCodes::BadValue, diff --git a/src/mongo/executor/connection_pool_tl.cpp b/src/mongo/executor/connection_pool_tl.cpp index dd04a67368b..8d2dc9f615c 100644 --- a/src/mongo/executor/connection_pool_tl.cpp +++ b/src/mongo/executor/connection_pool_tl.cpp @@ -34,6 +34,7 @@ #include "mongo/executor/connection_pool_tl.h" #include "mongo/client/authenticate.h" +#include "mongo/config.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/logv2/log.h" @@ -163,10 +164,12 @@ void TLConnection::cancelTimeout() { _timer->cancelTimeout(); } +namespace { + class TLConnectionSetupHook : public executor::NetworkConnectionHook { public: - explicit TLConnectionSetupHook(executor::NetworkConnectionHook* hookToWrap) - : _wrappedHook(hookToWrap) {} + explicit TLConnectionSetupHook(executor::NetworkConnectionHook* hookToWrap, bool x509AuthOnly) + : _wrappedHook(hookToWrap), _x509AuthOnly(x509AuthOnly) {} BSONObj augmentIsMasterRequest(BSONObj cmdObj) override { BSONObjBuilder bob(std::move(cmdObj)); @@ -174,7 +177,12 @@ public: if (internalSecurity.user) { bob.append("saslSupportedMechs", internalSecurity.user->getName().getUnambiguousName()); } - _speculativeAuthType = auth::speculateInternalAuth(&bob, &_session); + + if (_x509AuthOnly) { + _speculativeAuthType = auth::SpeculativeAuthType::kAuthenticate; + } else { + _speculativeAuthType = auth::speculateInternalAuth(&bob, &_session); + } return bob.obj(); } @@ -184,11 +192,17 @@ public: const RemoteCommandResponse& isMasterReply) override try { const auto& reply = isMasterReply.data; - const auto saslMechsElem = reply.getField("saslSupportedMechs"); - if (saslMechsElem.type() == Array) { - auto array = saslMechsElem.Array(); - for (const auto& elem : array) { - _saslMechsForInternalAuth.push_back(elem.checkAndGetStringData().toString()); + // 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"); + } else { + const auto saslMechsElem = reply.getField("saslSupportedMechs"); + if (saslMechsElem.type() == Array) { + auto array = saslMechsElem.Array(); + for (const auto& elem : array) { + _saslMechsForInternalAuth.push_back(elem.checkAndGetStringData().toString()); + } } } @@ -245,8 +259,39 @@ private: auth::SpeculativeAuthType _speculativeAuthType; BSONObj _speculativeAuthenticate; executor::NetworkConnectionHook* const _wrappedHook = nullptr; + bool _x509AuthOnly; }; +#ifdef MONGO_CONFIG_SSL +class TransientInternalAuthParametersProvider : public auth::InternalAuthParametersProvider { +public: + TransientInternalAuthParametersProvider( + const std::shared_ptr<const transport::SSLConnectionContext> transientSSLContext) + : _transientSSLContext(transientSSLContext) {} + + ~TransientInternalAuthParametersProvider() = default; + + BSONObj get(size_t index, StringData mechanism) final { + if (_transientSSLContext) { + if (index == 0) { + return auth::createInternalX509AuthDocument( + boost::optional<StringData>{_transientSSLContext->manager->getSSLConfiguration() + .clientSubjectName.toString()}); + } else { + return BSONObj(); + } + } + + return auth::getInternalAuthParams(index, mechanism); + } + +private: + const std::shared_ptr<const transport::SSLConnectionContext> _transientSSLContext; +}; +#endif + +} // namespace + void TLConnection::setup(Milliseconds timeout, SetupCallback cb) { auto anchor = shared_from_this(); @@ -269,7 +314,18 @@ void TLConnection::setup(Milliseconds timeout, SetupCallback cb) { } }); - auto isMasterHook = std::make_shared<TLConnectionSetupHook>(_onConnectHook); +#ifdef MONGO_CONFIG_SSL + bool x509AuthOnly = + _transientSSLContext.get() && _transientSSLContext->targetClusterURI.has_value(); + auto authParametersProvider = + std::make_shared<TransientInternalAuthParametersProvider>(_transientSSLContext); +#else + bool x509AuthOnly = false; + auto authParametersProvider = auth::createDefaultInternalAuthProvider(); +#endif + + // For transient connections, only use X.509 auth. + auto isMasterHook = std::make_shared<TLConnectionSetupHook>(_onConnectHook, x509AuthOnly); AsyncDBClient::connect( _peer, _sslMode, _serviceContext, _reactor, timeout, _transientSSLContext) @@ -291,7 +347,7 @@ void TLConnection::setup(Milliseconds timeout, SetupCallback cb) { isMasterHook->getSpeculativeAuthenticateReply(), isMasterHook->getSpeculativeAuthType()); }) - .then([this, isMasterHook](bool authenticatedDuringConnect) { + .then([this, isMasterHook, authParametersProvider](bool authenticatedDuringConnect) { if (_skipAuth || authenticatedDuringConnect) { return Future<void>::makeReady(); } @@ -299,7 +355,7 @@ void TLConnection::setup(Milliseconds timeout, SetupCallback cb) { boost::optional<std::string> mechanism; if (!isMasterHook->saslMechsForInternalAuth().empty()) mechanism = isMasterHook->saslMechsForInternalAuth().front(); - return _client->authenticateInternal(std::move(mechanism)); + return _client->authenticateInternal(std::move(mechanism), authParametersProvider); }) .then([this] { if (!_onConnectHook) { diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript index 71c46e22df6..80ba93870ed 100644 --- a/src/mongo/util/net/SConscript +++ b/src/mongo/util/net/SConscript @@ -249,6 +249,8 @@ if get_option('ssl') == 'on': ], LIBDEPS=[ '$BUILD_DIR/mongo/client/connection_string', + '$BUILD_DIR/mongo/db/auth/builtin_roles', + '$BUILD_DIR/mongo/db/auth/user', '$BUILD_DIR/mongo/executor/network_interface', '$BUILD_DIR/mongo/executor/network_interface_factory', '$BUILD_DIR/mongo/executor/network_interface_fixture', diff --git a/src/mongo/util/net/network_interface_ssl_test.cpp b/src/mongo/util/net/network_interface_ssl_test.cpp index 8e3b446bdfe..b6eeebf1295 100644 --- a/src/mongo/util/net/network_interface_ssl_test.cpp +++ b/src/mongo/util/net/network_interface_ssl_test.cpp @@ -29,21 +29,23 @@ #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest -#include <fstream> - #include "mongo/platform/basic.h" +#include <fstream> + +#include "mongo/client/authenticate.h" +#include "mongo/db/auth/authorization_session_impl.h" #include "mongo/executor/network_interface_integration_fixture.h" #include "mongo/logv2/log.h" #include "mongo/unittest/integration_test.h" #include "mongo/unittest/unittest.h" -#include "mongo/util/assert_util.h" +#include "mongo/util/net/ssl_options.h" namespace mongo { namespace executor { namespace { -std::string LoadFile(const std::string& name) { +std::string loadFile(const std::string& name) { std::ifstream input(name); std::string str((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>()); return str; @@ -52,10 +54,26 @@ std::string LoadFile(const std::string& name) { class NetworkInterfaceSSLFixture : public NetworkInterfaceIntegrationFixture { public: void setUp() final { + + // Setup an internal user so that we can use it for external auth + UserHandle user(User(UserName("__system", "local"))); + + internalSecurity.user = user; + + // Force all connections to use SSL for outgoing + sslGlobalParams.sslMode.store(static_cast<int>(SSLParams::SSLModes::SSLMode_requireSSL)); + sslGlobalParams.sslCAFile = "jstests/libs/ca.pem"; + // Set a client cert that should be ignored if we use the transient cert correctly. + sslGlobalParams.sslPEMKeyFile = "jstests/libs/client.pem"; + + // Set the internal user auth parameters so we auth with X.509 externally + auth::setInternalUserAuthParams( + auth::createInternalX509AuthDocument(boost::optional<StringData>("Ignored"))); + ConnectionPool::Options options; options.transientSSLParams.emplace([] { TransientSSLParams params; - params.sslClusterPEMPayload = LoadFile("jstests/libs/client.pem"); + params.sslClusterPEMPayload = loadFile("jstests/libs/server.pem"); params.targetedClusterConnectionString = ConnectionString::forLocal(); return params; }()); diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp index 39228cdfb8c..d80d882fe87 100644 --- a/src/mongo/util/net/ssl_manager.cpp +++ b/src/mongo/util/net/ssl_manager.cpp @@ -372,11 +372,8 @@ void SSLManagerCoordinator::rotate() { int clusterAuthMode = serverGlobalParams.clusterAuthMode.load(); if (clusterAuthMode == ServerGlobalParams::ClusterAuthMode_x509 || clusterAuthMode == ServerGlobalParams::ClusterAuthMode_sendX509) { - auth::setInternalUserAuthParams( - BSON(saslCommandMechanismFieldName - << "MONGODB-X509" << saslCommandUserDBFieldName << "$external" - << saslCommandUserFieldName - << manager->getSSLConfiguration().clientSubjectName.toString())); + auth::setInternalUserAuthParams(auth::createInternalX509AuthDocument( + StringData(manager->getSSLConfiguration().clientSubjectName.toString()))); } auto tl = getGlobalServiceContext()->getTransportLayer(); diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp index 1cba80be3a4..a0ef1ec1e42 100644 --- a/src/mongo/util/net/ssl_manager_openssl.cpp +++ b/src/mongo/util/net/ssl_manager_openssl.cpp @@ -97,6 +97,8 @@ int SSL_CTX_set_ciphersuites(SSL_CTX*, const char*) { } #endif +using namespace fmt::literals; + namespace mongo { namespace { @@ -1128,7 +1130,7 @@ class SSLManagerOpenSSL : public SSLManagerInterface, public std::enable_shared_from_this<SSLManagerOpenSSL> { public: explicit SSLManagerOpenSSL(const SSLParams& params, bool isServer); - ~SSLManagerOpenSSL() final { + ~SSLManagerOpenSSL() { stopJobs(); } @@ -1292,13 +1294,23 @@ private: * @param subjectName as a pointer to the subject name variable being set. * @param serverNotAfter a Date_t object pointer that is valued if the * date is to be checked (as for a server certificate) and null otherwise. - * @return bool showing if the function was successful. + * @return Status::Ok showing if the function was successful. */ - bool _parseAndValidateCertificate(const std::string& keyFile, - PasswordFetcher* keyPassword, - SSLX509Name* subjectName, - Date_t* serverNotAfter); - + Status _parseAndValidateCertificate(const std::string& keyFile, + PasswordFetcher* keyPassword, + SSLX509Name* subjectName, + Date_t* serverNotAfter); + + Status _parseAndValidateCertificateFromBIO(UniqueBIO inBio, + PasswordFetcher* keyPassword, + StringData fileNameForLogging, + SSLX509Name* subjectName, + Date_t* serverNotAfter); + + Status _parseAndValidateCertificateFromMemory(StringData buffer, + PasswordFetcher* keyPassword, + SSLX509Name* subjectName, + Date_t* serverNotAfter); /* * Parse and return x509 object from the provided keyfile. * @param keyFile referencing the PEM file to be read. @@ -1554,11 +1566,13 @@ SSLManagerOpenSSL::SSLManagerOpenSSL(const SSLParams& params, bool isServer) } if (!clientPEM.empty()) { - if (!_parseAndValidateCertificate( - clientPEM, clientPassword, &_sslConfiguration.clientSubjectName, nullptr)) { - uasserted(16941, "ssl initialization problem"); - } + auto status = _parseAndValidateCertificate( + clientPEM, clientPassword, &_sslConfiguration.clientSubjectName, nullptr); + uassertStatusOKWithContext( + status, + str::stream() << "ssl client initialization problem for certificate: " << clientPEM); } + // SSL server specific initialization if (isServer) { if (!_initSynchronousSSLContext(&_serverContext, params, ConnectionDirection::kIncoming)) { @@ -1566,12 +1580,15 @@ SSLManagerOpenSSL::SSLManagerOpenSSL(const SSLParams& params, bool isServer) } SSLX509Name serverSubjectName; - if (!_parseAndValidateCertificate(params.sslPEMKeyFile, - &_serverPEMPassword, - &serverSubjectName, - &_sslConfiguration.serverCertificateExpirationDate)) { - uasserted(16942, "ssl initialization problem"); - } + auto status = + _parseAndValidateCertificate(params.sslPEMKeyFile, + &_serverPEMPassword, + &serverSubjectName, + &_sslConfiguration.serverCertificateExpirationDate); + uassertStatusOKWithContext(status, + str::stream() + << "ssl server initialization problem for certificate: " + << params.sslPEMKeyFile); uassertStatusOK(_sslConfiguration.setServerSubjectName(std::move(serverSubjectName))); @@ -2187,6 +2204,15 @@ Status SSLManagerOpenSSL::initSSLContext(SSL_CTX* context, str::stream() << "Can not set up transient ssl cluster certificate for " << transientParams.targetedClusterConnectionString); } + + auto status = _parseAndValidateCertificateFromMemory(transientParams.sslClusterPEMPayload, + &_clusterPEMPassword, + &_sslConfiguration.clientSubjectName, + nullptr); + if (!status.isOK()) { + return status.withContext("Could not validate transient certificate"); + } + } else if (direction == ConnectionDirection::kOutgoing && params.tlsWithholdClientCertificate) { // Do not send a client certificate if they have been suppressed. @@ -2294,60 +2320,97 @@ bool SSLManagerOpenSSL::_initSynchronousSSLContext(UniqueSSLContext* contextPtr, return true; } -bool SSLManagerOpenSSL::_parseAndValidateCertificate(const std::string& keyFile, - PasswordFetcher* keyPassword, - SSLX509Name* subjectName, - Date_t* serverCertificateExpirationDate) { - BIO* inBIO = BIO_new(BIO_s_file()); - if (inBIO == nullptr) { - LOGV2_ERROR(23243, - "Failed to allocate BIO object", - "error"_attr = getSSLErrorMessage(ERR_get_error())); - return false; +Status SSLManagerOpenSSL::_parseAndValidateCertificate(const std::string& keyFile, + PasswordFetcher* keyPassword, + SSLX509Name* subjectName, + Date_t* serverCertificateExpirationDate) { + UniqueBIO inBio(BIO_new(BIO_s_file())); + if (!inBio) { + return Status( + ErrorCodes::InvalidSSLConfiguration, + "Failed to allocate BIO object. error: {}"_format(getSSLErrorMessage(ERR_get_error()))); } - ON_BLOCK_EXIT([&] { BIO_free(inBIO); }); - if (BIO_read_filename(inBIO, keyFile.c_str()) <= 0) { - LOGV2_ERROR(23244, - "Cannot read key file when setting subject name", - "keyFile"_attr = keyFile, - "error"_attr = getSSLErrorMessage(ERR_get_error())); - return false; + if (BIO_read_filename(inBio.get(), keyFile.c_str()) <= 0) { + return Status(ErrorCodes::InvalidSSLConfiguration, + "Cannot read key file '{}' when setting subject name. error: {}"_format( + keyFile, getSSLErrorMessage(ERR_get_error()))); + } + + return _parseAndValidateCertificateFromBIO( + std::move(inBio), keyPassword, keyFile, subjectName, serverCertificateExpirationDate); +} + +Status SSLManagerOpenSSL::_parseAndValidateCertificateFromMemory( + StringData buffer, + PasswordFetcher* keyPassword, + SSLX509Name* subjectName, + Date_t* serverCertificateExpirationDate) { + logv2::DynamicAttributes errorAttrs; + +#if OPENSSL_VERSION_NUMBER <= 0x1000114fL + UniqueBIO inBio(BIO_new_mem_buf(const_cast<char*>(buffer.rawData()), buffer.size())); +#else + UniqueBIO inBio(BIO_new_mem_buf(buffer.rawData(), buffer.size())); +#endif + + if (!inBio) { + CaptureSSLErrorInAttrs capture(errorAttrs); + return Status(ErrorCodes::InvalidSSLConfiguration, + "Failed to allocate BIO object from in-memory payload. error: {}"_format( + getSSLErrorMessage(ERR_get_error()))); } + return _parseAndValidateCertificateFromBIO(std::move(inBio), + keyPassword, + "transient"_sd, + subjectName, + serverCertificateExpirationDate); +} + +Status SSLManagerOpenSSL::_parseAndValidateCertificateFromBIO( + UniqueBIO inBio, + PasswordFetcher* keyPassword, + StringData fileNameForLogging, + SSLX509Name* subjectName, + Date_t* serverCertificateExpirationDate) { X509* x509 = PEM_read_bio_X509( - inBIO, nullptr, &SSLManagerOpenSSL::password_cb, static_cast<void*>(&keyPassword)); + inBio.get(), nullptr, &SSLManagerOpenSSL::password_cb, static_cast<void*>(&keyPassword)); if (x509 == nullptr) { - LOGV2_ERROR(23245, - "Cannot retrieve certificate from keyfile", - "keyFile"_attr = keyFile, - "error"_attr = getSSLErrorMessage(ERR_get_error())); - return false; + return Status( + ErrorCodes::InvalidSSLConfiguration, + "Cannot retrieve certificate from keyfile '{}' when setting subject name. error: {}"_format( + fileNameForLogging, getSSLErrorMessage(ERR_get_error()))); } ON_BLOCK_EXIT([&] { X509_free(x509); }); *subjectName = getCertificateSubjectX509Name(x509); - if (serverCertificateExpirationDate != nullptr) { - auto notBeforeMillis = convertASN1ToMillis(X509_get_notBefore(x509)); - if (notBeforeMillis == Date_t()) { - LOGV2_ERROR(23873, "date conversion failed"); - return false; - } - auto notAfterMillis = convertASN1ToMillis(X509_get_notAfter(x509)); - if (notAfterMillis == Date_t()) { - LOGV2_ERROR(23874, "date conversion failed"); - return false; - } + auto notBeforeMillis = convertASN1ToMillis(X509_get_notBefore(x509)); + if (notBeforeMillis == Date_t()) { + return Status(ErrorCodes::InvalidSSLConfiguration, + "notBefore certificate date conversion failed"); + } - if ((notBeforeMillis > Date_t::now()) || (Date_t::now() > notAfterMillis)) { - LOGV2_FATAL_NOTRACE(28652, "The provided SSL certificate is expired or not yet valid."); - } + auto notAfterMillis = convertASN1ToMillis(X509_get_notAfter(x509)); + if (notAfterMillis == Date_t()) { + return Status(ErrorCodes::InvalidSSLConfiguration, + "notAfter certificate date conversion failed"); + } + auto now = Date_t::now(); + if ((notBeforeMillis > now) || (now > notAfterMillis)) { + return Status( + ErrorCodes::InvalidSSLConfiguration, + "The provided SSL certificate is expired or not yet valid. notBefore {}, notAfter {}"_format( + notBeforeMillis.toString(), notAfterMillis.toString())); + } + + if (serverCertificateExpirationDate != nullptr) { *serverCertificateExpirationDate = notAfterMillis; } - return true; + return Status::OK(); } // static diff --git a/src/mongo/util/net/ssl_manager_test.cpp b/src/mongo/util/net/ssl_manager_test.cpp index 1fbd42bde40..60349b4b2c2 100644 --- a/src/mongo/util/net/ssl_manager_test.cpp +++ b/src/mongo/util/net/ssl_manager_test.cpp @@ -109,7 +109,7 @@ private: transport::TransportLayer* _transport = nullptr; }; -std::string LoadFile(const std::string& name) { +std::string loadFile(const std::string& name) { std::ifstream input(name); std::string str((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>()); return str; @@ -521,7 +521,7 @@ TEST(SSLManager, InitContextFromFileShouldFail) { // TODO SERVER-52858: there is no exception on Mac & Windows. ASSERT_THROWS_CODE([¶ms] { SSLManagerInterface::create(params, true /* isSSLServer */); }(), DBException, - 16942); + ErrorCodes::InvalidSSLConfiguration); #endif } @@ -571,7 +571,7 @@ TEST(SSLManager, InitContextFromMemory) { params.sslCAFile = "jstests/libs/ca.pem"; TransientSSLParams transientParams; - transientParams.sslClusterPEMPayload = LoadFile("jstests/libs/client.pem"); + transientParams.sslClusterPEMPayload = loadFile("jstests/libs/client.pem"); std::shared_ptr<SSLManagerInterface> manager = SSLManagerInterface::create(params, false /* isSSLServer */); @@ -590,7 +590,7 @@ TEST(SSLManager, InitServerSideContextFromMemory) { params.sslCAFile = "jstests/libs/ca.pem"; TransientSSLParams transientParams; - transientParams.sslClusterPEMPayload = LoadFile("jstests/libs/client.pem"); + transientParams.sslClusterPEMPayload = loadFile("jstests/libs/client.pem"); std::shared_ptr<SSLManagerInterface> manager = SSLManagerInterface::create(params, true /* isSSLServer */); @@ -622,7 +622,7 @@ TEST(SSLManager, TransientSSLParams) { transport::TransportLayerASIO tla(options, &sepu); TransientSSLParams transientSSLParams; - transientSSLParams.sslClusterPEMPayload = LoadFile("jstests/libs/client.pem"); + transientSSLParams.sslClusterPEMPayload = loadFile("jstests/libs/client.pem"); transientSSLParams.targetedClusterConnectionString = ConnectionString::forLocal(); auto result = tla.createTransientSSLContext(transientSSLParams, manager.get()); diff --git a/src/mongo/util/net/ssl_parameters_auth.cpp b/src/mongo/util/net/ssl_parameters_auth.cpp index 612c2bc70cc..980c93df358 100644 --- a/src/mongo/util/net/ssl_parameters_auth.cpp +++ b/src/mongo/util/net/ssl_parameters_auth.cpp @@ -99,9 +99,7 @@ Status ClusterAuthModeServerParameter::setFromString(const std::string& strMode) "connections"}; } serverGlobalParams.clusterAuthMode.store(mode); - auth::setInternalUserAuthParams(BSON(saslCommandMechanismFieldName - << "MONGODB-X509" << saslCommandUserDBFieldName - << "$external")); + auth::setInternalUserAuthParams(auth::createInternalX509AuthDocument()); } else if ((mode == ServerGlobalParams::ClusterAuthMode_x509) && (oldMode == ServerGlobalParams::ClusterAuthMode_sendX509)) { serverGlobalParams.clusterAuthMode.store(mode); |