diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2018-09-17 18:31:30 -0400 |
---|---|---|
committer | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2018-09-17 18:31:45 -0400 |
commit | 0780841a51470b33105ec2b0a7831531b82d0a8d (patch) | |
tree | f8911373b7b93144f6055b0378c482ed9f4fba2f /src | |
parent | 6e3c5ea176aadbd0475f8f87525b9f0fabd4bdc9 (diff) | |
download | mongo-0780841a51470b33105ec2b0a7831531b82d0a8d.tar.gz |
SERVER-36250 Add support for optionally logging specific negotiated TLS versions
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/transport/session_asio.h | 4 | ||||
-rw-r--r-- | src/mongo/util/net/sock.cpp | 5 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager.cpp | 40 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager.h | 29 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_apple.cpp | 47 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_openssl.cpp | 38 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_windows.cpp | 39 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_options.h | 1 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_options_server.cpp | 41 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_options_test.cpp | 8 |
10 files changed, 182 insertions, 70 deletions
diff --git a/src/mongo/transport/session_asio.h b/src/mongo/transport/session_asio.h index 00eab1d0ae0..8f382e0f441 100644 --- a/src/mongo/transport/session_asio.h +++ b/src/mongo/transport/session_asio.h @@ -237,7 +237,7 @@ protected: auto sslManager = getSSLManager(); auto swPeerInfo = uassertStatusOK(sslManager->parseAndValidatePeerCertificate( - _sslSocket->native_handle(), target.host())); + _sslSocket->native_handle(), target.host(), target)); if (swPeerInfo) { SSLPeerInfo::forSession(shared_from_this()) = std::move(*swPeerInfo); @@ -616,7 +616,7 @@ private: if (sslPeerInfo.subjectName.empty()) { auto sslManager = getSSLManager(); auto swPeerInfo = sslManager->parseAndValidatePeerCertificate( - _sslSocket->native_handle(), ""); + _sslSocket->native_handle(), "", _remote); // The value of swPeerInfo is a bit complicated: // diff --git a/src/mongo/util/net/sock.cpp b/src/mongo/util/net/sock.cpp index e8a52f9c285..11427875a92 100644 --- a/src/mongo/util/net/sock.cpp +++ b/src/mongo/util/net/sock.cpp @@ -239,7 +239,7 @@ bool Socket::secure(SSLManagerInterface* mgr, const std::string& remoteHost) { } _sslManager = mgr; _sslConnection.reset(_sslManager->connect(this)); - mgr->parseAndValidatePeerCertificateDeprecated(_sslConnection.get(), remoteHost); + mgr->parseAndValidatePeerCertificateDeprecated(_sslConnection.get(), remoteHost, HostAndPort()); return true; } @@ -257,7 +257,8 @@ SSLPeerInfo Socket::doSSLHandshake(const char* firstBytes, int len) { remoteString()); } _sslConnection.reset(_sslManager->accept(this, firstBytes, len)); - return _sslManager->parseAndValidatePeerCertificateDeprecated(_sslConnection.get(), ""); + return _sslManager->parseAndValidatePeerCertificateDeprecated( + _sslConnection.get(), "", HostAndPort()); } std::string Socket::getSNIServerName() const { diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp index a0a21e6923c..59601090989 100644 --- a/src/mongo/util/net/ssl_manager.cpp +++ b/src/mongo/util/net/ssl_manager.cpp @@ -759,6 +759,46 @@ public: } } tlsVersionStatus; +void recordTLSVersion(TLSVersion version, const HostAndPort& hostForLogging) { + StringData versionString; + auto& counts = mongo::TLSVersionCounts::get(getGlobalServiceContext()); + switch (version) { + case TLSVersion::kTLS10: + counts.tls10.addAndFetch(1); + if (std::find(sslGlobalParams.tlsLogVersions.cbegin(), + sslGlobalParams.tlsLogVersions.cend(), + SSLParams::Protocols::TLS1_0) != sslGlobalParams.tlsLogVersions.cend()) { + versionString = "1.0"_sd; + } + break; + case TLSVersion::kTLS11: + counts.tls11.addAndFetch(1); + if (std::find(sslGlobalParams.tlsLogVersions.cbegin(), + sslGlobalParams.tlsLogVersions.cend(), + SSLParams::Protocols::TLS1_1) != sslGlobalParams.tlsLogVersions.cend()) { + versionString = "1.1"_sd; + } + break; + case TLSVersion::kTLS12: + counts.tls12.addAndFetch(1); + if (std::find(sslGlobalParams.tlsLogVersions.cbegin(), + sslGlobalParams.tlsLogVersions.cend(), + SSLParams::Protocols::TLS1_2) != sslGlobalParams.tlsLogVersions.cend()) { + versionString = "1.2"_sd; + } + break; + default: + if (!sslGlobalParams.tlsLogVersions.empty()) { + versionString = "unkown"_sd; + } + break; + } + + if (!versionString.empty()) { + log() << "Accepted connection with TLS Version " << versionString << " from connection " + << hostForLogging; + } +} #endif diff --git a/src/mongo/util/net/ssl_manager.h b/src/mongo/util/net/ssl_manager.h index 990aca6e2d9..cee1abeb515 100644 --- a/src/mongo/util/net/ssl_manager.h +++ b/src/mongo/util/net/ssl_manager.h @@ -159,7 +159,9 @@ public: * a StatusWith instead. */ virtual SSLPeerInfo parseAndValidatePeerCertificateDeprecated( - const SSLConnectionInterface* conn, const std::string& remoteHost) = 0; + const SSLConnectionInterface* conn, + const std::string& remoteHost, + const HostAndPort& hostForLogging) = 0; /** * Gets the SSLConfiguration containing all information about the current SSL setup @@ -200,7 +202,9 @@ public: * X509 authorization will be returned. */ virtual StatusWith<boost::optional<SSLPeerInfo>> parseAndValidatePeerCertificate( - SSLConnectionType ssl, const std::string& remoteHost) = 0; + SSLConnectionType ssl, + const std::string& remoteHost, + const HostAndPort& hostForLogging) = 0; }; // Access SSL functions through this instance. @@ -231,5 +235,26 @@ std::string removeFQDNRoot(std::string name); */ std::string escapeRfc2253(StringData str); +/** + * Platform neutral TLS version enum + */ +enum class TLSVersion { + kUnknown, + kTLS10, + kTLS11, + kTLS12, +}; + +/** + * Map SSL version to platform-neutral enum. + */ +StatusWith<TLSVersion> mapTLSVersion(SSLConnectionType conn); + +/** + * Record information about TLS versions and optionally log the TLS version + */ +void recordTLSVersion(TLSVersion version, const HostAndPort& hostForLogging); + + } // namespace mongo #endif // #ifdef MONGO_CONFIG_SSL diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp index ac7b9d911da..e18f3cdb720 100644 --- a/src/mongo/util/net/ssl_manager_apple.cpp +++ b/src/mongo/util/net/ssl_manager_apple.cpp @@ -1076,10 +1076,13 @@ public: SSLConnectionInterface* accept(Socket* socket, const char* initialBytes, int len) final; SSLPeerInfo parseAndValidatePeerCertificateDeprecated(const SSLConnectionInterface* conn, - const std::string& remoteHost) final; + const std::string& remoteHost, + const HostAndPort& hostForLogging) final; StatusWith<boost::optional<SSLPeerInfo>> parseAndValidatePeerCertificate( - ::SSLContextRef conn, const std::string& remoteHost) final; + ::SSLContextRef conn, + const std::string& remoteHost, + const HostAndPort& hostForLogging) final; const SSLConfiguration& getSSLConfiguration() const final { return _sslConfiguration; @@ -1263,10 +1266,12 @@ SSLConnectionInterface* SSLManagerApple::accept(Socket* socket, const char* init } SSLPeerInfo SSLManagerApple::parseAndValidatePeerCertificateDeprecated( - const SSLConnectionInterface* conn, const std::string& remoteHost) { + const SSLConnectionInterface* conn, + const std::string& remoteHost, + const HostAndPort& hostForLogging) { auto ssl = checked_cast<const SSLConnectionApple*>(conn)->get(); - auto swPeerSubjectName = parseAndValidatePeerCertificate(ssl, remoteHost); + auto swPeerSubjectName = parseAndValidatePeerCertificate(ssl, remoteHost, hostForLogging); // We can't use uassertStatusOK here because we need to throw a NetworkException. if (!swPeerSubjectName.isOK()) { throwSocketError(SocketErrorKind::CONNECT_ERROR, swPeerSubjectName.getStatus().reason()); @@ -1274,44 +1279,34 @@ SSLPeerInfo SSLManagerApple::parseAndValidatePeerCertificateDeprecated( return swPeerSubjectName.getValue().get_value_or(SSLPeerInfo()); } -void recordTLSVersion(::SSLContextRef ssl) { +StatusWith<TLSVersion> mapTLSVersion(SSLContextRef ssl) { ::SSLProtocol protocol; uassertOSStatusOK(::SSLGetNegotiatedProtocolVersion(ssl, &protocol)); - auto& counts = mongo::TLSVersionCounts::get(getGlobalServiceContext()); switch (protocol) { case kTLSProtocol1: - counts.tls10.addAndFetch(1); - break; + return TLSVersion::kTLS10; case kTLSProtocol11: - counts.tls11.addAndFetch(1); - break; + return TLSVersion::kTLS11; case kTLSProtocol12: - counts.tls12.addAndFetch(1); - break; - // case kTLSProtocol13: - // counts.tls13.addAndFetch(1); - // break; - case kSSLProtocolUnknown: - case kSSLProtocol2: - case kSSLProtocol3: - case kSSLProtocol3Only: - case kTLSProtocol1Only: - case kSSLProtocolAll: - case kDTLSProtocol1: + return TLSVersion::kTLS12; default: // Some system headers may define additional protocols, so suppress warnings. - // Do nothing - break; + return TLSVersion::kUnknown; } } StatusWith<boost::optional<SSLPeerInfo>> SSLManagerApple::parseAndValidatePeerCertificate( - ::SSLContextRef ssl, const std::string& remoteHost) { + ::SSLContextRef ssl, const std::string& remoteHost, const HostAndPort& hostForLogging) { // Record TLS version stats - recordTLSVersion(ssl); + auto tlsVersionStatus = mapTLSVersion(ssl); + if (!tlsVersionStatus.isOK()) { + return tlsVersionStatus.getStatus(); + } + + recordTLSVersion(tlsVersionStatus.getValue(), hostForLogging); /* While we always have a system CA via the Keychain, * we'll pretend not to in terms of validation if the server diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp index bf0ad8d5cca..edc2f55ad5f 100644 --- a/src/mongo/util/net/ssl_manager_openssl.cpp +++ b/src/mongo/util/net/ssl_manager_openssl.cpp @@ -343,10 +343,11 @@ public: SSLConnectionInterface* accept(Socket* socket, const char* initialBytes, int len) final; SSLPeerInfo parseAndValidatePeerCertificateDeprecated(const SSLConnectionInterface* conn, - const std::string& remoteHost) final; + const std::string& remoteHost, + const HostAndPort& hostForLogging) final; StatusWith<boost::optional<SSLPeerInfo>> parseAndValidatePeerCertificate( - SSL* conn, const std::string& remoteHost) final; + SSL* conn, const std::string& remoteHost, const HostAndPort& hostForLogging) final; const SSLConfiguration& getSSLConfiguration() const final { return _sslConfiguration; @@ -1272,35 +1273,34 @@ SSLConnectionInterface* SSLManagerOpenSSL::accept(Socket* socket, } -void recordTLSVersion(const SSL* conn) { +StatusWith<TLSVersion> mapTLSVersion(SSL* conn) { int protocol = SSL_version(conn); - auto& counts = mongo::TLSVersionCounts::get(getGlobalServiceContext()); switch (protocol) { case TLS1_VERSION: - counts.tls10.addAndFetch(1); - break; + return TLSVersion::kTLS10; case TLS1_1_VERSION: - counts.tls11.addAndFetch(1); - break; + return TLSVersion::kTLS11; case TLS1_2_VERSION: - counts.tls12.addAndFetch(1); - break; + return TLSVersion::kTLS12; #ifdef TLS1_3_VERSION case TLS1_3_VERSION: - counts.tls13.addAndFetch(1); - break; + return TLSVersion::kTLS13; #endif default: - // Do nothing - break; + return TLSVersion::kUnknown; } } StatusWith<boost::optional<SSLPeerInfo>> SSLManagerOpenSSL::parseAndValidatePeerCertificate( - SSL* conn, const std::string& remoteHost) { + SSL* conn, const std::string& remoteHost, const HostAndPort& hostForLogging) { + + auto tlsVersionStatus = mapTLSVersion(conn); + if (!tlsVersionStatus.isOK()) { + return tlsVersionStatus.getStatus(); + } - recordTLSVersion(conn); + recordTLSVersion(tlsVersionStatus.getValue(), hostForLogging); if (!_sslConfiguration.hasCA && isSSLServer) return {boost::none}; @@ -1414,10 +1414,12 @@ StatusWith<boost::optional<SSLPeerInfo>> SSLManagerOpenSSL::parseAndValidatePeer SSLPeerInfo SSLManagerOpenSSL::parseAndValidatePeerCertificateDeprecated( - const SSLConnectionInterface* connInterface, const std::string& remoteHost) { + const SSLConnectionInterface* connInterface, + const std::string& remoteHost, + const HostAndPort& hostForLogging) { const SSLConnectionOpenSSL* conn = checked_cast<const SSLConnectionOpenSSL*>(connInterface); - auto swPeerSubjectName = parseAndValidatePeerCertificate(conn->ssl, remoteHost); + auto swPeerSubjectName = parseAndValidatePeerCertificate(conn->ssl, remoteHost, hostForLogging); // We can't use uassertStatusOK here because we need to throw a NetworkException. if (!swPeerSubjectName.isOK()) { throwSocketError(SocketErrorKind::CONNECT_ERROR, swPeerSubjectName.getStatus().reason()); diff --git a/src/mongo/util/net/ssl_manager_windows.cpp b/src/mongo/util/net/ssl_manager_windows.cpp index 6ae5d567140..bba5b521833 100644 --- a/src/mongo/util/net/ssl_manager_windows.cpp +++ b/src/mongo/util/net/ssl_manager_windows.cpp @@ -272,10 +272,11 @@ public: SSLConnectionInterface* accept(Socket* socket, const char* initialBytes, int len) final; SSLPeerInfo parseAndValidatePeerCertificateDeprecated(const SSLConnectionInterface* conn, - const std::string& remoteHost) final; + const std::string& remoteHost, + const HostAndPort& hostForLogging) final; StatusWith<boost::optional<SSLPeerInfo>> parseAndValidatePeerCertificate( - PCtxtHandle ssl, const std::string& remoteHost) final; + PCtxtHandle ssl, const std::string& remoteHost, const HostAndPort& hostForLogging) final; const SSLConfiguration& getSSLConfiguration() const final { @@ -1482,11 +1483,14 @@ Status SSLManagerWindows::_validateCertificate(PCCERT_CONTEXT cert, } SSLPeerInfo SSLManagerWindows::parseAndValidatePeerCertificateDeprecated( - const SSLConnectionInterface* conn, const std::string& remoteHost) { + const SSLConnectionInterface* conn, + const std::string& remoteHost, + const HostAndPort& hostForLogging) { auto swPeerSubjectName = parseAndValidatePeerCertificate( const_cast<SSLConnectionWindows*>(static_cast<const SSLConnectionWindows*>(conn)) ->_engine.native_handle(), - remoteHost); + remoteHost, + hostForLogging); // We can't use uassertStatusOK here because we need to throw a SocketException. if (!swPeerSubjectName.isOK()) { throwSocketError(SocketErrorKind::CONNECT_ERROR, swPeerSubjectName.getStatus().reason()); @@ -1657,7 +1661,7 @@ Status validatePeerCertificate(const std::string& remoteHost, return Status::OK(); } -Status recordTLSVersion(PCtxtHandle ssl) { +StatusWith<TLSVersion> mapTLSVersion(PCtxtHandle ssl) { SecPkgContext_ConnectionInfo connInfo; SECURITY_STATUS ss = QueryContextAttributes(ssl, SECPKG_ATTR_CONNECTION_INFO, &connInfo); @@ -1668,37 +1672,32 @@ Status recordTLSVersion(PCtxtHandle ssl) { << ss); } - auto& counts = mongo::TLSVersionCounts::get(getGlobalServiceContext()); switch (connInfo.dwProtocol) { case SP_PROT_TLS1_CLIENT: case SP_PROT_TLS1_SERVER: - counts.tls10.addAndFetch(1); - break; + return TLSVersion::kTLS10; case SP_PROT_TLS1_1_CLIENT: case SP_PROT_TLS1_1_SERVER: - counts.tls11.addAndFetch(1); - break; + return TLSVersion::kTLS11; case SP_PROT_TLS1_2_CLIENT: case SP_PROT_TLS1_2_SERVER: - counts.tls12.addAndFetch(1); - break; + return TLSVersion::kTLS12; default: - // Do nothing - break; + return TLSVersion::kUnknown; } - - return Status::OK(); } StatusWith<boost::optional<SSLPeerInfo>> SSLManagerWindows::parseAndValidatePeerCertificate( - PCtxtHandle ssl, const std::string& remoteHost) { + PCtxtHandle ssl, const std::string& remoteHost, const HostAndPort& hostForLogging) { PCCERT_CONTEXT cert; - auto countStatus = recordTLSVersion(ssl); - if (!countStatus.isOK()) { - return countStatus; + auto tlsVersionStatus = mapTLSVersion(ssl); + if (!tlsVersionStatus.isOK()) { + return tlsVersionStatus.getStatus(); } + recordTLSVersion(tlsVersionStatus.getValue(), hostForLogging); + if (!_sslConfiguration.hasCA && isSSLServer) return {boost::none}; diff --git a/src/mongo/util/net/ssl_options.h b/src/mongo/util/net/ssl_options.h index 85b0bd44f64..64a34654d1a 100644 --- a/src/mongo/util/net/ssl_options.h +++ b/src/mongo/util/net/ssl_options.h @@ -72,6 +72,7 @@ struct SSLParams { #endif std::vector<Protocols> sslDisabledProtocols; // --sslDisabledProtocols + std::vector<Protocols> tlsLogVersions; // --tlsLogVersion bool sslWeakCertificateValidation = false; // --sslWeakCertificateValidation bool sslFIPSMode = false; // --sslFIPSMode bool sslAllowInvalidCertificates = false; // --sslAllowInvalidCertificates diff --git a/src/mongo/util/net/ssl_options_server.cpp b/src/mongo/util/net/ssl_options_server.cpp index 8ccc3f30cc1..fa9fe9108ca 100644 --- a/src/mongo/util/net/ssl_options_server.cpp +++ b/src/mongo/util/net/ssl_options_server.cpp @@ -40,6 +40,7 @@ #include "mongo/util/log.h" #include "mongo/util/options_parser/startup_option_init.h" #include "mongo/util/options_parser/startup_options.h" +#include "mongo/util/text.h" #if MONGO_CONFIG_SSL_PROVIDER == MONGO_CONFIG_SSL_PROVIDER_OPENSSL #include <openssl/ssl.h> @@ -145,6 +146,13 @@ Status addSSLServerOptions(moe::OptionSection* options) { {"net.ssl.disabledProtocols"}, {"sslDisabledProtocols"}); + + options->addOptionChaining( + "net.tls.logVersions", + "tlsLogVersions", + moe::String, + "Comma separated list of TLS protocols to log on connect [TLS1_0,TLS1_1,TLS1_2]"); + options->addOptionChaining("net.tls.weakCertificateValidation", "tlsWeakCertificateValidation", moe::Switch, @@ -206,6 +214,32 @@ Status addSSLServerOptions(moe::OptionSection* options) { return Status::OK(); } +Status storeTLSLogVersion(const std::string& loggedProtocols) { + // The tlsLogVersion field is composed of a comma separated list of protocols to + // log. First, tokenize the field. + const auto tokens = StringSplitter::split(loggedProtocols, ","); + + // All universally accepted tokens, and their corresponding enum representation. + const std::map<std::string, SSLParams::Protocols> validConfigs{ + {"TLS1_0", SSLParams::Protocols::TLS1_0}, + {"TLS1_1", SSLParams::Protocols::TLS1_1}, + {"TLS1_2", SSLParams::Protocols::TLS1_2}, + }; + + // Map the tokens to their enum values, and push them onto the list of logged protocols. + for (const std::string& token : tokens) { + auto mappedToken = validConfigs.find(token); + if (mappedToken != validConfigs.end()) { + sslGlobalParams.tlsLogVersions.push_back(mappedToken->second); + continue; + } + + return Status(ErrorCodes::BadValue, "Unrecognized tlsLogVersions '" + token + "'"); + } + + return Status::OK(); +} + Status storeSSLServerOptions(const moe::Environment& params) { if (params.count("net.tls.mode")) { std::string sslModeParam = params["net.tls.mode"].as<string>(); @@ -304,6 +338,13 @@ Status storeSSLServerOptions(const moe::Environment& params) { #endif } + if (params.count("net.tls.logVersions")) { + const auto status = storeTLSLogVersion(params["net.tls.logVersions"].as<string>()); + if (!status.isOK()) { + return status; + } + } + if (params.count("net.tls.weakCertificateValidation")) { sslGlobalParams.sslWeakCertificateValidation = params["net.tls.weakCertificateValidation"].as<bool>(); diff --git a/src/mongo/util/net/ssl_options_test.cpp b/src/mongo/util/net/ssl_options_test.cpp index 048b6e5dc3d..99d4b3d5640 100644 --- a/src/mongo/util/net/ssl_options_test.cpp +++ b/src/mongo/util/net/ssl_options_test.cpp @@ -189,6 +189,8 @@ TEST(SetupOptions, tlsModeRequired) { argv.push_back("pw2"); argv.push_back("--tlsDisabledProtocols"); argv.push_back("TLS1_1"); + argv.push_back("--tlsLogVersions"); + argv.push_back("TLS1_2"); std::map<std::string, std::string> env_map; ASSERT_OK(mongo::addSSLServerOptions(&options)); @@ -217,6 +219,8 @@ TEST(SetupOptions, tlsModeRequired) { ASSERT_EQ(::mongo::sslGlobalParams.sslClusterPassword, "pw2"); ASSERT_EQ(static_cast<int>(::mongo::sslGlobalParams.sslDisabledProtocols.back()), static_cast<int>(::mongo::SSLParams::Protocols::TLS1_1)); + ASSERT_EQ(static_cast<int>(::mongo::sslGlobalParams.tlsLogVersions.back()), + static_cast<int>(::mongo::SSLParams::Protocols::TLS1_2)); } TEST(SetupOptions, sslModeRequired) { @@ -253,6 +257,8 @@ TEST(SetupOptions, sslModeRequired) { argv.push_back("pw2"); argv.push_back("--sslDisabledProtocols"); argv.push_back("TLS1_1"); + argv.push_back("--tlsLogVersions"); + argv.push_back("TLS1_0"); std::map<std::string, std::string> env_map; ASSERT_OK(mongo::addSSLServerOptions(&options)); @@ -281,6 +287,8 @@ TEST(SetupOptions, sslModeRequired) { ASSERT_EQ(::mongo::sslGlobalParams.sslClusterPassword, "pw2"); ASSERT_EQ(static_cast<int>(::mongo::sslGlobalParams.sslDisabledProtocols.back()), static_cast<int>(::mongo::SSLParams::Protocols::TLS1_1)); + ASSERT_EQ(static_cast<int>(::mongo::sslGlobalParams.tlsLogVersions.back()), + static_cast<int>(::mongo::SSLParams::Protocols::TLS1_0)); } #ifdef MONGO_CONFIG_SSL_CERTIFICATE_SELECTORS |