summaryrefslogtreecommitdiff
path: root/src/mongo/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/util')
-rw-r--r--src/mongo/util/concurrency/notification.h10
-rw-r--r--src/mongo/util/exception_filter_win32.cpp10
-rw-r--r--src/mongo/util/hex.cpp4
-rw-r--r--src/mongo/util/net/SConscript11
-rw-r--r--src/mongo/util/net/ssl_manager.cpp309
-rw-r--r--src/mongo/util/net/ssl_manager.h26
-rw-r--r--src/mongo/util/net/ssl_manager_status.cpp70
-rw-r--r--src/mongo/util/net/ssl_options.cpp11
-rw-r--r--src/mongo/util/net/ssl_options.h4
-rw-r--r--src/mongo/util/net/ssl_types.h67
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;
};