diff options
author | Henrik Edin <henrik.edin@mongodb.com> | 2019-05-17 15:51:49 -0400 |
---|---|---|
committer | Henrik Edin <henrik.edin@mongodb.com> | 2019-05-21 12:40:03 -0400 |
commit | 00528cee4912c79713c94de7d161638c7653cb2e (patch) | |
tree | 932c5053a89b0a3dd59574ccf66e5307f3940705 /jstests/ssl/repl_ssl_split_horizon.js | |
parent | ac1a0ee0babcc676b59b261bd6747553f4852a6f (diff) | |
download | mongo-00528cee4912c79713c94de7d161638c7653cb2e.tar.gz |
SERVER-40643 SERVER-40645 Add jstests that test the split horizon feature
Split horizon relies on SNI over TLS and we simulate this by using HOSTALIASES (Linux only)
Diffstat (limited to 'jstests/ssl/repl_ssl_split_horizon.js')
-rw-r--r-- | jstests/ssl/repl_ssl_split_horizon.js | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/jstests/ssl/repl_ssl_split_horizon.js b/jstests/ssl/repl_ssl_split_horizon.js new file mode 100644 index 00000000000..d70980ebe88 --- /dev/null +++ b/jstests/ssl/repl_ssl_split_horizon.js @@ -0,0 +1,190 @@ +(function() { + 'use strict'; + // Create a temporary host file that creates two aliases for localhost that are in the + // splithorizon certificate. + // The aliases are 'splithorizon1' and 'splithorizon2' + const hostsFile = MongoRunner.dataPath + 'split-horizon-hosts'; + writeFile(hostsFile, "splithorizon1 localhost\nsplithorizon2 localhost\n"); + + // Check if HOSTALIASES works on this system (Will not work on Windows or OSX and may not work + // on Linux) + try { + var rc = + runMongoProgram("env", "HOSTALIASES=" + hostsFile, "getent", "hosts", "splithorizon1"); + } catch (e) { + jsTestLog( + `Failed the check for HOSTALIASES support using env, we are probably on a non-GNU platform. Skipping this test.`); + removeFile(hostsFile); + return; + } + + if (rc != 0) { + removeFile(hostsFile); + + // Check glibc version to figure out of HOSTALIASES will work as expected + clearRawMongoProgramOutput(); + var rc = runProgram("getconf", "GNU_LIBC_VERSION"); + if (rc != 0) { + jsTestLog( + `Failed the check for GLIBC version, we are probably on a non-GNU platform. Skipping this test.`); + return; + } + + // Output is of the format: 'glibc x.yz' + var output = rawMongoProgramOutput(); + var fields = output.split(" "); + var glibc_version = parseFloat(fields[2]); + + // Fail this test if we are on GLIBC >= 2.2 and HOSTALIASES still doesn't work + if (glibc_version < 2.2) { + jsTestLog(`HOSTALIASES does not seem to work as expected on this system. GLIBC + version is ${glibc_version}, skipping this test.`); + return; + } else { + assert(false, `HOSTALIASES does not seem to work as expected on this system. GLIBC + version is ${glibc_version}`); + } + } + + var replTest = new ReplSetTest({ + name: "splitHorizontest", + nodes: 2, + nodeOptions: { + sslMode: "requireSSL", + sslPEMKeyFile: "jstests/libs/splithorizon-server.pem", + }, + host: "localhost", + useHostName: false, + }); + + replTest.startSet({ + env: { + SSL_CERT_FILE: 'jstests/libs/splithorizon-ca.pem', + }, + }); + + // Create some variables needed for our horizons, we're replacing localhost with the horizon + // name, leaving the port the same (so we can connect) + var node0 = replTest.nodeList()[0]; + var node1 = replTest.nodeList()[1]; + var node0localHostname = node0; + var node1localHostname = node1; + var node0horizonHostname = node0.replace("localhost", "splithorizon1"); + var node1horizonHostname = node1.replace("localhost", "splithorizon1"); + var node0horizonMissingHostname = node0.replace("localhost", "splithorizon2"); + var node1horizonMissingHostname = node1.replace("localhost", "splithorizon2"); + + var config = replTest.getReplSetConfig(); + config.members[0].horizons = {}; + config.members[0].horizons.horizon_name = node0horizonHostname; + config.members[1].horizons = {}; + config.members[1].horizons.horizon_name = node1horizonHostname; + + replTest.initiate(config); + + var checkExpectedHorizon = function(url, memberIndex, expectedHostname) { + // Run isMaster in the shell and check that we get the expected hostname back + var argv = [ + 'env', + "HOSTALIASES=" + hostsFile, + "SSL_CERT_FILE=jstests/libs/splithorizon-ca.pem", + './mongo', + url, + '--eval', + ("assert(db.runCommand({isMaster: 1})['hosts'][" + memberIndex + "] == '" + + expectedHostname + "')") + ]; + return runMongoProgram(...argv); + }; + + // Using localhost should use the default horizon + var defaultURL = `mongodb://${node0localHostname}/admin?replicaSet=${replTest.name}&ssl=true`; + jsTestLog(`URL without horizon: ${defaultURL}`); + assert.eq(checkExpectedHorizon(defaultURL, 0, node0localHostname), + 0, + "localhost does not return horizon"); + assert.eq(checkExpectedHorizon(defaultURL, 1, node1localHostname), + 0, + "localhost does not return horizon"); + + // Using 'splithorizon1' should use that horizon + var horizonURL = `mongodb://${node0horizonHostname}/admin?replicaSet=${replTest.name}&ssl=true`; + jsTestLog(`URL with horizon: ${horizonURL}`); + assert.eq(checkExpectedHorizon(horizonURL, 0, node0horizonHostname), + 0, + "does not return horizon as expected"); + assert.eq(checkExpectedHorizon(horizonURL, 1, node1horizonHostname), + 0, + "does not return horizon as expected"); + + // Using 'splithorizon2' does not have a horizon so it should return default + var horizonMissingURL = + `mongodb://${node0horizonMissingHostname}/admin?replicaSet=${replTest.name}&ssl=true`; + jsTestLog(`URL with horizon: ${horizonMissingURL}`); + assert.eq(checkExpectedHorizon(horizonMissingURL, 0, node0localHostname), + 0, + "does not return localhost as expected"); + assert.eq(checkExpectedHorizon(horizonMissingURL, 1, node1localHostname), + 0, + "does not return localhost as expected"); + + // Check so we can replSetReconfig to add another horizon + config.version += 1; + config.members[0].horizons.other_horizon_name = node0horizonMissingHostname; + config.members[1].horizons.other_horizon_name = node1horizonMissingHostname; + + assert.adminCommandWorkedAllowingNetworkError(replTest.getPrimary(), {replSetReconfig: config}); + + // Using 'splithorizon2' should now return the new horizon + var horizonMissingURL = + `mongodb://${node0horizonMissingHostname}/admin?replicaSet=${replTest.name}&ssl=true`; + jsTestLog(`URL with horizon: ${horizonMissingURL}`); + assert.eq(checkExpectedHorizon(horizonMissingURL, 0, node0horizonMissingHostname), + 0, + "does not return horizon as expected"); + assert.eq(checkExpectedHorizon(horizonMissingURL, 1, node1horizonMissingHostname), + 0, + "does not return horizon as expected"); + + // Change horizon to return a different port to connect to, so the feature can be used in a + // port-forwarding environment + var node0horizonHostnameDifferentPort = "splithorizon1:80"; + var node1horizonHostnameDifferentPort = "splithorizon1:81"; + config.version += 1; + config.members[0].horizons.horizon_name = node0horizonHostnameDifferentPort; + config.members[1].horizons.horizon_name = node1horizonHostnameDifferentPort; + + assert.adminCommandWorkedAllowingNetworkError(replTest.getPrimary(), {replSetReconfig: config}); + + // Build the connection URL, do not set replicaSet as that will trigger the ReplicaSetMonitor + // which will fail as we can't actually connect now (port is wrong) + var horizonDifferentPortURL = `mongodb://${node0horizonHostname}/admin?ssl=true`; + jsTestLog(`URL with horizon using different port: ${horizonDifferentPortURL}`); + assert.eq(checkExpectedHorizon(horizonDifferentPortURL, 0, node0horizonHostnameDifferentPort), + 0, + "does not return horizon as expected"); + assert.eq(checkExpectedHorizon(horizonDifferentPortURL, 1, node1horizonHostnameDifferentPort), + 0, + "does not return horizon as expected"); + + // Providing a config where horizons does not exist in all members is expected to fail + config.version += 1; + config.members[0].horizons.horizon_mismatch = node0.replace("localhost", "splithorizon3"); + assert.commandFailed(replTest.getPrimary().adminCommand({replSetReconfig: config})); + + // Providing a config where horizon hostnames are duplicated in members is expected to fail + config.version += 1; + config.members[1].horizons.horizon_mismatch = config.members[0].horizons.horizon_mismatch; + assert.commandFailed(replTest.getPrimary().adminCommand({replSetReconfig: config})); + + // Two horizons with duplicated hostnames are not allowed + config.version += 1; + delete config.members[0].horizons.horizon_mismatch; + delete config.members[1].horizons.horizon_mismatch; + config.members[0].horizons.horizon_dup_hostname = config.members[0].horizons.horizon_name; + config.members[1].horizons.horizon_dup_hostname = config.members[1].horizons.horizon_name; + assert.commandFailed(replTest.getPrimary().adminCommand({replSetReconfig: config})); + + replTest.stopSet(); + removeFile(hostsFile); +})(); |