diff --git a/jstests/ssl/ssl_invalid_selector.js b/jstests/ssl/ssl_invalid_selector.js new file mode 100644 index 00000000000..bc19b1f1445 --- /dev/null +++ b/jstests/ssl/ssl_invalid_selector.js @@ -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)); +}); diff --git a/jstests/ssl/ssl_invalid_server_cert.js b/jstests/ssl/ssl_invalid_server_cert.js index b25964b0357..fed9a49fce2 100644 --- a/jstests/ssl/ssl_invalid_server_cert.js +++ b/jstests/ssl/ssl_invalid_server_cert.js @@ -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"); +}); \ No newline at end of file diff --git a/jstests/ssl_linear/ssl_cert_selector.js b/jstests/ssl_linear/ssl_cert_selector.js index 909e410cefd..8680ccb3c9e 100644 --- a/jstests/ssl_linear/ssl_cert_selector.js +++ b/jstests/ssl_linear/ssl_cert_selector.js @@ -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');