diff options
Diffstat (limited to 'src/mongo/util')
| -rw-r--r-- | src/mongo/util/concurrency/notification.h | 10 | ||||
| -rw-r--r-- | src/mongo/util/exception_filter_win32.cpp | 10 | ||||
| -rw-r--r-- | src/mongo/util/hex.cpp | 4 | ||||
| -rw-r--r-- | src/mongo/util/net/SConscript | 11 | ||||
| -rw-r--r-- | src/mongo/util/net/ssl_manager.cpp | 309 | ||||
| -rw-r--r-- | src/mongo/util/net/ssl_manager.h | 26 | ||||
| -rw-r--r-- | src/mongo/util/net/ssl_manager_status.cpp | 70 | ||||
| -rw-r--r-- | src/mongo/util/net/ssl_options.cpp | 11 | ||||
| -rw-r--r-- | src/mongo/util/net/ssl_options.h | 4 | ||||
| -rw-r--r-- | src/mongo/util/net/ssl_types.h | 67 |
10 files changed, 449 insertions, 73 deletions
diff --git a/src/mongo/util/concurrency/notification.h b/src/mongo/util/concurrency/notification.h index d24fc84e5f9..25f0c65f187 100644 --- a/src/mongo/util/concurrency/notification.h +++ b/src/mongo/util/concurrency/notification.h @@ -102,12 +102,10 @@ public: * set (in which case a subsequent call to get is guaranteed to not block) or false otherwise. * If the wait is interrupted, throws an exception. */ - bool waitFor(OperationContext* txn, Microseconds waitTimeout) { - const auto waitDeadline = Date_t::now() + waitTimeout; - + bool waitFor(OperationContext* txn, Milliseconds waitTimeout) { stdx::unique_lock<stdx::mutex> lock(_mutex); - return _condVar.wait_until( - lock, waitDeadline.toSystemTimePoint(), [&]() { return !!_value; }); + return txn->waitForConditionOrInterruptFor( + _condVar, lock, waitTimeout, [&]() { return !!_value; }); } private: @@ -137,7 +135,7 @@ public: _notification.set(true); } - bool waitFor(OperationContext* txn, Microseconds waitTimeout) { + bool waitFor(OperationContext* txn, Milliseconds waitTimeout) { return _notification.waitFor(txn, waitTimeout); } diff --git a/src/mongo/util/exception_filter_win32.cpp b/src/mongo/util/exception_filter_win32.cpp index db0e3e9bb56..30d904a88cf 100644 --- a/src/mongo/util/exception_filter_win32.cpp +++ b/src/mongo/util/exception_filter_win32.cpp @@ -129,8 +129,8 @@ LONG WINAPI exceptionFilter(struct _EXCEPTION_POINTERS* excPointers) { sizeof(addressString), "0x%p", excPointers->ExceptionRecord->ExceptionAddress); - log() << "*** unhandled exception " << exceptionString << " at " << addressString - << ", terminating"; + severe() << "*** unhandled exception " << exceptionString << " at " << addressString + << ", terminating"; if (excPointers->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { ULONG acType = excPointers->ExceptionRecord->ExceptionInformation[0]; const char* acTypeString; @@ -152,10 +152,10 @@ LONG WINAPI exceptionFilter(struct _EXCEPTION_POINTERS* excPointers) { sizeof(addressString), " 0x%p", excPointers->ExceptionRecord->ExceptionInformation[1]); - log() << "*** access violation was a " << acTypeString << addressString; + severe() << "*** access violation was a " << acTypeString << addressString; } - log() << "*** stack trace for unhandled exception:"; + severe() << "*** stack trace for unhandled exception:"; // Create a copy of context record because printWindowsStackTrace will mutate it. CONTEXT contextCopy(*(excPointers->ContextRecord)); @@ -166,7 +166,7 @@ LONG WINAPI exceptionFilter(struct _EXCEPTION_POINTERS* excPointers) { // Don't go through normal shutdown procedure. It may make things worse. // Do not go through _exit or ExitProcess(), terminate immediately - log() << "*** immediate exit due to unhandled exception"; + severe() << "*** immediate exit due to unhandled exception"; TerminateProcess(GetCurrentProcess(), EXIT_ABRUPT); // We won't reach here diff --git a/src/mongo/util/hex.cpp b/src/mongo/util/hex.cpp index d589d751b45..4ee2967ddff 100644 --- a/src/mongo/util/hex.cpp +++ b/src/mongo/util/hex.cpp @@ -62,6 +62,10 @@ std::string integerToHexDef(T inInt) { } template <> +std::string integerToHex<char>(char val) { + return integerToHexDef(val); +} +template <> std::string integerToHex<int>(int val) { return integerToHexDef(val); } diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript index 59b546fad65..d2647622471 100644 --- a/src/mongo/util/net/SConscript +++ b/src/mongo/util/net/SConscript @@ -64,6 +64,17 @@ networkEnv.Library( ) env.Library( + target='ssl_manager_status', + source=[ + "ssl_manager_status.cpp", + ], + LIBDEPS=[ + 'network', + '$BUILD_DIR/mongo/db/commands/core', + ], +) + +env.Library( target='message_port_mock', source=[ "message_port_mock.cpp", diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp index 1b120a3be83..dd6e73c3e08 100644 --- a/src/mongo/util/net/ssl_manager.cpp +++ b/src/mongo/util/net/ssl_manager.cpp @@ -51,6 +51,7 @@ #include "mongo/util/concurrency/threadlocal.h" #include "mongo/util/debug_util.h" #include "mongo/util/exit.h" +#include "mongo/util/hex.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/net/message.h" @@ -82,6 +83,8 @@ const SSLParams& getSSLGlobalParams() { return sslGlobalParams; } +namespace { + /** * Configurable via --setParameter disableNonSSLConnectionLogging=true. If false (default) * if the sslMode is set to preferSSL, we will log connections that are not using SSL. @@ -92,6 +95,18 @@ ExportedServerParameter<bool, ServerParameterType::kStartupOnly> "disableNonSSLConnectionLogging", &sslGlobalParams.disableNonSSLConnectionLogging); +ExportedServerParameter<bool, ServerParameterType::kStartupOnly> + suppressNoTLSPeerCertificateWarning(ServerParameterSet::getGlobal(), + "suppressNoTLSPeerCertificateWarning", + &sslGlobalParams.suppressNoTLSPeerCertificateWarning); + +ExportedServerParameter<bool, ServerParameterType::kStartupOnly> sslWithholdClientCertificate( + ServerParameterSet::getGlobal(), + "sslWithholdClientCertificate", + &sslGlobalParams.tlsWithholdClientCertificate); + +} // namespace + class OpenSSLCipherConfigParameter : public ExportedServerParameter<std::string, ServerParameterType::kStartupOnly> { public: @@ -159,6 +174,9 @@ IMPLEMENT_ASN1_ENCODE_FUNCTIONS_const_fname(ASN1_SEQUENCE_ANY, ASN1_SET_ANY, ASN const STACK_OF(X509_EXTENSION) * X509_get0_extensions(const X509* peerCert) { return peerCert->cert_info->extensions; } +inline int X509_NAME_ENTRY_set(const X509_NAME_ENTRY* ne) { + return ne->set; +} #endif /** @@ -316,6 +334,7 @@ private: bool _weakValidation; bool _allowInvalidCertificates; bool _allowInvalidHostnames; + bool _suppressNoCertificateWarning; SSLConfiguration _sslConfiguration; /** @@ -356,7 +375,7 @@ private: */ bool _parseAndValidateCertificate(const std::string& keyFile, const std::string& keyPassword, - std::string* subjectName, + SSLX509Name* subjectName, Date_t* serverNotAfter); @@ -418,12 +437,18 @@ void setupFIPS() { fassertFailedNoTrace(17089); #endif } + +TLSVersionCounts tlsVersionCounts; + } // namespace +TLSVersionCounts& TLSVersionCounts::get() { + return tlsVersionCounts; +} + // Global variable indicating if this is a server or a client instance bool isSSLServer = false; - MONGO_INITIALIZER(SetupOpenSSL)(InitializerContext*) { SSL_library_init(); SSL_load_error_strings(); @@ -463,23 +488,42 @@ SSLManagerInterface* getSSLManager() { return NULL; } -std::string getCertificateSubjectName(X509* cert) { - std::string result; +SSLX509Name getCertificateSubjectX509Name(X509* cert) { + std::vector<std::vector<SSLX509Name::Entry>> entries; + + auto name = X509_get_subject_name(cert); + int count = X509_NAME_entry_count(name); + int prevSet = -1; + std::vector<SSLX509Name::Entry> rdn; + for (int i = count - 1; i >= 0; --i) { + auto* entry = X509_NAME_get_entry(name, i); + + const auto currentSet = X509_NAME_ENTRY_set(entry); + if (currentSet != prevSet) { + if (!rdn.empty()) { + entries.push_back(std::move(rdn)); + rdn = std::vector<SSLX509Name::Entry>(); + } + prevSet = currentSet; + } - BIO* out = BIO_new(BIO_s_mem()); - uassert(16884, "unable to allocate BIO memory", NULL != out); - ON_BLOCK_EXIT(BIO_free, out); + char buffer[128]; + // OBJ_obj2txt can only fail if we pass a nullptr from get_object, + // or if OpenSSL's BN library falls over. + // In either case, just panic. + uassert(ErrorCodes::InvalidSSLConfiguration, + "Unable to parse certiciate subject name", + OBJ_obj2txt(buffer, sizeof(buffer), X509_NAME_ENTRY_get_object(entry), 1) > 0); - if (X509_NAME_print_ex(out, X509_get_subject_name(cert), 0, XN_FLAG_RFC2253) >= 0) { - if (BIO_number_written(out) > 0) { - result.resize(BIO_number_written(out)); - BIO_read(out, &result[0], result.size()); - } - } else { - log() << "failed to convert subject name to RFC2253 format"; + const auto* str = X509_NAME_ENTRY_get_data(entry); + rdn.emplace_back( + buffer, str->type, std::string(reinterpret_cast<const char*>(str->data), str->length)); + } + if (!rdn.empty()) { + entries.push_back(std::move(rdn)); } - return result; + return SSLX509Name(std::move(entries)); } SSLConnection::SSLConnection(SSL_CTX* context, Socket* sock, const char* initialBytes, int len) @@ -512,6 +556,96 @@ SSLConnection::~SSLConnection() { } namespace { +std::string x509OidToShortName(const std::string& name) { + const auto nid = OBJ_txt2nid(name.c_str()); + if (nid == 0) { + return name; + } + const auto* sn = OBJ_nid2sn(nid); + if (!sn) { + return name; + } + return sn; +} + +// Characters that need to be escaped in RFC 2253 +const std::array<char, 7> rfc2253EscapeChars = {',', '+', '"', '\\', '<', '>', ';'}; + +// See section "2.4 Converting an AttributeValue from ASN.1 to a String" in RFC 2243 +std::string escapeRfc2253(StringData str) { + std::string ret; + + if (str.size() > 0) { + size_t pos = 0; + + // a space or "#" character occurring at the beginning of the string + if (str[0] == ' ') { + ret = "\\ "; + pos = 1; + } else if (str[0] == '#') { + ret = "\\#"; + pos = 1; + } + + while (pos < str.size()) { + if (static_cast<signed char>(str[pos]) < 0) { + ret += '\\'; + ret += integerToHex(str[pos]); + } else { + if (std::find(rfc2253EscapeChars.cbegin(), rfc2253EscapeChars.cend(), str[pos]) != + rfc2253EscapeChars.cend()) { + ret += '\\'; + } + + ret += str[pos]; + } + ++pos; + } + + // a space character occurring at the end of the string + if (ret.size() > 2 && ret[ret.size() - 1] == ' ') { + ret[ret.size() - 1] = '\\'; + ret += ' '; + } + } + + return ret; +} + +} // namespace + +StatusWith<std::string> SSLX509Name::getOID(StringData oid) const { + for (const auto& rdn : _entries) { + for (const auto& entry : rdn) { + if (entry.oid == oid) { + return entry.value; + } + } + } + return {ErrorCodes::KeyNotFound, "OID does not exist"}; +} + +StringBuilder& operator<<(StringBuilder& os, const SSLX509Name& name) { + std::string comma; + for (const auto& rdn : name._entries) { + std::string plus; + os << comma; + for (const auto& entry : rdn) { + os << plus << x509OidToShortName(entry.oid) << "=" << escapeRfc2253(entry.value); + plus = "+"; + } + comma = ","; + } + return os; +} + +std::string SSLX509Name::toString() const { + StringBuilder os; + os << *this; + return os.str(); +} + +namespace { void canonicalizeClusterDN(std::vector<std::string>* dn) { // remove all RDNs we don't care about for (size_t i = 0; i < dn->size(); i++) { @@ -526,30 +660,62 @@ void canonicalizeClusterDN(std::vector<std::string>* dn) { } std::stable_sort(dn->begin(), dn->end()); } + +constexpr StringData kOID_DC = "0.9.2342.19200300.100.1.25"_sd; +constexpr StringData kOID_O = "2.5.4.10"_sd; +constexpr StringData kOID_OU = "2.5.4.11"_sd; + +std::vector<SSLX509Name::Entry> canonicalizeClusterDN( + const std::vector<std::vector<SSLX509Name::Entry>>& entries) { + std::vector<SSLX509Name::Entry> ret; + + for (const auto& rdn : entries) { + for (const auto& entry : rdn) { + if ((entry.oid != kOID_DC) && (entry.oid != kOID_O) && (entry.oid != kOID_OU)) { + continue; + } + ret.push_back(entry); + } + } + std::stable_sort(ret.begin(), ret.end()); + return ret; +} +} // namespace + +/** + * The behavior of isClusterMember() is subtly different when passed + * an SSLX509Name versus a StringData. + * + * The SSLX509Name version (immediately below) compares distinguished + * names in their raw, unescaped forms and provides a more reliable match. + * + * The StringData version attempts to do a simplified string compare + * with the serialized version of the server subject name. + * + * Because escaping is not checked in the StringData version, + * some not-strictly matching RDNs will appear to share O/OU/DC with the + * server subject name. Therefore, that variant should be called with care. + */ +bool SSLConfiguration::isClusterMember(const SSLX509Name& subject) const { + auto client = canonicalizeClusterDN(subject._entries); + auto server = canonicalizeClusterDN(serverSubjectName._entries); + + return !client.empty() && (client == server); } bool SSLConfiguration::isClusterMember(StringData subjectName) const { std::vector<std::string> clientRDN = StringSplitter::split(subjectName.toString(), ","); - std::vector<std::string> serverRDN = StringSplitter::split(serverSubjectName, ","); + std::vector<std::string> serverRDN = StringSplitter::split(serverSubjectName.toString(), ","); canonicalizeClusterDN(&clientRDN); canonicalizeClusterDN(&serverRDN); - if (clientRDN.size() == 0 || clientRDN.size() != serverRDN.size()) { - return false; - } - - for (size_t i = 0; i < serverRDN.size(); i++) { - if (clientRDN[i] != serverRDN[i]) { - return false; - } - } - return true; + return !clientRDN.empty() && (clientRDN == serverRDN); } BSONObj SSLConfiguration::getServerStatusBSON() const { BSONObjBuilder security; - security.append("SSLServerSubjectName", serverSubjectName); + security.append("SSLServerSubjectName", serverSubjectName.toString()); security.appendBool("SSLServerHasCertificateAuthority", hasCA); security.appendDate("SSLServerCertificateExpirationDate", serverCertificateExpirationDate); return security.obj(); @@ -562,7 +728,8 @@ SSLManager::SSLManager(const SSLParams& params, bool isServer) _clientContext(nullptr, _free_ssl_context), _weakValidation(params.sslWeakCertificateValidation), _allowInvalidCertificates(params.sslAllowInvalidCertificates), - _allowInvalidHostnames(params.sslAllowInvalidHostnames) { + _allowInvalidHostnames(params.sslAllowInvalidHostnames), + _suppressNoCertificateWarning(params.suppressNoTLSPeerCertificateWarning) { if (!_initSynchronousSSLContext(&_clientContext, params, ConnectionDirection::kOutgoing)) { uasserted(16768, "ssl initialization problem"); } @@ -716,23 +883,33 @@ Status SSLManager::initSSLContext(SSL_CTX* context, << getSSLErrorMessage(ERR_get_error())); } - if (direction == ConnectionDirection::kOutgoing && !params.sslClusterFile.empty()) { + if (direction == ConnectionDirection::kOutgoing && params.tlsWithholdClientCertificate) { + // Do not send a client certificate if they have been suppressed. + + } else if (direction == ConnectionDirection::kOutgoing && !params.sslClusterFile.empty()) { + // Use the configured clusterFile as our client certificate. ::EVP_set_pw_prompt("Enter cluster certificate passphrase"); if (!_setupPEM(context, params.sslClusterFile, params.sslClusterPassword)) { return Status(ErrorCodes::InvalidSSLConfiguration, "Can not set up ssl clusterFile."); } + } else if (!params.sslPEMKeyFile.empty()) { - // Use the pemfile for everything else + // Use the base pemKeyFile for any other outgoing connections, + // as well as all incoming connections. ::EVP_set_pw_prompt("Enter PEM passphrase"); if (!_setupPEM(context, params.sslPEMKeyFile, params.sslPEMKeyPassword)) { return Status(ErrorCodes::InvalidSSLConfiguration, "Can not set up PEM key file."); } } - const auto status = - params.sslCAFile.empty() ? _setupSystemCA(context) : _setupCA(context, params.sslCAFile); - if (!status.isOK()) + std::string cafile = params.sslCAFile; + if (direction == ConnectionDirection::kIncoming && !params.sslClusterCAFile.empty()) { + cafile = params.sslClusterCAFile; + } + const auto status = cafile.empty() ? _setupSystemCA(context) : _setupCA(context, cafile); + if (!status.isOK()) { return status; + } if (!params.sslCRLFile.empty()) { if (!_setupCRL(context, params.sslCRLFile)) { @@ -795,7 +972,7 @@ unsigned long long SSLManager::_convertASN1ToMillis(ASN1_TIME* asn1time) { bool SSLManager::_parseAndValidateCertificate(const std::string& keyFile, const std::string& keyPassword, - std::string* subjectName, + SSLX509Name* subjectName, Date_t* serverCertificateExpirationDate) { BIO* inBIO = BIO_new(BIO_s_file()); if (inBIO == NULL) { @@ -822,7 +999,7 @@ bool SSLManager::_parseAndValidateCertificate(const std::string& keyFile, } ON_BLOCK_EXIT(X509_free, x509); - *subjectName = getCertificateSubjectName(x509); + *subjectName = getCertificateSubjectX509Name(x509); if (serverCertificateExpirationDate != NULL) { unsigned long long notBeforeMillis = _convertASN1ToMillis(X509_get_notBefore(x509)); if (notBeforeMillis == 0) { @@ -1210,8 +1387,36 @@ bool SSLManager::_hostNameMatch(const char* nameToMatch, const char* certHostNam } } +void recordTLSVersion(const SSL* conn) { + int protocol = SSL_version(conn); + + auto& counts = mongo::TLSVersionCounts::get(); + switch (protocol) { + case TLS1_VERSION: + counts.tls10.addAndFetch(1); + break; + case TLS1_1_VERSION: + counts.tls11.addAndFetch(1); + break; + case TLS1_2_VERSION: + counts.tls12.addAndFetch(1); + break; +#ifdef TLS1_3_VERSION + case TLS1_3_VERSION: + counts.tls13.addAndFetch(1); + break; +#endif + default: + // Do nothing + break; + } +} + StatusWith<boost::optional<SSLPeerInfo>> SSLManager::parseAndValidatePeerCertificate( SSL* conn, const std::string& remoteHost) { + + recordTLSVersion(conn); + if (!_sslConfiguration.hasCA && isSSLServer) return {boost::none}; @@ -1219,7 +1424,11 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManager::parseAndValidatePeerCertifi if (NULL == peerCert) { // no certificate presented by peer if (_weakValidation) { - warning() << "no SSL certificate provided by peer"; + // do not give warning if certificate warnings are suppressed + if (!_suppressNoCertificateWarning) { + warning() << "no SSL certificate provided by peer"; + } + return {boost::none}; } else { auto msg = "no SSL certificate provided by peer; connection rejected"; error() << msg; @@ -1246,8 +1455,8 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManager::parseAndValidatePeerCertifi } // TODO: check optional cipher restriction, using cert. - std::string peerSubjectName = getCertificateSubjectName(peerCert); - LOG(2) << "Accepted TLS connection from peer: " << peerSubjectName; + auto peerSubject = getCertificateSubjectX509Name(peerCert); + LOG(2) << "Accepted TLS connection from peer: " << peerSubject; StatusWith<stdx::unordered_set<RoleName>> swPeerCertificateRoles = _parsePeerRoles(peerCert); if (!swPeerCertificateRoles.isOK()) { @@ -1258,7 +1467,7 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManager::parseAndValidatePeerCertifi // perform hostname validation of the remote server if (remoteHost.empty()) { return boost::make_optional( - SSLPeerInfo(peerSubjectName, std::move(swPeerCertificateRoles.getValue()))); + SSLPeerInfo(peerSubject, std::move(swPeerCertificateRoles.getValue()))); } // Try to match using the Subject Alternate Name, if it exists. @@ -1288,19 +1497,19 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManager::parseAndValidatePeerCertifi } } sk_GENERAL_NAME_pop_free(sanNames, GENERAL_NAME_free); - } else if (peerSubjectName.find("CN=") != std::string::npos) { + } else { // If Subject Alternate Name (SAN) doesn't exist and Common Name (CN) does, // check Common Name. - int cnBegin = peerSubjectName.find("CN=") + 3; - int cnEnd = peerSubjectName.find(",", cnBegin); - std::string commonName = peerSubjectName.substr(cnBegin, cnEnd - cnBegin); - - if (_hostNameMatch(remoteHost.c_str(), commonName.c_str())) { - cnMatch = true; + auto swCN = peerSubject.getOID(kOID_CommonName); + if (swCN.isOK()) { + auto commonName = std::move(swCN.getValue()); + if (_hostNameMatch(remoteHost.c_str(), commonName.c_str())) { + cnMatch = true; + } + certificateNames << "CN: " << commonName; + } else { + certificateNames << "No Common Name (CN) or Subject Alternate Names (SAN) found"; } - certificateNames << "CN: " << commonName; - } else { - certificateNames << "No Common Name (CN) or Subject Alternate Names (SAN) found"; } if (!sanMatch && !cnMatch) { @@ -1316,7 +1525,7 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManager::parseAndValidatePeerCertifi } } - return boost::make_optional(SSLPeerInfo(peerSubjectName, stdx::unordered_set<RoleName>())); + return boost::make_optional(SSLPeerInfo(peerSubject, stdx::unordered_set<RoleName>())); } diff --git a/src/mongo/util/net/ssl_manager.h b/src/mongo/util/net/ssl_manager.h index bbdafdabff1..0058f7223b2 100644 --- a/src/mongo/util/net/ssl_manager.h +++ b/src/mongo/util/net/ssl_manager.h @@ -38,6 +38,8 @@ #include "mongo/base/disallow_copying.h" #include "mongo/base/string_data.h" #include "mongo/bson/bsonobj.h" +#include "mongo/db/service_context.h" +#include "mongo/platform/atomic_word.h" #include "mongo/util/decorable.h" #include "mongo/util/net/sock.h" #include "mongo/util/net/ssl_types.h" @@ -72,18 +74,11 @@ public: }; struct SSLConfiguration { - SSLConfiguration() : serverSubjectName(""), clientSubjectName("") {} - SSLConfiguration(const std::string& serverSubjectName, - const std::string& clientSubjectName, - const Date_t& serverCertificateExpirationDate) - : serverSubjectName(serverSubjectName), - clientSubjectName(clientSubjectName), - serverCertificateExpirationDate(serverCertificateExpirationDate) {} - bool isClusterMember(StringData subjectName) const; + bool isClusterMember(const SSLX509Name& subjectName) const; BSONObj getServerStatusBSON() const; - std::string serverSubjectName; - std::string clientSubjectName; + SSLX509Name serverSubjectName; + SSLX509Name clientSubjectName; Date_t serverCertificateExpirationDate; bool hasCA = false; }; @@ -106,6 +101,17 @@ const ASN1OID mongodbRolesOID("1.3.6.1.4.1.34601.2.1.1", "MongoRoles", "Sequence of MongoDB Database Roles"); +/** + * Counts of negogtiated version used by TLS connections. + */ +struct TLSVersionCounts { + AtomicInt64 tls10; + AtomicInt64 tls11; + AtomicInt64 tls12; + + static TLSVersionCounts& get(); +}; + class SSLManagerInterface : public Decorable<SSLManagerInterface> { public: static std::unique_ptr<SSLManagerInterface> create(const SSLParams& params, bool isServer); diff --git a/src/mongo/util/net/ssl_manager_status.cpp b/src/mongo/util/net/ssl_manager_status.cpp new file mode 100644 index 00000000000..559d06d4dd2 --- /dev/null +++ b/src/mongo/util/net/ssl_manager_status.cpp @@ -0,0 +1,70 @@ +/** + * Copyright (C) 2018 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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/platform/basic.h" + +#include "mongo/util/net/ssl_manager.h" + +#include "mongo/config.h" +#include "mongo/db/commands/server_status.h" + + +#ifdef MONGO_CONFIG_SSL + +namespace mongo { +namespace { + +/** + * Status section of which tls versions connected to MongoDB and completed an SSL handshake. + * Note: Clients are only not counted if they try to connect to the server with a unsupported TLS + * version. They are still counted if the server rejects them for certificate issues in + * parseAndValidatePeerCertificate. + */ +class TLSVersionSatus : public ServerStatusSection { +public: + TLSVersionSatus() : ServerStatusSection("transportSecurity") {} + + bool includeByDefault() const final { + return true; + } + + BSONObj generateSection(OperationContext* txn, const BSONElement& configElement) const final { + auto& counts = TLSVersionCounts::get(); + + BSONObjBuilder builder; + builder.append("1.0", counts.tls10.load()); + builder.append("1.1", counts.tls11.load()); + builder.append("1.2", counts.tls12.load()); + return builder.obj(); + } +} tlsVersionStatus; + +} // namespace +} // namespace mongo + +#endif diff --git a/src/mongo/util/net/ssl_options.cpp b/src/mongo/util/net/ssl_options.cpp index b9785d1b83f..0d1d3504ae7 100644 --- a/src/mongo/util/net/ssl_options.cpp +++ b/src/mongo/util/net/ssl_options.cpp @@ -81,6 +81,11 @@ Status addSSLServerOptions(moe::OptionSection* options) { options->addOptionChaining( "net.ssl.CAFile", "sslCAFile", moe::String, "Certificate Authority file for SSL"); + options->addOptionChaining("net.ssl.clusterCAFile", + "sslClusterCAFile", + moe::String, + "CA used for verifying remotes during outbound connections"); + options->addOptionChaining( "net.ssl.CRLFile", "sslCRLFile", moe::String, "Certificate Revocation List file for SSL"); @@ -327,6 +332,12 @@ Status storeSSLServerOptions(const moe::Environment& params) { .generic_string(); } + if (params.count("net.ssl.clusterCAFile")) { + sslGlobalParams.sslClusterCAFile = + boost::filesystem::absolute(params["net.ssl.clusterCAFile"].as<std::string>()) + .generic_string(); + } + if (params.count("net.ssl.CRLFile")) { sslGlobalParams.sslCRLFile = boost::filesystem::absolute(params["net.ssl.CRLFile"].as<std::string>()) diff --git a/src/mongo/util/net/ssl_options.h b/src/mongo/util/net/ssl_options.h index aef2860093b..02a82108529 100644 --- a/src/mongo/util/net/ssl_options.h +++ b/src/mongo/util/net/ssl_options.h @@ -50,6 +50,7 @@ struct SSLParams { std::string sslClusterFile; // --sslInternalKeyFile std::string sslClusterPassword; // --sslInternalKeyPassword std::string sslCAFile; // --sslCAFile + std::string sslClusterCAFile; // --sslClusterCAFile std::string sslCRLFile; // --sslCRLFile std::string sslCipherConfig; // --sslCipherConfig std::vector<Protocols> sslDisabledProtocols; // --sslDisabledProtocols @@ -59,6 +60,9 @@ struct SSLParams { bool sslAllowInvalidHostnames = false; // --sslAllowInvalidHostnames bool disableNonSSLConnectionLogging = false; // --setParameter disableNonSSLConnectionLogging=true + bool suppressNoTLSPeerCertificateWarning = + false; // --setParameter suppressNoTLSPeerCertificateWarning + bool tlsWithholdClientCertificate = false; // --setParameter tlsWithholdClientCertificate SSLParams() { sslMode.store(SSLMode_disabled); diff --git a/src/mongo/util/net/ssl_types.h b/src/mongo/util/net/ssl_types.h index fc8f600625c..f7c2fa33050 100644 --- a/src/mongo/util/net/ssl_types.h +++ b/src/mongo/util/net/ssl_types.h @@ -29,21 +29,84 @@ #include <string> +#include "mongo/bson/util/builder.h" #include "mongo/db/auth/role_name.h" #include "mongo/stdx/unordered_set.h" namespace mongo { +constexpr StringData kOID_CommonName = "2.5.4.3"_sd; + +/** + * Represents a structed X509 certificate subject name. + * For example: C=US,O=MongoDB,OU=KernelTeam,CN=server + * would be held as a four element vector of Entries. + * The first entry of which yould be broken down something like: + * {{"2.5.4.6", 19, "US"}}. + * Note that _entries is a vector of vectors to accomodate + * multi-value RDNs. + */ +class SSLX509Name { +public: + struct Entry { + Entry(std::string oid, int type, std::string value) + : oid(std::move(oid)), type(type), value(std::move(value)) {} + std::string oid; // e.g. "2.5.4.8" (ST) + int type; // e.g. 19 (PRINTABLESTRING) + std::string value; + std::tuple<const std::string&, const int&, const std::string&> equalityLens() const { + return std::tie(oid, type, value); + } + }; + + SSLX509Name() = default; + explicit SSLX509Name(std::vector<std::vector<Entry>> entries) : _entries(std::move(entries)) {} + + /** + * Retreive the first instance of the value for a given OID in this name. + * Returns ErrorCodes::KeyNotFound if the OID does not exist. + */ + StatusWith<std::string> getOID(StringData oid) const; + + bool empty() const { + return std::all_of(_entries.cbegin(), _entries.cend(), [](const std::vector<Entry>& e) { + return e.empty(); + }); + } + + friend StringBuilder& operator<<(StringBuilder&, const SSLX509Name&); + std::string toString() const; + + friend bool operator==(const SSLX509Name& lhs, const SSLX509Name& rhs) { + return lhs._entries == rhs._entries; + } + friend bool operator!=(const SSLX509Name& lhs, const SSLX509Name& rhs) { + return !(lhs._entries == rhs._entries); + } + +private: + friend struct SSLConfiguration; + std::vector<std::vector<Entry>> _entries; +}; + +std::ostream& operator<<(std::ostream&, const SSLX509Name&); +inline bool operator==(const SSLX509Name::Entry& lhs, const SSLX509Name::Entry& rhs) { + return lhs.equalityLens() == rhs.equalityLens(); +} +inline bool operator<(const SSLX509Name::Entry& lhs, const SSLX509Name::Entry& rhs) { + return lhs.equalityLens() < rhs.equalityLens(); +} + /** * Contains information extracted from the peer certificate which is consumed by subsystems * outside of the networking stack. */ struct SSLPeerInfo { - SSLPeerInfo(std::string subjectName, stdx::unordered_set<RoleName> roles) + SSLPeerInfo(SSLX509Name subjectName, stdx::unordered_set<RoleName> roles) : subjectName(std::move(subjectName)), roles(std::move(roles)) {} SSLPeerInfo() = default; - std::string subjectName; + SSLX509Name subjectName; stdx::unordered_set<RoleName> roles; }; |
