SERVER-94769 Expand testing of certificate selectors (#27942)

GitOrigin-RevId: edd87e3868164bdab0e9f8eded09539f88b74ecd
This commit is contained in:
Erwin Pe 2024-10-10 10:48:57 -04:00 committed by MongoDB Bot
parent a304082e75
commit 0080b6e41c
3 changed files with 220 additions and 37 deletions

View File

@ -0,0 +1,48 @@
/**
* Tests various failure cases when using certificate selectors on Windows.
*/
import {
requireSSLProvider,
TRUSTED_SERVER_CERT,
} from "jstests/ssl/libs/ssl_helpers.js";
const notFoundError = "failed to find cert";
const badValueError = "Invalid certificate selector value";
const startupFailureTestCases = [
{selector: `thumbprint=DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF`, error: notFoundError},
{selector: `subject=Unknown Test Client`, error: notFoundError},
{selector: `thumbprint=LOL`, error: badValueError},
{
keyFile: TRUSTED_SERVER_CERT,
clusterSelector: `thumbprint=DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF`,
error: notFoundError
},
{
keyFile: TRUSTED_SERVER_CERT,
clusterSelector: `subject=Unknown Test Client`,
error: notFoundError
},
{keyFile: TRUSTED_SERVER_CERT, clusterSelector: `thumbprint=LOL`, error: badValueError},
];
function testStartupFails(testCase) {
jsTestLog(`Running testStartupFails with test case: ${tojson(testCase)}`);
const opts = {
tlsMode: 'requireTLS',
tlsCertificateKeyFile: testCase.keyFile,
tlsCertificateSelector: testCase.selector,
tlsClusterCertificateSelector: testCase.clusterSelector,
tlsAllowInvalidHostnames: "",
setParameter: {tlsUseSystemCA: true},
waitForConnect: true,
};
clearRawMongoProgramOutput();
assert.throws(() => {
MongoRunner.runMongod(opts);
});
assert(rawMongoProgramOutput().includes(testCase.error));
}
requireSSLProvider('windows', function() {
startupFailureTestCases.forEach(test => testStartupFails(test));
});

View File

@ -1,5 +1,7 @@
// Test invalid SSL keyfile settings.
import {requireSSLProvider} from "jstests/ssl/libs/ssl_helpers.js";
function runTest(name, config, expect) {
jsTest.log('Running test: ' + name);
clearRawMongoProgramOutput();
@ -38,3 +40,49 @@ runTest('expired', expired, validityMessage);
// Test that startup fails with no certificate at all.
const needKeyFile = 'need tlsCertificateKeyFile or certificateSelector when TLS is enabled';
runTest('no-key-file', {tlsMode: 'requireTLS', tlsCAFile: 'jstests/libs/ca.pem'}, needKeyFile);
// Test that startup also fails if only tlsClusterFile is provided
runTest('cluster-file-only',
{
tlsMode: 'requireTLS',
tlsCAFile: 'jstests/libs/ca.pem',
tlsClusterFile: 'jstests/libs/client.pem'
},
needKeyFile);
requireSSLProvider(['windows', 'apple'], function() {
const selector = "subject=Trusted Kernel Test Server";
// Test that startup also fails if only tlsClusterSelector is provided
runTest('cluster-selector-only',
{
tlsMode: 'requireTLS',
tlsCAFile: 'jstests/libs/ca.pem',
tlsClusterCertificateSelector: selector,
},
needKeyFile);
// Test that startup fails if both key file and cert selector are provided
const keyFileAndSelector = {
tlsMode: 'requireTLS',
tlsCAFile: 'jstests/libs/ca.pem',
tlsCertificateKeyFile: 'jstests/libs/client.pem',
tlsCertificateSelector: selector,
};
runTest(
'keyfile-and-selector',
keyFileAndSelector,
"net.tls.certificateKeyFile is not allowed when net.tls.certificateSelector is specified");
// Test that startup fails if both cluster file and cluster cert selector are provided
const clusterFileAndSelector = {
tlsMode: 'requireTLS',
tlsCAFile: 'jstests/libs/ca.pem',
tlsClusterFile: 'jstests/libs/client.pem',
tlsClusterCertificateSelector: selector,
};
runTest(
'cluster-keyfile-and-selector',
clusterFileAndSelector,
"net.tls.clusterFile is not allowed when net.tls.clusterCertificateSelector is specified");
});

View File

@ -1,15 +1,130 @@
/**
* Validate that the shell can load certificates from the certificate store and connect to the
* server.
* Tests that the server can load its keys and certificates from the certificate store
* through a selector, and that the correct keys are used for ingress/egress connections.
*/
import {getPython3Binary} from "jstests/libs/python.js";
import {ReplSetTest} from "jstests/libs/replsettest.js";
import {
requireSSLProvider,
TRUSTED_CA_CERT,
TRUSTED_SERVER_CERT
TRUSTED_CLIENT_CERT,
TRUSTED_SERVER_CERT,
} from "jstests/ssl/libs/ssl_helpers.js";
const clientThumbprint = cat('jstests/libs/trusted-client.pem.digest.sha1');
const serverThumbprint = cat('jstests/libs/trusted-server.pem.digest.sha1');
const CLIENT = 'CN=Trusted Kernel Test Client,OU=Kernel,O=MongoDB,L=New York City,ST=New York,C=US';
const SERVER = 'CN=Trusted Kernel Test Server,OU=Kernel,O=MongoDB,L=New York City,ST=New York,C=US';
const testCases = [
{
selector: `thumbprint=${serverThumbprint}`,
expectIngressKeyUsed: SERVER,
expectEgressKeyUsed: SERVER
},
{
selector: `thumbprint=${serverThumbprint}`,
clusterSelector: `thumbprint=${clientThumbprint}`,
expectIngressKeyUsed: SERVER,
expectEgressKeyUsed: CLIENT,
},
{
keyFile: TRUSTED_SERVER_CERT,
clusterSelector: `thumbprint=${clientThumbprint}`,
expectIngressKeyUsed: SERVER,
expectEgressKeyUsed: CLIENT,
},
// TODO: SERVER-95653 uncomment this test case once bug is fixed
/*{
selector: `thumbprint=${serverThumbprint}`,
clusterFile: TRUSTED_CLIENT_CERT,
expectIngressKeyUsed: SERVER,
expectEgressKeyUsed: CLIENT,
},*/
{
selector: 'subject=Trusted Kernel Test Server',
clusterSelector: 'subject=Trusted Kernel Test Client',
expectIngressKeyUsed: SERVER,
expectEgressKeyUsed: CLIENT,
},
];
function testServerSelectorKeyUsage(testCase) {
jsTestLog(`Running testServerSelectorKeyUsage with test case: ${tojson(testCase)}`);
// Start a replica set with one mongod configured with the test case key file parameters
// and a system CA store containing trusted-ca.pem.
const rst = new ReplSetTest({nodes: 1});
rst.startSet({
tlsMode: 'requireTLS',
tlsCertificateKeyFile: testCase.keyFile,
tlsCertificateSelector: testCase.selector,
tlsClusterFile: testCase.clusterFile,
tlsClusterCertificateSelector: testCase.clusterSelector,
tlsAllowInvalidHostnames: "",
tlsAllowConnectionsWithoutCertificates: "",
waitForConnect: true,
setParameter: {tlsUseSystemCA: true},
});
rst.initiate();
rst.awaitReplication();
let conn = rst.getPrimary();
jsTestLog("Testing server uses correct key on ingress");
assert.soon(function() {
return runMongoProgram('mongo',
'--tls',
'--tlsAllowInvalidHostnames',
'--tlsCAFile',
TRUSTED_CA_CERT,
'--tlsCertificateKeyFile',
TRUSTED_CLIENT_CERT,
'--port',
conn.port,
'--eval',
'quit()') === 0;
}, "mongo did not initialize properly");
assert.soon(
() => {
const log = rawMongoProgramOutput();
return log.search(testCase.expectIngressKeyUsed) !== -1;
},
`logfile did not contain expected peer certificate info: ${
testCase.expectIngressKeyUsed}.\n` +
"Log File Contents\n==============================\n" + rawMongoProgramOutput() +
"\n==============================\n");
jsTestLog("Testing server uses correct key on egress");
// Add new node to test the other node's egress key
let otherNode = rst.add({
tlsMode: 'requireTLS',
tlsCertificateKeyFile: TRUSTED_SERVER_CERT,
tlsCAFile: TRUSTED_CA_CERT,
tlsAllowInvalidHostnames: "",
setParameter: {tlsWithholdClientCertificate: true},
waitForConnect: true,
});
jsTestLog("Reinitiating replica set with one additional node");
rst.reInitiate();
rst.awaitSecondaryNodes();
assert.commandWorked(otherNode.adminCommand({clearLog: 'global'}));
// Verify node 1 can now connect to node 2
jsTestLog("Forcing egress connection with replSetTestEgress...");
assert.commandWorked(conn.adminCommand({replSetTestEgress: 1}));
checkLog.containsRelaxedJson(
otherNode, 6723802, {peerSubjectName: testCase.expectEgressKeyUsed});
jsTestLog("Stopping the replica set...");
rst.stopSet();
}
requireSSLProvider('windows', function() {
if (_isWindows()) {
assert.eq(0,
@ -19,43 +134,15 @@ requireSSLProvider('windows', function() {
runProgram("certutil.exe", "-addstore", "-f", "Root", TRUSTED_CA_CERT);
// Import a pfx file since it contains both a cert and private key and is easy to import
// via command line.
runProgram("certutil.exe",
"-importpfx",
"-f",
"-p",
"qwerty",
"jstests\\libs\\trusted-client.pfx");
const importPfx = function(pfxFile) {
return runProgram("certutil.exe", "-importpfx", "-f", "-p", "qwerty", pfxFile);
};
assert.eq(0, importPfx("jstests\\libs\\trusted-client.pfx"));
assert.eq(0, importPfx("jstests\\libs\\trusted-server.pfx"));
}
try {
const mongod = MongoRunner.runMongod({
tlsMode: 'requireTLS',
tlsCertificateKeyFile: TRUSTED_SERVER_CERT,
setParameter: {tlsUseSystemCA: true},
useHostname: false,
});
const testWithCert = function(certSelector) {
jsTest.log(`Testing with SSL cert ${certSelector}`);
const conn = new Mongo(mongod.host, undefined, {
tls: {
certificateSelector: certSelector,
}
});
assert.commandWorked(conn.getDB('admin').runCommand({buildinfo: 1}));
};
const trusted_client_thumbprint = cat('jstests/libs/trusted-client.pem.digest.sha1');
assert.doesNotThrow(function() {
testWithCert("thumbprint=" + trusted_client_thumbprint);
});
assert.doesNotThrow(function() {
testWithCert("subject=Trusted Kernel Test Client");
});
MongoRunner.stopMongod(mongod);
testCases.forEach(test => testServerSelectorKeyUsage(test));
} finally {
if (_isWindows()) {
const trusted_ca_thumbprint = cat('jstests/libs/trusted-ca.pem.digest.sha1');