summaryrefslogtreecommitdiff
path: root/jstests/ssl/repl_ssl_split_horizon.js
blob: 198c4d4d6f9b62ea325ec2e2dbce6e75853f60e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
(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();
    clearRawMongoProgramOutput();

    jsTestLog(`getconf GNU_LIBC_VERSION\n${output}`);

    var fields = output.split(" ");
    var glibc_version = parseFloat(fields[2]);

    var rc = runProgram("cat", "/etc/os-release");
    if (rc != 0) {
        jsTestLog(
            `Failed the check for /etc/os-release, we are probably not on a *nix. Skipping this test.`);
        return;
    }

    var osRelease = rawMongoProgramOutput();
    clearRawMongoProgramOutput();

    jsTestLog(`cat /etc/os-release\n${osRelease}`);

    var suzeMatch = osRelease.match(/ID="?sles"?/);

    // 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 if (suzeMatch) {
        jsTestLog(
            `HOSTALIASES does not seem to work as expected but we detected SLES. GLIBC
                version is ${glibc_version}, skipping this test.`);
        return;
    }

    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
    const assertion = (memberIndex === "me")
        ? ("assert(db.runCommand({isMaster: 1})['me'] == '" + expectedHostname + "')")
        : ("assert(db.runCommand({isMaster: 1})['hosts'][" + memberIndex + "] == '" +
           expectedHostname + "')");

    var argv = [
        'env',
        "HOSTALIASES=" + hostsFile,
        "SSL_CERT_FILE=jstests/libs/splithorizon-ca.pem",
        'mongo',
        url,
        '--eval',
        assertion
    ];
    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, "me", 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, "me", 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, "me", 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, "me", 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, "me", 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);
})();