mongo/jstests/sslSpecial/cluster_auth_x509_rolling_c...

321 lines
11 KiB
JavaScript

/**
* This test does full rollovers of the X509 auth for cluster membership using configurable
* attributes and extensions. It ensures that it is possible to change the attributes and/or
* extensions used to determine cluster membership while rotating cluster certificates.
*
* @tags: [requires_persistence, requires_replication, requires_fcv_70]
*/
import {ReplSetTest} from "jstests/libs/replsettest.js";
import {determineSSLProvider} from "jstests/ssl/libs/ssl_helpers.js";
if (determineSSLProvider() !== "openssl") {
print("Skipping test, tlsClusterAuthX509 options are only available with OpenSSL");
quit();
}
/**
* This is the path of the original certificate and the CA cert used in all configurations.
*/
// Subject: C=US, ST=New York, L=New York, O=MongoDB, OU=Kernel, CN=server
const originalDNAttributes = "O=MongoDB, OU=Kernel";
const originalCert = "jstests/libs/server.pem";
const originalCACert = "jstests/libs/ca.pem";
const defaultPolicyClusterAuthX509Override = {
attributes: originalDNAttributes,
};
/**
* This is the tlsClusterAuthX509Attributes and path of the certificate which is rolled over to
* after switching to custom attributes.
*/
// Subject: C=US, ST=New York, L=New York City, CN=server, title=foo
const fooTitleDNAttributes = "C=US, ST=New York, L=New York City, title=foo";
const fooTitleDNCert = "jstests/libs/server_title_foo_no_o_ou_dc.pem";
const fooTitleClusterAuthX509Override = {
attributes: fooTitleDNAttributes,
};
/**
* This is the DN of the certificate which is rolled over to after switching the custom attribute
* value.
*/
// Subject: C=US, ST=New York, L=New York City, O=MongoDB, OU=Kernel, CN=server, title=bar
const barTitleDNAttributes = "C=US, ST=New York, L=New York City, title=bar";
const barTitleDNCert = "jstests/libs/server_title_bar.pem";
const barTitleClusterAuthX509Override = {
attributes: barTitleDNAttributes,
};
/**
* This is the path of the certificate containing the cluster membership extension set to 'foo'.
*/
const fooExtensionCert = "jstests/ssl/libs/cluster-member-foo.pem";
const fooExtensionClusterAuthX509Override = {
extensionValue: "foo",
};
/**
* This is the path of the certificate containing the cluster membership extension set to 'bar'.
*/
const barExtensionCert = "jstests/ssl/libs/cluster-member-bar.pem";
const barExtensionClusterAuthX509Override = {
extensionValue: "bar",
};
const rst = new ReplSetTest({
nodes: 3,
waitForKeys: false,
nodeOptions: {
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: originalCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
},
});
rst.startSet();
rst.initiate(
Object.extend(rst.getReplSetConfig(), {
writeConcernMajorityJournalDefault: true,
}),
null,
{initiateWithDefaultElectionTimeout: true, allNodesAuthorizedToRunRSGetStatus: false},
);
// Create a user to login as when auth is enabled later
rst.getPrimary()
.getDB("admin")
.createUser({user: "root", pwd: "root", roles: ["root"]}, {w: 3});
rst.nodes.forEach((node) => {
assert(node.getDB("admin").auth("root", "root"));
});
// Future connections should authenticate immediately on connecting so that replSet actions succeed.
const originalAwaitConnection = MongoRunner.awaitConnection;
MongoRunner.awaitConnection = function (args) {
const conn = originalAwaitConnection(args);
assert(conn.getDB("admin").auth("root", "root"));
return conn;
};
// This will rollover the cluster to a new config in a rolling fashion. It will return when
// there is a primary and we are able to write to it.
function rolloverConfig(newConfig) {
function restart(node) {
const nodeId = rst.getNodeId(node);
rst.stop(nodeId);
const configId = "n" + nodeId;
rst.nodeOptions[configId] = newConfig;
rst.start(nodeId, {remember: false}, true, true);
rst.awaitSecondaryNodes();
}
rst.nodes.forEach(function (node) {
restart(node);
});
assert.soon(() => {
let primary = rst.getPrimary();
assert.commandWorked(primary.getDB("admin").runCommand({hello: 1}));
assert.commandWorked(primary.getDB("test").a.insert({a: 1, str: "TESTTESTTEST"}));
return true;
});
}
// Scenario 1: From no tlsClusterAuthX509 to tlsClusterAuthX509Attributes.
jsTestLog("Transitioning from DC/O/OU only to DC/O/OU and custom subject DN attributes");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: originalCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509Attributes: fooTitleDNAttributes,
setParameter: {
tlsClusterAuthX509Override: tojson(defaultPolicyClusterAuthX509Override),
},
});
jsTestLog("Rotating cluster member certificates to use subject DNs with custom attributes");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: fooTitleDNCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509Attributes: fooTitleDNAttributes,
setParameter: {
tlsClusterAuthX509Override: tojson(defaultPolicyClusterAuthX509Override),
},
});
jsTestLog("Removing DC/O/OU policy from override");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: fooTitleDNCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509Attributes: fooTitleDNAttributes,
});
jsTestLog("SUCCESS - certificate rotation from DC/O/OU certs to custom subject DN attribute-based certs");
// Scenario 2: From tlsClusterAuthX509Attributes: 'title=foo' to tlsClusterAuthX509Attributes:
// 'title=bar'
jsTestLog(
"Transitioning from custom subject DN attributes with 'title=foo' to custom subject DN attributes with 'title=bar'",
);
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: fooTitleDNCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509Attributes: barTitleDNAttributes,
setParameter: {
tlsClusterAuthX509Override: tojson(fooTitleClusterAuthX509Override),
},
});
jsTestLog("Rotating cluster member certificates to use certificates with 'title=bar'");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: barTitleDNCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509Attributes: barTitleDNAttributes,
setParameter: {
tlsClusterAuthX509Override: tojson(fooTitleClusterAuthX509Override),
},
});
jsTestLog("Removing 'title=foo' attribute from override");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: barTitleDNCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509Attributes: barTitleDNAttributes,
});
jsTestLog("SUCCESS - certificate rotation from one set of custom subject DN attributes to another");
// Scenario 3: From tlsClusterAuthX509Attributes to tlsClusterAuthX509ExtensionValue.
jsTestLog("Transitioning from custom attributes to certificate extension");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: barTitleDNCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509ExtensionValue: "foo",
setParameter: {
tlsClusterAuthX509Override: tojson(barTitleClusterAuthX509Override),
},
});
jsTestLog("Rotating cluster member certificates to use certificates with extension set to 'foo'");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: fooExtensionCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509ExtensionValue: "foo",
setParameter: {
tlsClusterAuthX509Override: tojson(barTitleClusterAuthX509Override),
},
});
jsTestLog("Removing 'title=bar' attribute from override");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: fooExtensionCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509ExtensionValue: "foo",
});
jsTestLog("SUCCESS - certificate rotation from custom subject DN attributes to custom extension value");
// Scenario 4: From tlsClusterAuthX509ExtensionValue: 'foo' to tlsClusterAuthX509ExtensionValue:
// 'bar'
jsTestLog("Transitioning from one custom extension value to another");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: fooExtensionCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509ExtensionValue: "bar",
setParameter: {
tlsClusterAuthX509Override: tojson(fooExtensionClusterAuthX509Override),
},
});
jsTestLog("Rotating cluster member certificates to use certificates with extension set to 'bar'");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: barExtensionCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509ExtensionValue: "bar",
setParameter: {
tlsClusterAuthX509Override: tojson(fooExtensionClusterAuthX509Override),
},
});
jsTestLog("Removing 'foo' extension from override");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: barExtensionCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
tlsClusterAuthX509ExtensionValue: "bar",
});
jsTestLog("SUCCESS - certificate rotation from one custom extension value to another");
// Scenario 5: From tlsClusterAuthExtensionValue back to default.
jsTestLog("Transitioning custom extension value to custom subject DN attributes");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: fooExtensionCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
setParameter: {
tlsClusterAuthX509Override: tojson(barExtensionClusterAuthX509Override),
},
});
jsTestLog("Rotating cluster member certificates to use certificates with DC/O/OU");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: originalCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
setParameter: {
tlsClusterAuthX509Override: tojson(barExtensionClusterAuthX509Override),
},
});
// This works because the original cert meets the default DC/O/OU criteria.
jsTestLog("Removing 'bar' extension from override and the custom attributes");
rolloverConfig({
tlsMode: "preferTLS",
clusterAuthMode: "x509",
tlsCertificateKeyFile: originalCert,
tlsCAFile: originalCACert,
tlsAllowInvalidHostnames: "",
});
jsTestLog("SUCCESS - certificate rotation from extension value back to default");
rst.stopSet();