diff options
-rw-r--r-- | jstests/core/views/views_all_commands.js | 3 | ||||
-rw-r--r-- | jstests/ssl/sni_name_advertisement.js | 76 | ||||
-rw-r--r-- | src/mongo/db/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/commands/whats_my_sni_command.cpp | 82 | ||||
-rw-r--r-- | src/mongo/dbtests/querytests.cpp | 11 | ||||
-rw-r--r-- | src/mongo/embedded/mongo_embedded/mongo_embedded_test.cpp | 123 | ||||
-rw-r--r-- | src/mongo/shell/shardingtest.js | 4 | ||||
-rw-r--r-- | src/mongo/util/net/ssl/detail/impl/engine_apple.ipp | 13 | ||||
-rw-r--r-- | src/mongo/util/net/ssl/detail/impl/engine_openssl.ipp | 13 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_apple.cpp | 6 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_openssl.cpp | 15 |
11 files changed, 274 insertions, 73 deletions
diff --git a/jstests/core/views/views_all_commands.js b/jstests/core/views/views_all_commands.js index f1d07b67d79..ae1c882cac2 100644 --- a/jstests/core/views/views_all_commands.js +++ b/jstests/core/views/views_all_commands.js @@ -526,7 +526,8 @@ let viewsCommandTests = { voteCommitIndexBuild: {skip: isUnrelated}, voteCommitTransaction: {skip: isUnrelated}, voteAbortTransaction: {skip: isUnrelated}, - whatsmyuri: {skip: isUnrelated} + whatsmyuri: {skip: isUnrelated}, + whatsmysni: {skip: isUnrelated} }; /** diff --git a/jstests/ssl/sni_name_advertisement.js b/jstests/ssl/sni_name_advertisement.js new file mode 100644 index 00000000000..9e8f012c1cf --- /dev/null +++ b/jstests/ssl/sni_name_advertisement.js @@ -0,0 +1,76 @@ +/* + * Tests that SNI names are advertised if and only if they are a URL, and NOT an IP address. + */ + +(function() { +'use strict'; + +let path = "jstests/libs/"; +let pemKeyFile = path + "server.pem"; +let caFile = path + "ca.pem"; +let testURL = "local.10gen.cc"; +let testIP = "127.0.0.1"; + +let params = { + tlsCertificateKeyFile: pemKeyFile, + tlsCAFile: caFile, + tlsMode: "preferTLS", + bind_ip: testURL, + tlsAllowInvalidHostnames: "" +}; + +/* we will have two test server configurations: one that is bound to a URL, and one that is bound to + * an IP address + * The bind_ip here is only to confirm that mongod and the shell are on the same page. bind_ip is + * not what is used for testing SNI advertisement. That is the IP address supplied to the shell. */ +let ipParams = Object.merge(params, {bind_ip: testIP}); +let urlParams = params; + +// returns the result of command "whatsmysni" from a regular mongod +function getSNI(params) { + let mongod = MongoRunner.runMongod(params); + let m = new Mongo(params.bind_ip + ":" + mongod.port); + let db = m.getDB("admin"); + + const sni = assert.commandWorked(db.runCommand({whatsmysni: 1}))["sni"]; + MongoRunner.stopMongod(mongod); + + return sni; +} + +// returns the result of command "whatsmysni" performed between nodes of a sharded cluster +function getSNISharded(params) { + let s = new ShardingTest({ + name: "shard", + shards: 2, + useHostname: true, + host: params.bind_ip, + other: {configOptions: params, mongosOptions: params, shardOptions: params} + }); + let db = s.getDB("admin"); + + // sort of have to fish out the value from deep within the output of multicast... + const multicastData = + assert.commandWorked(db.runCommand({multicast: {whatsmysni: 1}}))["hosts"]; + const hostName = Object.keys(multicastData)[0]; + const sni = multicastData[hostName]["data"]["sni"]; + + s.stop(); + + return sni; +} + +// TODO SERVER-41045 remove if-statement once SNI is supported on Windows +if (!_isWindows()) { + jsTestLog("Testing mongod bound to URL " + testURL); + assert.eq(testURL, getSNI(urlParams), "URL host is not advertised as SNI name in basic mongod"); + assert.eq(testURL, + getSNISharded(urlParams), + "URL host is not advertised as SNI name in sharded mongod"); + + jsTestLog("Testing mongod bound to IP " + testIP); + assert.eq(false, getSNI(ipParams), "IP host is advertised as SNI name in basic mongod"); + assert.eq( + false, getSNISharded(ipParams), "IP host is advertised as SNI name in sharded mongod"); +} +})();
\ No newline at end of file diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 7e0ed44fa10..61c5901ac47 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -271,6 +271,7 @@ env.Library( "run_aggregate.cpp", "sleep_command.cpp", "validate.cpp", + "whats_my_sni_command.cpp", "write_commands/write_commands.cpp", env.Idlc('create.idl')[0], env.Idlc('enable_coordinator_for_create_indexes_command.idl')[0], diff --git a/src/mongo/db/commands/whats_my_sni_command.cpp b/src/mongo/db/commands/whats_my_sni_command.cpp new file mode 100644 index 00000000000..0b758235c8b --- /dev/null +++ b/src/mongo/db/commands/whats_my_sni_command.cpp @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2019-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kCommand + +#include "mongo/db/commands.h" +#include "mongo/util/log.h" + +namespace mongo { + +/* for diagnostic / testing purposes. Enabled via command line. */ +class CmdWhatsMySNI : public BasicCommand { +public: + CmdWhatsMySNI() : BasicCommand("whatsmysni") {} + + std::string help() const override { + return "internal testing command. Returns an object containing the connection's " + "advertised SNI name, if any, and false if none is being advertised"; + } + + bool run(OperationContext* opCtx, + const std::string& ns, + const BSONObj& cmdObj, + BSONObjBuilder& result) override { + auto sniName = opCtx->getClient()->getSniNameForSession(); + // if no SNI name is advertised, output is false + if (!sniName) { + result.append("sni", false); + } else { + result.append("sni", *sniName); + } + + result.append("ok", 1); + + return true; + } + + bool supportsWriteConcern(const BSONObj& cmd) const override { + return false; + } + + bool adminOnly() const override { + return true; + } + + AllowedOnSecondary secondaryAllowed(ServiceContext*) const override { + return AllowedOnSecondary::kAlways; + } + + void addRequiredPrivileges(const std::string& dbname, + const BSONObj& cmdObj, + std::vector<Privilege>* out) const override {} +}; + +MONGO_REGISTER_TEST_COMMAND(CmdWhatsMySNI) +} // namespace mongo diff --git a/src/mongo/dbtests/querytests.cpp b/src/mongo/dbtests/querytests.cpp index 044448eaabc..9792bfcb7b4 100644 --- a/src/mongo/dbtests/querytests.cpp +++ b/src/mongo/dbtests/querytests.cpp @@ -1717,6 +1717,16 @@ public: } }; +class WhatsMySni : public CollectionBase { +public: + WhatsMySni() : CollectionBase("whatsmysni") {} + void run() { + BSONObj result; + _client.runCommand("admin", BSON("whatsmysni" << 1), result); + ASSERT_EQUALS("", result["sni"].str()); + } +}; + class QueryByUuid : public CollectionBase { public: QueryByUuid() : CollectionBase("QueryByUuid") {} @@ -1912,6 +1922,7 @@ public: add<QueryReadsAll>(); add<queryobjecttests::names1>(); add<OrderingTest>(); + add<WhatsMySni>(); } }; diff --git a/src/mongo/embedded/mongo_embedded/mongo_embedded_test.cpp b/src/mongo/embedded/mongo_embedded/mongo_embedded_test.cpp index a46909dc903..9bfab7ef5dc 100644 --- a/src/mongo/embedded/mongo_embedded/mongo_embedded_test.cpp +++ b/src/mongo/embedded/mongo_embedded/mongo_embedded_test.cpp @@ -562,67 +562,68 @@ TEST_F(MongodbCAPITest, InsertAndUpdate) { TEST_F(MongodbCAPITest, RunListCommands) { auto client = createClient(); - std::vector<std::string> whitelist = { - "_hashBSONElement", - "aggregate", - "buildInfo", - "collMod", - "collStats", - "configureFailPoint", - "count", - "create", - "createIndexes", - "currentOp", - "dataSize", - "dbStats", - "delete", - "distinct", - "drop", - "dropDatabase", - "dropIndexes", - "echo", - "endSessions", - "explain", - "find", - "findAndModify", - "getLastError", - "getMore", - "getParameter", - "httpClientRequest", - "insert", - "isMaster", - "killCursors", - "killOp", - "killSessions", - "killAllSessions", - "killAllSessionsByPattern", - "listCollections", - "listCommands", - "listDatabases", - "listIndexes", - "lockInfo", - "ping", - "planCacheClear", - "planCacheClearFilters", - "planCacheListFilters", - "planCacheListPlans", - "planCacheListQueryShapes", - "planCacheSetFilter", - "reIndex", - "refreshLogicalSessionCacheNow", - "refreshSessions", - "renameCollection", - "repairDatabase", - "resetError", - "serverStatus", - "setBatteryLevel", - "setParameter", - "sleep", - "startSession", - "trimMemory", - "update", - "validate", - }; + std::vector<std::string> whitelist = {"_hashBSONElement", + "aggregate", + "buildInfo", + "collMod", + "collStats", + "configureFailPoint", + "count", + "create", + "createIndexes", + "currentOp", + "dataSize", + "dbStats", + "delete", + "distinct", + "drop", + "dropDatabase", + "dropIndexes", + "echo", + "endSessions", + "explain", + "find", + "findAndModify", + "getLastError", + "getMore", + "getParameter", + "httpClientRequest", + "insert", + "isMaster", + "killCursors", + "killOp", + "killSessions", + "killAllSessions", + "killAllSessionsByPattern", + "listCollections", + "listCommands", + "listDatabases", + "listIndexes", + "lockInfo", + "ping", + "planCacheClear", + "planCacheClearFilters", + "planCacheListFilters", + "planCacheListPlans", + "planCacheListQueryShapes", + "planCacheSetFilter", + "reIndex", + "refreshLogicalSessionCacheNow", + "refreshSessions", + "renameCollection", + "repairDatabase", + "resetError", + "serverStatus", + "setBatteryLevel", + "setParameter", + "sleep", + "startSession", + "trimMemory", + "twoPhaseCreateIndexes", + "update", + "validate", + "whatsmysni"}; + std::sort(whitelist.begin(), whitelist.end()); mongo::BSONObj listCommandsObj = mongo::fromjson("{ listCommands: 1 }"); diff --git a/src/mongo/shell/shardingtest.js b/src/mongo/shell/shardingtest.js index d31bab4ebee..6a855c4b8b6 100644 --- a/src/mongo/shell/shardingtest.js +++ b/src/mongo/shell/shardingtest.js @@ -1133,7 +1133,7 @@ var ShardingTest = function(params) { } var keyFile = otherParams.keyFile; - var hostName = getHostName(); + var hostName = otherParams.host === undefined ? getHostName() : otherParams.host; this._testName = testName; this._otherParams = otherParams; @@ -1265,6 +1265,7 @@ var ShardingTest = function(params) { var rs = new ReplSetTest({ name: setName, nodes: numReplicas, + host: hostName, useHostName: otherParams.useHostname, useBridge: otherParams.useBridge, bridgeOptions: otherParams.bridgeOptions, @@ -1394,6 +1395,7 @@ var ShardingTest = function(params) { // Using replica set for config servers var rstOptions = { useHostName: otherParams.useHostname, + host: hostName, useBridge: otherParams.useBridge, bridgeOptions: otherParams.bridgeOptions, keyFile: keyFile, diff --git a/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp b/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp index 154f08707aa..031260b8945 100644 --- a/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp +++ b/src/mongo/util/net/ssl/detail/impl/engine_apple.ipp @@ -37,6 +37,9 @@ #include "asio/detail/push_options.hpp" #include "asio/detail/throw_error.hpp" #include "asio/error.hpp" +#include "asio/ip/address.hpp" + +#include <arpa/inet.h> #include "mongo/util/log.h" #include "mongo/util/net/ssl/apple.hpp" @@ -186,9 +189,13 @@ bool engine::_initSSL(stream_base::handshake_type type, asio::error_code& ec) { status = ::SSLSetSessionOption(_ssl.get(), ::kSSLSessionOptionBreakOnClientAuth, true); } - if (!_remoteHostName.empty() && (status == ::errSecSuccess)) { - status = - ::SSLSetPeerDomainName(_ssl.get(), _remoteHostName.c_str(), _remoteHostName.size()); + if (!_remoteHostName.empty() && (status == ::errSecSuccess) { + error_code ec; + ip::make_address(_remoteHostName, ec); + if (ec) { + status = + ::SSLSetPeerDomainName(_ssl.get(), _remoteHostName.c_str(), _remoteHostName.size()); + } } if (status != ::errSecSuccess) { diff --git a/src/mongo/util/net/ssl/detail/impl/engine_openssl.ipp b/src/mongo/util/net/ssl/detail/impl/engine_openssl.ipp index fc0ff1128b7..def2a7e7936 100644 --- a/src/mongo/util/net/ssl/detail/impl/engine_openssl.ipp +++ b/src/mongo/util/net/ssl/detail/impl/engine_openssl.ipp @@ -16,6 +16,7 @@ #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) #include "asio/detail/config.hpp" +#include "asio/ip/address.hpp" #include "asio/detail/throw_error.hpp" #include "asio/error.hpp" @@ -211,9 +212,15 @@ int engine::do_accept(void*, std::size_t) { int engine::do_connect(void*, std::size_t) { if (!_remoteHostName.empty()) { - int ret = ::SSL_set_tlsext_host_name(ssl_, _remoteHostName.c_str()); - if (ret != 1) - return ret; + error_code ec; + ip::make_address(_remoteHostName, ec); + // only have TLS advertise _remoteHostName as an SNI if it is not an IP address + if (ec) { + int ret = ::SSL_set_tlsext_host_name(ssl_, _remoteHostName.c_str()); + if (ret != 1) { + return ret; + } + } _remoteHostName.clear(); } diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp index 2b6a1daa106..463ae07f864 100644 --- a/src/mongo/util/net/ssl_manager_apple.cpp +++ b/src/mongo/util/net/ssl_manager_apple.cpp @@ -55,6 +55,8 @@ #include "mongo/util/net/ssl_options.h" #include "mongo/util/net/ssl_parameters_gen.h" +#include <arpa/inet.h> + using asio::ssl::apple::CFUniquePtr; /* This API appears in the Security framework even though @@ -1108,7 +1110,9 @@ public: uassertOSStatusOK(::SSLSetProtocolVersionMin(_ssl.get(), ctx->protoMin)); uassertOSStatusOK(::SSLSetProtocolVersionMax(_ssl.get(), ctx->protoMax)); - if (!hostname.empty()) { + std::array<uint8_t, INET6_ADDRSTRLEN> unusedBuf; + if (!hostname.empty() && (inet_pton(AF_INET, hostname.c_str(), unusedBuf.data()) == 0) && + (inet_pton(AF_INET6, hostname.c_str(), unusedBuf.data()) == 0)) { uassertOSStatusOK( ::SSLSetPeerDomainName(_ssl.get(), hostname.c_str(), hostname.size())); } diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp index e6fdd202bb3..2ba1e1a56d1 100644 --- a/src/mongo/util/net/ssl_manager_openssl.cpp +++ b/src/mongo/util/net/ssl_manager_openssl.cpp @@ -68,6 +68,7 @@ #ifndef _WIN32 #include <netinet/in.h> #endif +#include <arpa/inet.h> #include <openssl/asn1.h> #include <openssl/asn1t.h> #include <openssl/dh.h> @@ -1441,9 +1442,17 @@ SSLConnectionInterface* SSLManagerOpenSSL::connect(Socket* socket) { _clientContext.get(), socket, (const char*)nullptr, 0); const auto undotted = removeFQDNRoot(socket->remoteAddr().hostOrIp()); - int ret = ::SSL_set_tlsext_host_name(sslConn->ssl, undotted.c_str()); - if (ret != 1) - _handleSSLError(sslConn.get(), ret); + + // only have TLS advertise host name if it is not an IP address + int ret; + std::array<uint8_t, INET6_ADDRSTRLEN> unusedBuf; + if ((inet_pton(AF_INET, undotted.c_str(), unusedBuf.data()) == 0) && + (inet_pton(AF_INET6, undotted.c_str(), unusedBuf.data()) == 0)) { + ret = ::SSL_set_tlsext_host_name(sslConn->ssl, undotted.c_str()); + if (ret != 1) { + _handleSSLError(sslConn.get(), ret); + } + } do { ret = ::SSL_connect(sslConn->ssl); |