mirror of https://github.com/mongodb/mongo
413 lines
14 KiB
JavaScript
413 lines
14 KiB
JavaScript
import "jstests/multiVersion/libs/multi_rs.js";
|
|
|
|
import {isDebian, isRHEL8, isUbuntu, isUbuntu2004} from "jstests/libs/os_helpers.js";
|
|
import {ShardingTest} from "jstests/libs/shardingtest.js";
|
|
import {basicReplsetTest} from "jstests/replsets/libs/basic_replset_test.js";
|
|
|
|
// Do not fail if this test leaves unterminated processes because this file expects replset1.js to
|
|
// throw for invalid SSL options.
|
|
TestData.ignoreUnterminatedProcesses = true;
|
|
|
|
//=== Shared SSL testing library functions and constants ===
|
|
|
|
export var KEYFILE = "jstests/libs/key1";
|
|
|
|
export var SERVER_CERT = "jstests/libs/server.pem";
|
|
export var TRUSTED_SERVER_CERT = "jstests/libs/trusted-server.pem";
|
|
export var CA_CERT = "jstests/libs/ca.pem";
|
|
export var TRUSTED_CA_CERT = "jstests/libs/trusted-ca.pem";
|
|
export var CLIENT_CERT = "jstests/libs/client.pem";
|
|
export var TRUSTED_CLIENT_CERT = "jstests/libs/trusted-client.pem";
|
|
export var DH_PARAM = "jstests/libs/8k-prime.dhparam";
|
|
export var CLUSTER_CERT = "jstests/libs/cluster_cert.pem";
|
|
|
|
// Note: "tlsAllowInvalidCertificates" is enabled to avoid
|
|
// hostname conflicts with our testing certificates
|
|
export var disabled = {
|
|
tlsMode: "disabled",
|
|
};
|
|
|
|
export var allowTLS = {
|
|
tlsMode: "allowTLS",
|
|
tlsAllowInvalidCertificates: "",
|
|
tlsCertificateKeyFile: SERVER_CERT,
|
|
tlsCAFile: CA_CERT,
|
|
};
|
|
|
|
export var preferTLS = {
|
|
tlsMode: "preferTLS",
|
|
tlsAllowInvalidCertificates: "",
|
|
tlsCertificateKeyFile: SERVER_CERT,
|
|
tlsCAFile: CA_CERT,
|
|
};
|
|
|
|
export var requireTLS = {
|
|
tlsMode: "requireTLS",
|
|
tlsAllowInvalidCertificates: "",
|
|
tlsCertificateKeyFile: SERVER_CERT,
|
|
tlsCAFile: CA_CERT,
|
|
};
|
|
|
|
export var dhparamSSL = {
|
|
tlsMode: "requireTLS",
|
|
tlsAllowInvalidCertificates: "",
|
|
tlsCertificateKeyFile: SERVER_CERT,
|
|
tlsCAFile: CA_CERT,
|
|
setParameter: {"opensslDiffieHellmanParameters": DH_PARAM},
|
|
};
|
|
|
|
// Test if ssl replset configs work
|
|
|
|
export var replShouldSucceed = function (name, opt1, opt2) {
|
|
// try running this file using the given config
|
|
basicReplsetTest(15, opt1, opt2, name);
|
|
};
|
|
|
|
// Test if ssl replset configs fail
|
|
export var replShouldFail = function (name, opt1, opt2) {
|
|
// This will cause an assert.soon() in ReplSetTest to fail. This normally triggers the hang
|
|
// analyzer, but since we do not want to run it on expected timeouts, we temporarily disable it.
|
|
MongoRunner.runHangAnalyzer.disable();
|
|
try {
|
|
assert.throws(() => basicReplsetTest(15, opt1, opt2, name));
|
|
} finally {
|
|
MongoRunner.runHangAnalyzer.enable();
|
|
}
|
|
// Note: this leaves running mongod processes.
|
|
};
|
|
|
|
/**
|
|
* Test that $lookup works with a sharded source collection. This is tested because of
|
|
* the connections opened between mongos/shards and between the shards themselves.
|
|
*/
|
|
export function testShardedLookup(shardingTest) {
|
|
let st = shardingTest;
|
|
assert(st.adminCommand({enableSharding: "lookupTest"}), "error enabling sharding for this configuration");
|
|
assert(
|
|
st.adminCommand({shardCollection: "lookupTest.foo", key: {_id: "hashed"}}),
|
|
"error sharding collection for this configuration",
|
|
);
|
|
|
|
let lookupdb = st.getDB("lookupTest");
|
|
|
|
// insert a few docs to ensure there are documents on multiple shards.
|
|
let fooBulk = lookupdb.foo.initializeUnorderedBulkOp();
|
|
let barBulk = lookupdb.bar.initializeUnorderedBulkOp();
|
|
let lookupShouldReturn = [];
|
|
for (let i = 0; i < 64; i++) {
|
|
fooBulk.insert({_id: i});
|
|
barBulk.insert({_id: i});
|
|
lookupShouldReturn.push({_id: i, bar_docs: [{_id: i}]});
|
|
}
|
|
assert.commandWorked(fooBulk.execute());
|
|
assert.commandWorked(barBulk.execute());
|
|
|
|
let docs = lookupdb.foo
|
|
.aggregate([
|
|
{$sort: {_id: 1}},
|
|
{$lookup: {from: "bar", localField: "_id", foreignField: "_id", as: "bar_docs"}},
|
|
])
|
|
.toArray();
|
|
assert.eq(lookupShouldReturn, docs, "error $lookup failed in this configuration");
|
|
assert.commandWorked(lookupdb.dropDatabase());
|
|
}
|
|
|
|
/**
|
|
* Takes in two mongod/mongos configuration options and runs a basic
|
|
* sharding test to see if they can work together...
|
|
*/
|
|
export function mixedShardTest(options1, options2, shouldSucceed) {
|
|
let authSucceeded = false;
|
|
try {
|
|
// TODO SERVER-14017 is fixed the "enableBalancer" line can be removed.
|
|
// Start ShardingTest with enableBalancer because ShardingTest attempts to turn
|
|
// off the balancer otherwise, which it will not be authorized to do if auth is enabled.
|
|
|
|
// The mongo shell cannot authenticate as the internal __system user in tests that use x509
|
|
// for cluster authentication. Choosing the default value for wcMajorityJournalDefault in
|
|
// ReplSetTest cannot be done automatically without the shell performing such
|
|
// authentication, so in this test we must make the choice explicitly, based on the global
|
|
// test options.
|
|
let wcMajorityJournalDefault;
|
|
if (jsTestOptions().storageEngine == "inMemory") {
|
|
wcMajorityJournalDefault = false;
|
|
} else {
|
|
wcMajorityJournalDefault = true;
|
|
}
|
|
|
|
var st = new ShardingTest({
|
|
mongos: [options1],
|
|
config: 1,
|
|
shards: [options1, options2],
|
|
shouldFailInit: !shouldSucceed,
|
|
other: {
|
|
enableBalancer: true,
|
|
configOptions: options1,
|
|
writeConcernMajorityJournalDefault: wcMajorityJournalDefault,
|
|
},
|
|
});
|
|
|
|
// Create admin user in case the options include auth
|
|
st.admin.createUser({user: "admin", pwd: "pwd", roles: ["root"]});
|
|
st.admin.auth("admin", "pwd");
|
|
|
|
authSucceeded = true;
|
|
|
|
st.stopBalancer();
|
|
|
|
// Test that $lookup works because it causes outgoing connections to be opened
|
|
testShardedLookup(st);
|
|
|
|
// Test mongos talking to config servers
|
|
let r = st.adminCommand({enableSharding: "test", primaryShard: st.shard0.shardName});
|
|
assert.eq(r, true, "error enabling sharding for this configuration");
|
|
|
|
r = st.adminCommand({movePrimary: "test", to: st.shard1.shardName});
|
|
assert.eq(r, true, "error movePrimary failed for this configuration");
|
|
|
|
let db1 = st.getDB("test");
|
|
r = st.adminCommand({shardCollection: "test.col", key: {_id: 1}});
|
|
assert.eq(r, true, "error sharding collection for this configuration");
|
|
|
|
// Test mongos talking to shards
|
|
let bigstr = "#".repeat(1024 * 1024);
|
|
|
|
let bulk = db1.col.initializeUnorderedBulkOp();
|
|
for (let i = 0; i < 128; i++) {
|
|
bulk.insert({_id: i, string: bigstr});
|
|
}
|
|
assert.commandWorked(bulk.execute());
|
|
assert.eq(128, db1.col.count(), "error retrieving documents from cluster");
|
|
|
|
// Split chunk to make it small enough to move
|
|
assert.commandWorked(st.splitFind("test.col", {_id: 0}));
|
|
|
|
// Test shards talking to each other
|
|
r = st.getDB("test").adminCommand({moveChunk: "test.col", find: {_id: 0}, to: st.shard0.shardName});
|
|
assert(r.ok, "error moving chunks: " + tojson(r));
|
|
|
|
db1.col.remove({});
|
|
} catch (e) {
|
|
if (shouldSucceed) throw e;
|
|
// silence error if we should fail...
|
|
print("IMPORTANT! => Test failed when it should have failed...continuing...");
|
|
} finally {
|
|
// Authenticate csrs so ReplSetTest.stopSet() can do db hash check.
|
|
if (authSucceeded && st.configRS) {
|
|
st.configRS.nodes.forEach((node) => {
|
|
node.getDB("admin").auth("admin", "pwd");
|
|
});
|
|
}
|
|
|
|
// This has to be done in order for failure
|
|
// to not prevent future tests from running...
|
|
if (st) {
|
|
if (st.s.fullOptions.clusterAuthMode === "x509") {
|
|
// Index consistency check during shutdown needs a privileged user to auth as.
|
|
const x509User = "CN=client,OU=KernelUser,O=MongoDB,L=New York City,ST=New York,C=US";
|
|
st.s.getDB("$external").createUser({user: x509User, roles: [{role: "__system", db: "admin"}]});
|
|
}
|
|
|
|
st.stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
export function determineSSLProvider() {
|
|
const info = getBuildInfo();
|
|
const ssl = info.openssl === undefined ? "" : info.openssl.running;
|
|
if (/OpenSSL/.test(ssl)) {
|
|
return "openssl";
|
|
} else if (/Apple/.test(ssl)) {
|
|
return "apple";
|
|
} else if (/Windows/.test(ssl)) {
|
|
return "windows";
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function isMacOS(minVersion) {
|
|
function parseVersion(version) {
|
|
// Intentionally leave the end of string unanchored.
|
|
// This allows vesions like: 10.15.7-pl2 or other extra data.
|
|
const v = version.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
assert(v !== null, "Invalid version string '" + version + "'");
|
|
return (v[1] << 16) | (v[2] << 8) | v[3];
|
|
}
|
|
|
|
const macOS = getBuildInfo().macOS;
|
|
if (macOS === undefined) {
|
|
// Not macOS at all.
|
|
return false;
|
|
}
|
|
|
|
if (minVersion === undefined) {
|
|
// Don't care what version, but it's macOS.
|
|
return true;
|
|
}
|
|
|
|
assert(macOS.osProductVersion !== undefined, "Expected getBuildInfo() field 'macOS.osProductVersion' not present");
|
|
return parseVersion(minVersion) <= parseVersion(macOS.osProductVersion);
|
|
}
|
|
|
|
export function requireSSLProvider(required, fn) {
|
|
if (typeof required === "string") {
|
|
required = [required];
|
|
}
|
|
|
|
const provider = determineSSLProvider();
|
|
if (!required.includes(provider)) {
|
|
print("*****************************************************");
|
|
print("Skipping " + tojson(required) + " test because SSL provider is " + provider);
|
|
print("*****************************************************");
|
|
return;
|
|
}
|
|
fn();
|
|
}
|
|
|
|
export function detectDefaultTLSProtocol() {
|
|
const conn = MongoRunner.runMongod({
|
|
tlsMode: "allowTLS",
|
|
tlsCertificateKeyFile: SERVER_CERT,
|
|
tlsDisabledProtocols: "none",
|
|
useLogFiles: true,
|
|
tlsLogVersions: "TLS1_0,TLS1_1,TLS1_2,TLS1_3",
|
|
waitForConnect: true,
|
|
tlsCAFile: CA_CERT,
|
|
});
|
|
|
|
assert.eq(
|
|
0,
|
|
runMongoProgram(
|
|
"mongo",
|
|
"--ssl",
|
|
"--port",
|
|
conn.port,
|
|
"--tlsCertificateKeyFile",
|
|
CLIENT_CERT,
|
|
"--tlsCAFile",
|
|
CA_CERT,
|
|
"--eval",
|
|
";",
|
|
),
|
|
);
|
|
|
|
const res = conn.getDB("admin").serverStatus().transportSecurity;
|
|
|
|
MongoRunner.stopMongod(conn);
|
|
|
|
// Verify that the default protocol is either TLS1.2 or TLS1.3.
|
|
// No supported platform should default to an older protocol version.
|
|
assert.eq(0, res["1.0"]);
|
|
assert.eq(0, res["1.1"]);
|
|
assert.eq(0, res["unknown"]);
|
|
assert.neq(res["1.2"], res["1.3"]);
|
|
|
|
if (res["1.2"].tojson() != NumberLong(0).tojson()) {
|
|
return "TLS1_2";
|
|
} else {
|
|
return "TLS1_3";
|
|
}
|
|
}
|
|
|
|
export function sslProviderSupportsTLS1_0() {
|
|
if (isRHEL8()) {
|
|
const cryptoPolicy = cat("/etc/crypto-policies/config");
|
|
return cryptoPolicy.includes("LEGACY");
|
|
}
|
|
|
|
if (isOpenSSL3orGreater()) {
|
|
return false;
|
|
}
|
|
|
|
return !isDebian() && !isUbuntu2004();
|
|
}
|
|
|
|
export function sslProviderSupportsTLS1_1() {
|
|
if (isRHEL8()) {
|
|
const cryptoPolicy = cat("/etc/crypto-policies/config");
|
|
return cryptoPolicy.includes("LEGACY");
|
|
}
|
|
|
|
if (isOpenSSL3orGreater()) {
|
|
return false;
|
|
}
|
|
|
|
return !isDebian() && !isUbuntu2004();
|
|
}
|
|
|
|
export function isOpenSSL3orGreater() {
|
|
// Windows and macOS do not have "openssl.compiled" in buildInfo but they do have "running"
|
|
const opensslCompiledIn = getBuildInfo().openssl.compiled !== undefined;
|
|
if (!opensslCompiledIn) {
|
|
return false;
|
|
}
|
|
|
|
return opensslVersionAsInt() >= 0x3000000;
|
|
}
|
|
|
|
export function opensslVersionAsInt() {
|
|
const opensslInfo = getBuildInfo().openssl;
|
|
if (!opensslInfo || !opensslInfo.compiled) {
|
|
// openssl undefined if apple or windows
|
|
return undefined;
|
|
}
|
|
|
|
const matches = opensslInfo.running.match(/OpenSSL\s+(\d+)\.(\d+)\.(\d+)([a-z]?)/);
|
|
assert.neq(matches, null, "cannot parse openSSL version from '" + opensslInfo.running + "'");
|
|
|
|
let version = (matches[1] << 24) | (matches[2] << 16) | (matches[3] << 8);
|
|
|
|
return version;
|
|
}
|
|
|
|
export function supportsFIPS() {
|
|
// OpenSSL supports FIPS
|
|
let expectSupportsFIPS = determineSSLProvider() == "openssl";
|
|
|
|
// But OpenSSL supports FIPS only sometimes
|
|
// - Debian does not support FIPS, Fedora 37 does not, Fedora 38 does
|
|
// - Ubuntu only supports FIPS with Ubuntu pro
|
|
if (expectSupportsFIPS) {
|
|
if (isDebian() || isUbuntu()) {
|
|
expectSupportsFIPS = false;
|
|
}
|
|
}
|
|
|
|
return expectSupportsFIPS;
|
|
}
|
|
|
|
export function copyCertificateFile(a, b) {
|
|
if (_isWindows()) {
|
|
// correctly replace forward slashes for Windows
|
|
a = a.replace(/\//g, "\\");
|
|
b = b.replace(/\//g, "\\");
|
|
assert.eq(0, runProgram("cmd.exe", "/c", "copy", a, b));
|
|
return;
|
|
}
|
|
assert.eq(0, runProgram("cp", a, b));
|
|
}
|
|
|
|
export function clientSupportsTLS1_1() {
|
|
if (!sslProviderSupportsTLS1_1()) {
|
|
return false;
|
|
}
|
|
const opensslVersion = opensslVersionAsInt();
|
|
return opensslVersion === undefined ? true : opensslVersion >= 0x100000c; // 1.0.0l
|
|
}
|
|
|
|
export function clientSupportsTLS1_2() {
|
|
const opensslVersion = opensslVersionAsInt();
|
|
return opensslVersion === undefined ? true : opensslVersion >= 0x1000106; // 1.0.1f
|
|
}
|
|
|
|
export function clientSupportsTLS1_3() {
|
|
// SERVER-98279: support tls 1.3 for windows & apple
|
|
if (determineSSLProvider() === "apple" || determineSSLProvider() === "windows") {
|
|
return false;
|
|
}
|
|
const opensslVersion = opensslVersionAsInt();
|
|
return opensslVersion === undefined ? true : opensslVersion >= 0x1010100; // 1.1.1
|
|
}
|