summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2023-03-21 16:34:12 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2023-04-03 18:55:21 +0000
commitdc2ff7aa916ef1412ea939b0eaf49a063665d309 (patch)
tree1008fdea299ed3f78c22e945a214d0a84ea32051
parentfe3275102df92e99c7e83a860c63e6ac54218f35 (diff)
downloadmongo-dc2ff7aa916ef1412ea939b0eaf49a063665d309.tar.gz
SERVER-74999 Determine cluster membership based on X.509 extension
-rw-r--r--jstests/ssl/cluster_member.js116
-rw-r--r--jstests/ssl/libs/cluster-member-bar.pem58
-rw-r--r--jstests/ssl/libs/cluster-member-bar.pem.digest.sha11
-rw-r--r--jstests/ssl/libs/cluster-member-bar.pem.digest.sha2561
-rw-r--r--jstests/ssl/libs/cluster-member-foo-alt-rdn.pem58
-rw-r--r--jstests/ssl/libs/cluster-member-foo-alt-rdn.pem.digest.sha11
-rw-r--r--jstests/ssl/libs/cluster-member-foo-alt-rdn.pem.digest.sha2561
-rw-r--r--jstests/ssl/libs/cluster-member-foo.pem58
-rw-r--r--jstests/ssl/libs/cluster-member-foo.pem.digest.sha11
-rw-r--r--jstests/ssl/libs/cluster-member-foo.pem.digest.sha2561
-rw-r--r--jstests/ssl/x509/README1
-rw-r--r--jstests/ssl/x509/certs.yml32
-rwxr-xr-xjstests/ssl/x509/mkcert.py25
-rw-r--r--src/mongo/db/commands/authentication_commands.cpp23
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp2
-rw-r--r--src/mongo/util/net/SConscript1
-rw-r--r--src/mongo/util/net/ssl_manager.cpp5
-rw-r--r--src/mongo/util/net/ssl_manager.h10
-rw-r--r--src/mongo/util/net/ssl_manager_openssl.cpp83
-rw-r--r--src/mongo/util/net/ssl_options.h13
-rw-r--r--src/mongo/util/net/ssl_options_server.cpp13
-rw-r--r--src/mongo/util/net/ssl_options_server.idl9
-rw-r--r--src/mongo/util/net/ssl_peer_info.h11
23 files changed, 484 insertions, 40 deletions
diff --git a/jstests/ssl/cluster_member.js b/jstests/ssl/cluster_member.js
new file mode 100644
index 00000000000..670bbedd97c
--- /dev/null
+++ b/jstests/ssl/cluster_member.js
@@ -0,0 +1,116 @@
+// Test configuration parameter tlsClusterAuthX509ExtensionValue
+// aka: net.tls.clusterAuthX509.extensionValue
+// @tags: [ featureFlagConfigurableX509ClusterAuthn ]
+
+(function() {
+'use strict';
+
+load('jstests/ssl/libs/ssl_helpers.js');
+if (determineSSLProvider() !== "openssl") {
+ jsTest.log('Test requires openssl based TLS support');
+ return;
+}
+
+// Fails when used without clusterAuthMode == 'X509'
+{
+ const opts = {auth: '', tlsClusterAuthX509ExtensionValue: 'foo'};
+ const errmsg =
+ 'net.tls.clusterAuthX509.extensionValue requires a clusterAuthMode which allows for usage of X509';
+
+ jsTest.log('No clusterAuthMode set');
+ clearRawMongoProgramOutput();
+ assert.throws(() => MongoRunner.runMongod(opts));
+ assert(rawMongoProgramOutput().includes(errmsg));
+
+ jsTest.log('clusterAuthMode == keyFile');
+ clearRawMongoProgramOutput();
+ opts.clusterAuthMode = 'keyFile';
+ assert.throws(() => MongoRunner.runMongod(opts));
+ assert(rawMongoProgramOutput().includes(errmsg));
+}
+
+function authAndDo(port, cert, cmd = ';') {
+ jsTest.log('Connecting to localhost using cert: ' + cert);
+ function x509auth(db) {
+ const ext = db.getSiblingDB('$external');
+ assert.commandWorked(ext.runCommand({authenticate: 1, mechanism: 'MONGODB-X509'}));
+ return ext.adminCommand({connectionStatus: 1});
+ }
+ clearRawMongoProgramOutput();
+ const shell = runMongoProgram('mongo',
+ '--host',
+ 'localhost',
+ '--port',
+ port,
+ '--tls',
+ '--tlsCAFile',
+ 'jstests/libs/ca.pem',
+ '--tlsCertificateKeyFile',
+ cert,
+ '--eval',
+ x509auth + ' x509auth(db); ' + cmd);
+ assert.eq(shell, 0);
+}
+
+function runTest(conn) {
+ const SERVER_RDN = 'CN=server,OU=Kernel,O=MongoDB,L=New York City,ST=New York,C=US';
+ const SERVER = 'jstests/libs/server.pem';
+ const FOO_MEMBER = 'jstests/ssl/libs/cluster-member-foo.pem';
+ const BAR_MEMBER = 'jstests/ssl/libs/cluster-member-bar.pem';
+ const FOO_MEMBER_ALT = 'jstests/ssl/libs/cluster-member-foo-alt-rdn.pem';
+ const FOO_MEMBER_ALT_RDN = 'CN=Doer,OU=Business,O=Company,L=Fakesville,ST=Example,C=ZZ';
+
+ const admin = conn.getDB('admin');
+ const ext = conn.getDB('$external');
+
+ // Ensure no localhost auth bypass available.
+ assert.commandWorked(admin.runCommand({createUser: 'admin', pwd: 'admin', roles: ['root']}));
+ assert(admin.auth('admin', 'admin'));
+
+ // Connect using server.pem which has the same RDN, but no custom extension.
+ // This will result in an unknown user condition because we are
+ // not recognized as a cluster member.
+ assert.throws(() => authAndDo(conn.port, SERVER));
+
+ const insertCmd = 'assert.writeOK(db.getSiblingDB("test").mycoll.insert({x:1}));';
+ // Connect using same RDN WITH custom extension.
+ authAndDo(conn.port, FOO_MEMBER, insertCmd);
+
+ // Connect using cert with membership extension, but wrong value.
+ assert.throws(() => authAndDo(conn.port, BAR_MEMBER));
+
+ // Connect using cert with right membership, but different RDN (allowed).
+ authAndDo(conn.port, FOO_MEMBER_ALT, insertCmd);
+
+ // Create a user who would have been a cluster member under name based rules.
+ // We should have basic privs, testing with read but not write.
+ const readCmd = 'db.getSiblingDB("test").mycoll.find({});';
+ const readRoles = [{db: 'admin', role: 'readAnyDatabase'}];
+ assert.commandWorked(ext.runCommand({createUser: SERVER_RDN, roles: readRoles}));
+ authAndDo(conn.port, SERVER, readCmd);
+ assert.throws(() => authAndDo(conn.port, SERVER, insertCmd));
+
+ // Create a user with FOO_MEMBER_ALT's RDN to validate enforceUserClusterSeparation.
+ authAndDo(conn.port, FOO_MEMBER_ALT);
+ assert.commandWorked(ext.runCommand({createUser: FOO_MEMBER_ALT_RDN, roles: readRoles}));
+ assert.throws(() => authAndDo(conn.port, FOO_MEMBER_ALT));
+}
+
+{
+ const opts = {
+ auth: '',
+ tlsMode: 'requireTLS',
+ tlsCertificateKeyFile: 'jstests/ssl/libs/cluster-member-foo.pem',
+ tlsCAFile: 'jstests/libs/ca.pem',
+ clusterAuthMode: 'x509',
+ tlsClusterAuthX509ExtensionValue: 'foo',
+ setParameter: {
+ enforceUserClusterSeparation: 'true',
+ },
+ };
+
+ const mongod = MongoRunner.runMongod(opts);
+ runTest(mongod);
+ MongoRunner.stopMongod(mongod);
+}
+})();
diff --git a/jstests/ssl/libs/cluster-member-bar.pem b/jstests/ssl/libs/cluster-member-bar.pem
new file mode 100644
index 00000000000..27b9f533afd
--- /dev/null
+++ b/jstests/ssl/libs/cluster-member-bar.pem
@@ -0,0 +1,58 @@
+# Autogenerated file, do not edit.
+# Generate using jstests/ssl/x509/mkcert.py --config jstests/ssl/x509/certs.yml cluster-member-bar.pem
+#
+# A server certificate with the mongoClusterMembership extension with a value of bar
+-----BEGIN CERTIFICATE-----
+MIIEejCCA2KgAwIBAgIEWTZe1DANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV
+UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO
+BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVs
+IFRlc3QgQ0EwHhcNMjMwMzE1MTU0MTU2WhcNMjUwNjE2MTU0MTU2WjBsMQswCQYD
+VQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENp
+dHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEPMA0GA1UEAwwG
+c2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu9lxe3kPI/Hk
+ZTloS7DbcXxJOfvz6+SXkEmsQeWh8asKYl1vMj9trkwZonpUvdGy3u32aQ2OttBw
+ajE6TWpNxBpLPlksrpYcvOZBHROvVek5jkQIjCFY2a/xoD6bNSUKfjXiBVl3ahDy
+b7cg6oGC6X3xe+Sa9Zj7HhiOY0LaoRZr0PSuIkxBxboMpghEv/Mq0YFoxhyuS/XI
+9HGcIiipp9sVZNhiP4yZPfqruSB4ACYNVjDJTbNAgYhlCT8W1lHnO2pc2BRTbIj5
+NTbjcGeIjLzRf5ARzPF1XCknnECmszJFLHCONRG/k8Z8i87vIBqf83jo0y5W0GK7
+t5hTfDci3wIDAQABo4IBGjCCARYwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwHQYD
+VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQKCntdQt8iZZ8C
+mEjgcbjEJZhO/DCBiwYDVR0jBIGDMIGAoXikdjB0MQswCQYDVQQGEwJVUzERMA8G
+A1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNVBAoM
+B01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVsIFRlc3Qg
+Q0GCBHvUrJMwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMBQGCysGAQQBgo4p
+AgECBAUMA2JhcjANBgkqhkiG9w0BAQsFAAOCAQEAjY+PUCpyNisWgM82A+eN+ipq
+xGUJE97j7ikoGTzFYeGJ4ANYXxL9MlDakZjv+fNXy+ngSDqBGvZzN/mIIa72Phkz
+Q/L+jLSH2HUZL8/ptTnf6M2mdYwuABSBE7+KG6emb1ywUudHFztzxZZDlSE+JVCO
+F39amF2TMnzNqb1hBOz07RdZKBqEpo3PrL8MFlZxuN9i6YHp5b5Og+Li/ktWMaBv
+6kZ+drMK3E+ku5QRPTARXuGXf7vFT+eC5Rk/jTi3prwveg7n4WKmecS6BuzVlLjt
+kUIe0RqTS3HqkFqtb/mb4Dc1Bbi5MD86CZ1JNkWT1m8LozsAnKhfnrHbUViPdg==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC72XF7eQ8j8eRl
+OWhLsNtxfEk5+/Pr5JeQSaxB5aHxqwpiXW8yP22uTBmielS90bLe7fZpDY620HBq
+MTpNak3EGks+WSyulhy85kEdE69V6TmORAiMIVjZr/GgPps1JQp+NeIFWXdqEPJv
+tyDqgYLpffF75Jr1mPseGI5jQtqhFmvQ9K4iTEHFugymCES/8yrRgWjGHK5L9cj0
+cZwiKKmn2xVk2GI/jJk9+qu5IHgAJg1WMMlNs0CBiGUJPxbWUec7alzYFFNsiPk1
+NuNwZ4iMvNF/kBHM8XVcKSecQKazMkUscI41Eb+TxnyLzu8gGp/zeOjTLlbQYru3
+mFN8NyLfAgMBAAECggEAJDf8pW3l+Ww+OTYkYdOru+nWxJNLqIPepTdPOzVnUA1G
+Z0jUk7+fCigqGSW1CRRRhKIlDIRMq/rscc0kDKEedV0MfOz8rHzM9a7/hvewqsPZ
+EREVBM+5Ld+6msb3bfvCVitVdOqXF6BE3j1U32IxN4vM77JYHlpssJTTf1f4h25S
+qgZb+b8D+J54nuxiB6Q54WYSnzCMMCGmtIVceS/Itc6CVxLUl86WJUpoZwiQTUxX
+sXJoYDOahJLPnuwOT+tNzlXHiFLQ4kB1M7mYFjNhurj9X9YSOR4fxWLqy4IJGQTH
+oehCLuGBPVI578gPXim1QrAf3hG8to8ViFVgeNWygQKBgQDVCljM8t9dVZEm+F1r
+e4lDb6rauycK9zAwQLKzgRAAxAT7OMs0WyuCMWHD3ZZDjtpSmb5m1kP22OXy0BRB
+G08xFUtNAzsQkH5RBQHScec8RJ6bFgLQ99hd++Gc7jgp0XxuxoEvMKm1bEtbfoUu
+6AoCuRf/kTqL3PY1HP5Yt7L9UQKBgQDhuqzwLNpCXnGk+0gf7qEkIMPqZqFzqrdb
+eWWVmC1JO4kc5TNGJnEtVlS32ow+iN1qjZBptQY0/Ykohj3F2PZHF93zQ2sbDFAy
+7BQWbYjO9DAzemtBqMNVtFe+5oABzbyhqzmZtNVG4C2XKkJkw0RUCtyvmE5/0obH
+xT/t//RRLwKBgBO8JKO/r+9eeNbKVSUayYlks8gVZDWA1obxx1wXjZr0jZ2UEkbk
+VzB1UKArS7swZYsXUOsH2D3qs8p9ehLZ68kZNuOIdBVBvWHV++g5wvjzRloJfPNM
+sk9qgOjfrHY7QLKmUttDP8VdpdFw8/d3aU39RXrYQjsomeorqGghhEQxAoGAfqIZ
+LswazazqGGIX/kIDCJ+RCUj2PkuBfcHG6XtrvG+35gv3Dd23FHYgJNxoXRSvEn3E
+jGjPyJ6Leb6FnR6wWwXasAQcbBomS8sBIevlGiUHfXmp/jXND6GSsDfjjB99OT0z
+nTVDiPVu3iUJBjo9dOB7Gc9aCn9yuVPBH6W9zGUCgYB6ew5VTrbwWO0KKYpiM6aN
+ZXiYTMMcaJOjlmyBYtWYNdRusshh4i+ICF/eV9CXtW1cQcXGCM5gB0Py3r9ugSWk
+xQDVotkSUP3GswftggX17jEyjTQKMVtDDATyIxU3XohAWeNQYuRV98+A41lFqE0v
+GXbb4Dhv87TuIEAIsHCV5Q==
+-----END PRIVATE KEY-----
diff --git a/jstests/ssl/libs/cluster-member-bar.pem.digest.sha1 b/jstests/ssl/libs/cluster-member-bar.pem.digest.sha1
new file mode 100644
index 00000000000..53b59e4aa35
--- /dev/null
+++ b/jstests/ssl/libs/cluster-member-bar.pem.digest.sha1
@@ -0,0 +1 @@
+D498E0A2F8CF71D5349BB91E11E6D05350C88A3C \ No newline at end of file
diff --git a/jstests/ssl/libs/cluster-member-bar.pem.digest.sha256 b/jstests/ssl/libs/cluster-member-bar.pem.digest.sha256
new file mode 100644
index 00000000000..a0ce1bd86a8
--- /dev/null
+++ b/jstests/ssl/libs/cluster-member-bar.pem.digest.sha256
@@ -0,0 +1 @@
+F957FEBEEC5C9C08C2500C17432B47635C12101E4DD42183FD333542ACD0AE5D \ No newline at end of file
diff --git a/jstests/ssl/libs/cluster-member-foo-alt-rdn.pem b/jstests/ssl/libs/cluster-member-foo-alt-rdn.pem
new file mode 100644
index 00000000000..9b4a86dfd17
--- /dev/null
+++ b/jstests/ssl/libs/cluster-member-foo-alt-rdn.pem
@@ -0,0 +1,58 @@
+# Autogenerated file, do not edit.
+# Generate using jstests/ssl/x509/mkcert.py --config jstests/ssl/x509/certs.yml cluster-member-foo-alt-rdn.pem
+#
+# A server certificate with the mongoClusterMembership extension with a value of foo, but an unrelated RDN
+-----BEGIN CERTIFICATE-----
+MIIEdjCCA16gAwIBAgIEGs/cgTANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV
+UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO
+BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVs
+IFRlc3QgQ0EwHhcNMjMwMzE1MTU0MjEwWhcNMjUwNjE2MTU0MjEwWjBoMQswCQYD
+VQQGEwJaWjEQMA4GA1UECAwHRXhhbXBsZTETMBEGA1UEBwwKRmFrZXN2aWxsZTEQ
+MA4GA1UECgwHQ29tcGFueTERMA8GA1UECwwIQnVzaW5lc3MxDTALBgNVBAMMBERv
+ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCKB1iyc78amtXCaOfh
+3wZ7jidmiLI0IMGk1KuGnUzyoRlX6PlFKm+I5/rbyVgVK0MEKIJU1rxrxBwwJyW/
+/D1NOH1FTcKk+FnkBs7T1iwct+2OocMArQVcavFayqcqubxvWFztjBNxCoh578OH
+u7BBqG3iXu8HvWivm+FAkqYWNk8M0us5Ui/yQShRXcPRTYqAFyTatlcesijGMKEA
+J1AE4xgVNmJI88qoUmS7ftbFW0B53ru7aJKtQ9xGcu1EtDEUSXpJAVmmSDmuAF0L
+ZaGYUd/zerCweOgkmy0rEoFQPKKb9Ib9PJ4vo4VN6RKYt3DzDxpqu58pZMVJxxn+
+UjmnAgMBAAGjggEaMIIBFjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIFoDAdBgNVHSUE
+FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFOHQkRJH13hAyapsryfr
+spM9JebhMIGLBgNVHSMEgYMwgYCheKR2MHQxCzAJBgNVBAYTAlVTMREwDwYDVQQI
+DAhOZXcgWW9yazEWMBQGA1UEBwwNTmV3IFlvcmsgQ2l0eTEQMA4GA1UECgwHTW9u
+Z29EQjEPMA0GA1UECwwGS2VybmVsMRcwFQYDVQQDDA5LZXJuZWwgVGVzdCBDQYIE
+e9SskzAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwFAYLKwYBBAGCjikCAQIE
+BQwDZm9vMA0GCSqGSIb3DQEBCwUAA4IBAQBYpHCMUlGWm803moqfVGTkU/xGlPQd
+hpMtmcf8GsSlDKmGXW335+95f5emZV7WmfKqaolAI0rjA7/sI98QuiqcloCEhSE9
+eS3jEuEEeDvySwnqKgz45eTXyjqjpH746uIXju427xQtr4z6gYYQZBls1ozEFrYp
+MfQXZJqVm6Kodg72LNrjToWeuNGkeGtGikyqXAlCM3/s7FsapuN89KjNsQv1p8e0
+LTXnJAm/5yxcuQyxWq87pta11IS89RylDDwmMMBIJWwAE07O+zH/1OC6yevapKn7
+rZw/gYz4uhbmlzsQVJrHdsaZ8Dr6+Enpz5X9CmNRqijk5dbzaSS9wvbu
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCKB1iyc78amtXC
+aOfh3wZ7jidmiLI0IMGk1KuGnUzyoRlX6PlFKm+I5/rbyVgVK0MEKIJU1rxrxBww
+JyW//D1NOH1FTcKk+FnkBs7T1iwct+2OocMArQVcavFayqcqubxvWFztjBNxCoh5
+78OHu7BBqG3iXu8HvWivm+FAkqYWNk8M0us5Ui/yQShRXcPRTYqAFyTatlcesijG
+MKEAJ1AE4xgVNmJI88qoUmS7ftbFW0B53ru7aJKtQ9xGcu1EtDEUSXpJAVmmSDmu
+AF0LZaGYUd/zerCweOgkmy0rEoFQPKKb9Ib9PJ4vo4VN6RKYt3DzDxpqu58pZMVJ
+xxn+UjmnAgMBAAECggEAKHROwr653ApVbE1i6Qh81emsEpkt4alYF/9c5m9kBhjB
+XMqjhGoTloSnOZOhhVLQqX9V85ecUdmAiXxvy/0Z2nAcBxvrWH6RmguEwwGanDAs
+KAmxJZmQYK3XX0zWAee+GsRDODw91nvH1DU5kaao2hWLXzWDyTjyXcXKFyrkEs4Z
+1sQiJHCFQW1l5j6x7kAXrbOHUCziCww+vvCUCW7ujut/Nl1MLzPrsIvtghQKwvhe
+uJVB7uZxtBHjQEfycZOLWCNUEE6WOJ/muUeCtHbmVbr50omOlRSJHP/MqUQaxnpM
+KS38BoUbJpbaVOKvgokjLHXF8KojNQ+Embx1Ql0AgQKBgQC/6lS/3eQKAr1FmjlN
+PicKDb6t08aE7THupy/sDL9jqEIYGfJPCbu8Guyd7nCwBCdJ8KcHQehukcZ8i1O4
+2Z/gMtuurHvD2S4+6sYjHZ9SyRiTkW8XY/jmwCYqAraS9fNNWj+maifyTcAreO+f
+KVI5/2QCNPMqjqzFGaS2w5XueQKBgQC4HpMpbikIzUEK3QnpcpqIyufpYiZGhOb8
+qgwTZCw3Bqn7KKB7kfTYpghLdzQmqUch6yaBWN5+YgabFzNtOkXvstpH4m5BJ1Q0
+N1zTTiPHOxup0TUPWQo2Qa+h52p8BOvKSBNGcgNFDJ3tdDOAX5tNeywunx/w1HjA
+aUUNoKLhHwKBgB+xjDNvaoR4tVc0Q/hMplfTs0Szr5ouLcvS0mgyJr1HgTrHtit1
+WQqUi7T9NqDq3q4oTv001jTEYDobLEVfszZsT7lGBN5wFGIRlY0hDDm4uhVMtELx
+oJ5C50qSziHw+jAxEkfiShyK2IyVWUU4prqrQZHXuryxeTjHplsEa9NJAoGAURLV
+hjbFxuRqsZfnV25pcba3K+NWK1M2SyetrZQ8i/ZZPwkCsabxg7yIhoJ06lk7w0nC
+aM5zGn+bnQs4T+6LASNmTqT8G6BvyZZfP4R26LG0WrCOhrWUc5O0/Lvj/bxE/4uB
+QVHO8sa9e+PhEbQHtLR6HgVfkTJeAYvZJkkHr80CgYBAo06hacP0ATWy9GetJztU
+9OZfhobBuk3kBdU3tNFNe8UFRLg0MnUwv3FdXM6XsVhH/r18ApACityAzA+cMw5o
+9nPyi+C8GqWum1eg3XaSPpKxNCVsAQuTJpSjL3JqeZSo07XUOAS2om5AQBLsbGHB
+2hpwDA/Ccom2Sc8E/VysmQ==
+-----END PRIVATE KEY-----
diff --git a/jstests/ssl/libs/cluster-member-foo-alt-rdn.pem.digest.sha1 b/jstests/ssl/libs/cluster-member-foo-alt-rdn.pem.digest.sha1
new file mode 100644
index 00000000000..773e4493989
--- /dev/null
+++ b/jstests/ssl/libs/cluster-member-foo-alt-rdn.pem.digest.sha1
@@ -0,0 +1 @@
+94F9962116E92EBDB4FC7007304957CCE1A41F26 \ No newline at end of file
diff --git a/jstests/ssl/libs/cluster-member-foo-alt-rdn.pem.digest.sha256 b/jstests/ssl/libs/cluster-member-foo-alt-rdn.pem.digest.sha256
new file mode 100644
index 00000000000..02ea263cddb
--- /dev/null
+++ b/jstests/ssl/libs/cluster-member-foo-alt-rdn.pem.digest.sha256
@@ -0,0 +1 @@
+215C9A1DB0D815E937668EBE8230496B9FDB3DBE2F9700820B9F631B87C28CB5 \ No newline at end of file
diff --git a/jstests/ssl/libs/cluster-member-foo.pem b/jstests/ssl/libs/cluster-member-foo.pem
new file mode 100644
index 00000000000..a80d90767d7
--- /dev/null
+++ b/jstests/ssl/libs/cluster-member-foo.pem
@@ -0,0 +1,58 @@
+# Autogenerated file, do not edit.
+# Generate using jstests/ssl/x509/mkcert.py --config jstests/ssl/x509/certs.yml cluster-member-foo.pem
+#
+# A server certificate with the mongoClusterMembership extension with a value of foo
+-----BEGIN CERTIFICATE-----
+MIIEejCCA2KgAwIBAgIEU7DfoTANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV
+UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO
+BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVs
+IFRlc3QgQ0EwHhcNMjMwMzE1MTU0MTUzWhcNMjUwNjE2MTU0MTUzWjBsMQswCQYD
+VQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENp
+dHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEPMA0GA1UEAwwG
+c2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoj7sGnUpd3gu
+MWBZD3gwilIw5IoVySUak0g9F7VESbU0nvCS6Df594TnE7v+pYUczq6U2o8fgAUi
+8J1iH6Zj/osIbeQuoDbFpWyVmYGNFwDsvWcxXQEuWpdn0Fk2U6Ropaxbbp9Md9je
+Xp/1kfpV2Fmg0IKvC+l3hkoalnBBJseftbVV5qs0Gw1yftyL0t8Fu4JVl/mQQKYD
+19pyPxuDapgMRhGCmcjhjuNeFY0w6T17TBT/tQ9B8wM5hNlXElvWQqKnQybXF1S7
+ZRfXOHRFgBxUxJaEREPHHjt9QozFY6NS/BN9oBQyihj1PFqB54yFNoNRx/eQAM2C
+LUXk+wyfZwIDAQABo4IBGjCCARYwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwHQYD
+VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRS6fOGvmeuH1/U
+CQikWX+BkLLLgzCBiwYDVR0jBIGDMIGAoXikdjB0MQswCQYDVQQGEwJVUzERMA8G
+A1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNVBAoM
+B01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVsIFRlc3Qg
+Q0GCBHvUrJMwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMBQGCysGAQQBgo4p
+AgECBAUMA2ZvbzANBgkqhkiG9w0BAQsFAAOCAQEAPNCV4cJ+4rirKCT5Rw3p0ZUW
+OBmb4ZRKVJn0VuLTBth8516ftP3N1IXtuSy7UjpqW3wSrqN3YNI9tibNlrs5CGkA
+9EZiX1y0sxxUTM73EqzV9kx6dJ2g0BDolgc68sYdofIdIDNMzvfqg4cyIsH94KxJ
+h4FXD8bE3fnrusaZoD0TDUwJ7/YX6Jv191R06vZHR5YXnnPzZD+Kig+tKLh5ePCN
+KcgoPPMf3TPPbvpZVcyQHeceBSZ4+1lN/s4EUhSvit7TMO0TlfleLv2gC48MQt3R
+YKu70fqITRKchyXu2kAgIjAhUWjtllmYrIiWjiWwCaPJYcYIxXXIn3f9itzjjA==
+-----END CERTIFICATE-----
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCiPuwadSl3eC4x
+YFkPeDCKUjDkihXJJRqTSD0XtURJtTSe8JLoN/n3hOcTu/6lhRzOrpTajx+ABSLw
+nWIfpmP+iwht5C6gNsWlbJWZgY0XAOy9ZzFdAS5al2fQWTZTpGilrFtun0x32N5e
+n/WR+lXYWaDQgq8L6XeGShqWcEEmx5+1tVXmqzQbDXJ+3IvS3wW7glWX+ZBApgPX
+2nI/G4NqmAxGEYKZyOGO414VjTDpPXtMFP+1D0HzAzmE2VcSW9ZCoqdDJtcXVLtl
+F9c4dEWAHFTEloREQ8ceO31CjMVjo1L8E32gFDKKGPU8WoHnjIU2g1HH95AAzYIt
+ReT7DJ9nAgMBAAECggEAD9NUY1ZHR6x00QMlXMFr9qoCs+AWNOsGFxSmROA8+3WN
+3uz3X2hKXQ7dHUsqkQmVYEGeKl1ohKu7lz26uvyXZ1Y3acSmmaEOEU8wnmsJEJPa
+A7WDlp9NXq/DBAsXpfv06ygPORCXvF7ufctbgDQrWHGRopUErwREUNh8lGz5pecO
+FawUQoIrWfOx8bq/PFXAFiaJHfk1SaadZdHS1TX4ZUm07iYuYUTqxarffmyub8ZN
+lO7G/3fdivgfuBnMETUDFOu7xSphk56AFlxLBuEVk+u8/I5XHWiRcVKTA4vtbyUX
+xjM/sxO7qCZ6cY1Z/xaHOJUj0FoT5wqjjn4UltWerQKBgQDOPdiL6TLlQkF6rr6t
+Jo2anm/Xs+dr70NIwsfYxNNLGoLCENxAwYIjKjD93UuS/eTEkKga141bS/05pEiM
+rjv+jBxJd3El3RavBjYV+npjRiC7qlVg/hPhx6ZXCEOg2gx/6zIj9fzGmf8JEeLh
+VvIDqXw2b7mJUSv80AfWb+SHLQKBgQDJY8CZRP8hsGhV3b64PquME38CGwCSIdqW
+3MxAQHE0KFo98HFoElUecUklZh+AoHL9hpGuDRvSJ7AC3ynNnV5IxKYYjwM2pNL+
+nr3RNsNhrPcEMj/elW7BZoVG+zGyLjiTx7GO6e3S0rivUDhAQSEhp3G8ZM0kxsvi
+/RqxLXVdYwKBgCp+RaK2Hp1r5E/htzm3ys9Du6mG0LTFbGiOcVyxWRONV8miba8N
+78FNDSEROmQD2eHCKFC3ftGDu53nwmbx8zyEI8PjTzXM8sKHFhe7LwJLTa088DB2
+ySPo3dXqxvxaUN7+V6tfIIDO8+QrgkKJhn3IquYQaPro9ZY2SpcdIMnVAoGAFF08
+5YK/lcWD12Lz3SehKynxhuH6HczEkMrE8J5TlCWccnT00sQ/zTNBZUG9X8FZv18z
+LflvXcHbn363eG44UX1pGkSj24uxNkQRB63U9fSKiecW5EgSCgZ25aWS8eSQngjs
+YHoxLUdXm4quFXlAg2muK5G52MUtaseTQmVJX+cCgYBRFJ1zUDYHpniXEsGUx/OV
+FBEZg359fRTKZUnXAtOW4gbaWMmLZ8N47pRvJakbkvqrMWUFagZKWDbA7+BxopVB
+prIvNrSaIM3Q9o+H7gsA85O1qPkQQV+1Ue6OCPlxLz8AVnWb5zu1eFUNkrLeKwz0
+WREDb3ONFYMUxgzcvCDXgQ==
+-----END PRIVATE KEY-----
diff --git a/jstests/ssl/libs/cluster-member-foo.pem.digest.sha1 b/jstests/ssl/libs/cluster-member-foo.pem.digest.sha1
new file mode 100644
index 00000000000..21209a1dc30
--- /dev/null
+++ b/jstests/ssl/libs/cluster-member-foo.pem.digest.sha1
@@ -0,0 +1 @@
+5A081EAA0D42DED66771504EC405C5F9AE4885EA \ No newline at end of file
diff --git a/jstests/ssl/libs/cluster-member-foo.pem.digest.sha256 b/jstests/ssl/libs/cluster-member-foo.pem.digest.sha256
new file mode 100644
index 00000000000..4ef362e70f5
--- /dev/null
+++ b/jstests/ssl/libs/cluster-member-foo.pem.digest.sha256
@@ -0,0 +1 @@
+D0298CCEA9CEEBF3E739E331AD7A4C5A485DCB8FC66AD236F281BB5040136076 \ No newline at end of file
diff --git a/jstests/ssl/x509/README b/jstests/ssl/x509/README
index 346e06f750d..e85e25ecb4f 100644
--- a/jstests/ssl/x509/README
+++ b/jstests/ssl/x509/README
@@ -64,3 +64,4 @@ certs:
- mongoRoles:
- {role: readWrite, db: test1}
- {role: read, db: test2}
+ - mongoClusterMembership: clusterName
diff --git a/jstests/ssl/x509/certs.yml b/jstests/ssl/x509/certs.yml
index b2f50d283ba..ebedbaba66d 100644
--- a/jstests/ssl/x509/certs.yml
+++ b/jstests/ssl/x509/certs.yml
@@ -284,7 +284,7 @@ certs:
- name: 'server.pem'
description: General purpose server certificate file.
Subject: {CN: 'server'}
- extensions:
+ extensions: &server_pem_extensions
basicConstraints: {CA: false}
subjectKeyIdentifier: hash
keyUsage: [digitalSignature, keyEncipherment]
@@ -342,6 +342,36 @@ certs:
extensions:
extendedKeyUsage: [serverAuth]
+- name: 'cluster-member-foo.pem'
+ output_path: 'jstests/ssl/libs/'
+ description: A server certificate with the mongoClusterMembership extension with a value of foo
+ Subject: {CN: 'server'}
+ extensions:
+ <<: *server_pem_extensions
+ mongoClusterMembership: foo
+
+- name: 'cluster-member-bar.pem'
+ output_path: 'jstests/ssl/libs/'
+ description: A server certificate with the mongoClusterMembership extension with a value of bar
+ Subject: {CN: 'server'}
+ extensions:
+ <<: *server_pem_extensions
+ mongoClusterMembership: bar
+
+- name: 'cluster-member-foo-alt-rdn.pem'
+ output_path: 'jstests/ssl/libs/'
+ description: A server certificate with the mongoClusterMembership extension with a value of foo, but an unrelated RDN
+ Subject:
+ C: 'ZZ'
+ ST: 'Example'
+ L: 'Fakesville'
+ O: 'Company'
+ OU: 'Business'
+ CN: 'Doer'
+ extensions:
+ <<: *server_pem_extensions
+ mongoClusterMembership: foo
+
# For tenant migration testing.
- name: 'rs0.pem'
description: General purpose server certificate file.
diff --git a/jstests/ssl/x509/mkcert.py b/jstests/ssl/x509/mkcert.py
index 45ac802e51c..58848ceaaa6 100755
--- a/jstests/ssl/x509/mkcert.py
+++ b/jstests/ssl/x509/mkcert.py
@@ -20,10 +20,17 @@ import shutil
import mkdigest
# pylint: disable=protected-access
-OpenSSL._util.lib.OBJ_create(b'1.2.3.45', b'DummyOID45', b'Dummy OID 45')
-OpenSSL._util.lib.OBJ_create(b'1.2.3.56', b'DummyOID56', b'Dummy OID 56')
-OpenSSL._util.lib.OBJ_create(b'1.3.6.1.4.1.34601.2.1.1', b'mongoRoles',
- b'Sequence of MongoDB Database Roles')
+try:
+ # Newer versions of PyOpenSSL hide OBJ_create, but also seem okay without it.
+ OBJ_create = OpenSSL._util.lib.OBJ_create
+ OBJ_create(b'1.2.3.45', b'DummyOID45', b'Dummy OID 45')
+ OBJ_create(b'1.2.3.56', b'DummyOID56', b'Dummy OID 56')
+ OBJ_create(b'1.3.6.1.4.1.34601.2.1.1', b'mongoRoles',
+ b'Sequence of MongoDB Database Roles')
+ OBJ_create(b'1.3.6.1.4.1.34601.2.1.2', b'mongoClusterMembership',
+ b'Name of MongoDB cluster this cert is a member of')
+except:
+ pass
# pylint: enable=protected-access
CONFIGFILE = 'jstests/ssl/x509/certs.yml'
@@ -319,6 +326,15 @@ def set_mongo_roles_extension(exts, cert):
exts.append(OpenSSL.crypto.X509Extension(b'1.3.6.1.4.1.34601.2.1.1', False, value))
+def set_mongo_cluster_membership_extension(exts, cert):
+ """Encode a symbolic name to a mongodbClusterMembership extension."""
+ name = cert.get('extensions', {}).get('mongoClusterMembership')
+ if not name:
+ return
+
+ value = b'DER:' + binascii.hexlify(to_der_utf8_string(name))
+ exts.append(OpenSSL.crypto.X509Extension(b'1.3.6.1.4.1.34601.2.1.2', False, value))
+
def set_crl_distribution_point_extension(exts, cert):
"""Specify URI(s) for CRL distribution point(s)."""
uris = cert.get('extensions', {}).get('crlDistributionPoints')
@@ -345,6 +361,7 @@ def set_extensions(x509, cert):
set_crl_distribution_point_extension(exts, cert)
set_san_extension(x509, exts, cert)
set_mongo_roles_extension(exts, cert)
+ set_mongo_cluster_membership_extension(exts, cert)
ns_comment = cert.get('extensions', {}).get('nsComment')
if ns_comment:
diff --git a/src/mongo/db/commands/authentication_commands.cpp b/src/mongo/db/commands/authentication_commands.cpp
index c8fdf9e9be7..302654f40e7 100644
--- a/src/mongo/db/commands/authentication_commands.cpp
+++ b/src/mongo/db/commands/authentication_commands.cpp
@@ -229,7 +229,15 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session)
uassertStatusOK(authorizationSession->addAndAuthorizeUser(opCtx, request, boost::none));
};
- if (sslConfiguration.isClusterMember(clientName)) {
+ const bool isClusterMember = ([&] {
+ const auto& requiredValue = sslGlobalParams.clusterAuthX509ExtensionValue;
+ if (requiredValue.empty()) {
+ return sslConfiguration.isClusterMember(clientName);
+ }
+ return sslPeerInfo.getClusterMembership() == requiredValue;
+ })();
+
+ if (isClusterMember) {
// Handle internal cluster member auth, only applies to server-server connections
if (!clusterAuthMode.allowsX509()) {
uassert(ErrorCodes::AuthenticationFailed,
@@ -247,6 +255,19 @@ void _authenticateX509(OperationContext* opCtx, AuthenticationSession* session)
"with cluster membership");
}
+ if (gEnforceUserClusterSeparation &&
+ !sslGlobalParams.clusterAuthX509ExtensionValue.empty()) {
+ auto* am = AuthorizationManager::get(opCtx->getServiceContext());
+ BSONObj ignored;
+ const bool userExists =
+ am->getUserDescription(opCtx, request.name, &ignored).isOK();
+ uassert(ErrorCodes::AuthenticationFailed,
+ "The provided certificate represents both a cluster member and an "
+ "explicit user which exists in the authzn database. "
+ "Prohibiting authentication due to enforceUserClusterSeparation setting.",
+ !userExists);
+ }
+
session->setAsClusterMember();
authorizationSession->grantInternalAuthorization(client);
}
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp
index 4384a88ebbf..82ace1c063c 100644
--- a/src/mongo/db/commands/user_management_commands.cpp
+++ b/src/mongo/db/commands/user_management_commands.cpp
@@ -1030,7 +1030,7 @@ void CmdUMCTyped<CreateUserCommand>::Invocation::typedRun(OperationContext* opCt
#ifdef MONGO_CONFIG_SSL
auto& sslManager = opCtx->getClient()->session()->getSSLManager();
- if (isExternal && sslManager &&
+ if (isExternal && sslManager && sslGlobalParams.clusterAuthX509ExtensionValue.empty() &&
sslManager->getSSLConfiguration().isClusterMember(userName.getUser())) {
if (gEnforceUserClusterSeparation) {
uasserted(ErrorCodes::BadValue,
diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript
index 35dc7afe72d..09b609aadb3 100644
--- a/src/mongo/util/net/SConscript
+++ b/src/mongo/util/net/SConscript
@@ -64,6 +64,7 @@ env.Library(
'$BUILD_DIR/mongo/db/auth/auth_options',
'$BUILD_DIR/mongo/db/auth/cluster_auth_mode',
'$BUILD_DIR/mongo/db/server_base',
+ '$BUILD_DIR/mongo/db/server_feature_flags',
'$BUILD_DIR/mongo/util/options_parser/options_parser',
],
)
diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp
index 98c7a7a447a..74c2f2c8748 100644
--- a/src/mongo/util/net/ssl_manager.cpp
+++ b/src/mongo/util/net/ssl_manager.cpp
@@ -1071,6 +1071,11 @@ StatusWith<DERToken> DERToken::parse(ConstDataRange cdr, size_t* outLength) {
}
} // namespace
+StatusWith<std::string> parseDERString(ConstDataRange cdrExtension) {
+ ConstDataRangeCursor cdcExtension(cdrExtension);
+ return readDERString(cdcExtension);
+}
+
StatusWith<stdx::unordered_set<RoleName>> parsePeerRoles(ConstDataRange cdrExtension) {
stdx::unordered_set<RoleName> roles;
diff --git a/src/mongo/util/net/ssl_manager.h b/src/mongo/util/net/ssl_manager.h
index ae2682ba8a8..28b218530d5 100644
--- a/src/mongo/util/net/ssl_manager.h
+++ b/src/mongo/util/net/ssl_manager.h
@@ -152,6 +152,11 @@ const ASN1OID mongodbRolesOID("1.3.6.1.4.1.34601.2.1.1",
"MongoRoles",
"Sequence of MongoDB Database Roles");
+const ASN1OID mongodbClusterMembershipOID(
+ "1.3.6.1.4.1.34601.2.1.2",
+ "MongoDBClusterMembership",
+ "String name identifying the cluster this certificate is a member of");
+
/**
* Counts of negogtiated version used by TLS connections.
*/
@@ -411,6 +416,11 @@ extern bool isSSLServer;
bool hostNameMatchForX509Certificates(std::string nameToMatch, std::string certHostName);
/**
+ * Parse a UTF8 string from a DER encoded ASN.1 DisplayString.
+ */
+StatusWith<std::string> parseDERString(ConstDataRange cdr);
+
+/**
* Parse a binary blob of DER encoded ASN.1 into a set of RoleNames.
*/
StatusWith<stdx::unordered_set<RoleName>> parsePeerRoles(ConstDataRange cdrExtension);
diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp
index 361b8a78c89..de847d52da0 100644
--- a/src/mongo/util/net/ssl_manager_openssl.cpp
+++ b/src/mongo/util/net/ssl_manager_openssl.cpp
@@ -109,6 +109,11 @@ using transport::SSLConnectionContext;
namespace {
+// NIDs mapping to OIDs in the MongoDBInc namespace.
+// Allocated during MONGO_INITIALIZER(SSLManager), valid for process lifetime.
+static int sMongoDbRolesNID;
+static int sMongoDbClusterMembershipNID;
+
MONGO_FAIL_POINT_DEFINE(disableStapling);
using UniqueX509StoreCtx =
@@ -1515,11 +1520,21 @@ private:
*/
void _getCRLInfo(StringData crlFile, CRLInformationToLog* info) const;
- StatusWith<stdx::unordered_set<RoleName>> _parsePeerRoles(X509* peerCert) const;
+ struct ParsedPeerExtensions {
+ stdx::unordered_set<RoleName> roles;
+ boost::optional<std::string> clusterMembership;
+ };
+ StatusWith<ParsedPeerExtensions> _parsePeerExtensions(X509* peerCert) const;
+
+ // Fetches NID for mongodbRolesOID constant.
+ static int _getMongoDbRolesNID() {
+ return sMongoDbRolesNID;
+ }
- // Fetches NID for mongodbRolesOID constant. Initializes once, safe to invoke
- // multiple times.
- static int _getMongoDbRolesOID();
+ // Fetches NID for mongodbClusterMembershipOID constant.
+ static int _getMongoDbClusterMembershipNID() {
+ return sMongoDbClusterMembershipNID;
+ }
StatusWith<boost::optional<std::vector<DERInteger>>> _parseTLSFeature(X509* peerCert) const;
@@ -1618,17 +1633,19 @@ bool isSSLServer = false;
extern SSLManagerCoordinator* theSSLManagerCoordinator;
-static int sMongoDbRolesOID;
-
MONGO_INITIALIZER_WITH_PREREQUISITES(SSLManager, ("SetupOpenSSL", "EndStartupOptionHandling"))
(InitializerContext*) {
if (!isSSLServer || (sslGlobalParams.sslMode.load() != SSLParams::SSLMode_disabled)) {
theSSLManagerCoordinator = new SSLManagerCoordinator();
}
- sMongoDbRolesOID = OBJ_create(mongodbRolesOID.identifier.c_str(),
+ sMongoDbRolesNID = OBJ_create(mongodbRolesOID.identifier.c_str(),
mongodbRolesOID.shortDescription.c_str(),
mongodbRolesOID.longDescription.c_str());
+
+ sMongoDbClusterMembershipNID = OBJ_create(mongodbClusterMembershipOID.identifier.c_str(),
+ mongodbClusterMembershipOID.shortDescription.c_str(),
+ mongodbClusterMembershipOID.longDescription.c_str());
}
std::shared_ptr<SSLManagerInterface> SSLManagerInterface::create(
@@ -3319,13 +3336,13 @@ Future<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate(
"cipher"_attr = SSL_CIPHER_get_name(cipher));
}
- StatusWith<stdx::unordered_set<RoleName>> swPeerCertificateRoles =
- _parsePeerRoles(peerCert.get());
- if (!swPeerCertificateRoles.isOK()) {
- return Future<SSLPeerInfo>::makeReady(swPeerCertificateRoles.getStatus());
+ auto swParsedPeerExtensions = _parsePeerExtensions(peerCert.get());
+ if (!swParsedPeerExtensions.isOK()) {
+ return Future<SSLPeerInfo>::makeReady(swParsedPeerExtensions.getStatus());
}
+ auto parsedPeerExtensions = std::move(swParsedPeerExtensions.getValue());
- if (auto status = _validatePeerRoles(swPeerCertificateRoles.getValue(), conn); !status.isOK()) {
+ if (auto status = _validatePeerRoles(parsedPeerExtensions.roles, conn); !status.isOK()) {
return status;
}
@@ -3355,8 +3372,11 @@ Future<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate(
return std::move(ocspFuture)
.then([peerSubject,
sni,
- peerCertificateRoles = std::move(swPeerCertificateRoles.getValue())] {
- return SSLPeerInfo(peerSubject, sni, peerCertificateRoles);
+ roles = std::move(parsedPeerExtensions.roles),
+ clusterMembership = std::move(parsedPeerExtensions.clusterMembership)] {
+ SSLPeerInfo info(peerSubject, sni, roles);
+ info.setClusterMembership(clusterMembership);
+ return info;
});
}
@@ -3492,7 +3512,8 @@ SSLPeerInfo SSLManagerOpenSSL::parseAndValidatePeerCertificateDeprecated(
return swPeerSubjectName.getValue();
}
-StatusWith<stdx::unordered_set<RoleName>> SSLManagerOpenSSL::_parsePeerRoles(X509* peerCert) const {
+StatusWith<SSLManagerOpenSSL::ParsedPeerExtensions> SSLManagerOpenSSL::_parsePeerExtensions(
+ X509* peerCert) const {
// exts is owned by the peerCert
const STACK_OF(X509_EXTENSION)* exts = X509_get0_extensions(peerCert);
@@ -3501,29 +3522,37 @@ StatusWith<stdx::unordered_set<RoleName>> SSLManagerOpenSSL::_parsePeerRoles(X50
extCount = sk_X509_EXTENSION_num(exts);
}
- ASN1_OBJECT* rolesObj = OBJ_nid2obj(_getMongoDbRolesOID());
+ ASN1_OBJECT* rolesObj = OBJ_nid2obj(_getMongoDbRolesNID());
+ ASN1_OBJECT* clusterMembershipObj = OBJ_nid2obj(_getMongoDbClusterMembershipNID());
// Search all certificate extensions for our own
- stdx::unordered_set<RoleName> roles;
+ ParsedPeerExtensions parsed;
for (int i = 0; i < extCount; i++) {
X509_EXTENSION* ex = sk_X509_EXTENSION_value(exts, i);
ASN1_OBJECT* obj = X509_EXTENSION_get_object(ex);
if (!OBJ_cmp(obj, rolesObj)) {
- // We've found an extension which has our roles OID
+ // mongodbRoles extension.
ASN1_OCTET_STRING* data = X509_EXTENSION_get_data(ex);
-
- return parsePeerRoles(
- ConstDataRange(reinterpret_cast<char*>(data->data),
- reinterpret_cast<char*>(data->data) + data->length));
+ auto ptr = reinterpret_cast<char*>(data->data);
+ auto swRoles = parsePeerRoles(ConstDataRange(ptr, ptr + data->length));
+ if (!swRoles.isOK()) {
+ return std::move(swRoles.getStatus());
+ }
+ parsed.roles = std::move(swRoles.getValue());
+ } else if (!OBJ_cmp(obj, clusterMembershipObj)) {
+ // mongodbClusterMembership extension.
+ ASN1_OCTET_STRING* data = X509_EXTENSION_get_data(ex);
+ auto ptr = reinterpret_cast<char*>(data->data);
+ auto swMembership = parseDERString(ConstDataRange(ptr, ptr + data->length));
+ if (!swMembership.isOK()) {
+ return std::move(swMembership.getStatus());
+ }
+ parsed.clusterMembership = std::move(swMembership.getValue());
}
}
- return roles;
-}
-
-int SSLManagerOpenSSL::_getMongoDbRolesOID() {
- return sMongoDbRolesOID;
+ return parsed;
}
StatusWith<boost::optional<std::vector<DERInteger>>> SSLManagerOpenSSL::_parseTLSFeature(
diff --git a/src/mongo/util/net/ssl_options.h b/src/mongo/util/net/ssl_options.h
index e58bedcd076..41bb82c66ea 100644
--- a/src/mongo/util/net/ssl_options.h
+++ b/src/mongo/util/net/ssl_options.h
@@ -66,12 +66,13 @@ struct SSLParams {
std::string sslPEMKeyFile; // --tlsCertificateKeyFile
std::string sslPEMKeyPassword; // --tlsCertificateKeyFilePassword
std::string sslClusterFile; // --tlsInternalKeyFile
- std::string sslClusterPassword; // --tlsInternalKeyPassword
- std::string sslCAFile; // --tlsCAFile
- std::string sslClusterCAFile; // --tlsClusterCAFile
- std::string sslCRLFile; // --tlsCRLFile
- std::string sslCipherConfig; // --tlsCipherConfig
- std::string sslCipherSuiteConfig; // --tlsCipherSuiteConfig
+ std::string sslClusterPassword; // --tlsInternalKeyPassword
+ std::string sslCAFile; // --tlsCAFile
+ std::string sslClusterCAFile; // --tlsClusterCAFile
+ std::string sslCRLFile; // --tlsCRLFile
+ std::string sslCipherConfig; // --tlsCipherConfig
+ std::string sslCipherSuiteConfig; // --tlsCipherSuiteConfig
+ std::string clusterAuthX509ExtensionValue; // --tlsClusterAuthX509ExtensionValue
boost::optional<TLSCATrusts> tlsCATrusts; // --setParameter tlsCATrusts
diff --git a/src/mongo/util/net/ssl_options_server.cpp b/src/mongo/util/net/ssl_options_server.cpp
index 8636ea6eadd..2f1e6f15179 100644
--- a/src/mongo/util/net/ssl_options_server.cpp
+++ b/src/mongo/util/net/ssl_options_server.cpp
@@ -37,6 +37,7 @@
#include "mongo/base/status.h"
#include "mongo/config.h"
#include "mongo/db/auth/auth_options_gen.h"
+#include "mongo/db/server_feature_flags_gen.h"
#include "mongo/db/server_options.h"
#include "mongo/logv2/log.h"
#include "mongo/util/options_parser/startup_option_init.h"
@@ -241,6 +242,18 @@ MONGO_STARTUP_OPTIONS_POST(SSLServerOptions)(InitializerContext*) {
}
}
+ if (params.count("net.tls.clusterAuthX509.extensionValue")) {
+ uassert(ErrorCodes::BadValue,
+ "Unknown configuration option 'net.tls.clusterAuthX509.extensionValue'",
+ gFeatureFlagConfigurableX509ClusterAuthn.isEnabledAndIgnoreFCV());
+ uassert(ErrorCodes::BadValue,
+ "net.tls.clusterAuthX509.extensionValue requires "
+ "a clusterAuthMode which allows for usage of X509",
+ clusterAuthMode.allowsX509());
+ sslGlobalParams.clusterAuthX509ExtensionValue =
+ params["net.tls.clusterAuthX509.extensionValue"].as<std::string>();
+ }
+
if (sslGlobalParams.sslMode.load() == SSLParams::SSLMode_allowSSL) {
// allowSSL and x509 is valid only when we are transitioning to auth.
if (clusterAuthMode.sendsX509() && !serverGlobalParams.transitionToAuth) {
diff --git a/src/mongo/util/net/ssl_options_server.idl b/src/mongo/util/net/ssl_options_server.idl
index cdb0a4602f0..0311f3e74da 100644
--- a/src/mongo/util/net/ssl_options_server.idl
+++ b/src/mongo/util/net/ssl_options_server.idl
@@ -180,3 +180,12 @@ configs:
short_name: tlsLogVersions
arg_vartype: String
+ "net.tls.clusterAuthX509.extensionValue":
+ description: >-
+ If specified, clients who expect to be regarded as cluster members
+ must present a valid X.509 certificate containing an X.509 extension
+ for OID 1.3.6.1.4.1.34601.2.1.2 which contains the specified value.
+ short_name: tlsClusterAuthX509ExtensionValue
+ arg_vartype: String
+ # // TODO SERVER-74989 X.509 Subject Name Matching
+ # conflicts: "net.tls.clusterAuthX509.attributes"
diff --git a/src/mongo/util/net/ssl_peer_info.h b/src/mongo/util/net/ssl_peer_info.h
index befef936c1b..39b1ec51779 100644
--- a/src/mongo/util/net/ssl_peer_info.h
+++ b/src/mongo/util/net/ssl_peer_info.h
@@ -29,6 +29,8 @@
#pragma once
+#include <boost/optional.hpp>
+
#include "mongo/transport/session.h"
#include "mongo/util/net/ssl_types.h"
@@ -70,6 +72,14 @@ public:
static SSLPeerInfo& forSession(const std::shared_ptr<transport::Session>& session);
static const SSLPeerInfo& forSession(const std::shared_ptr<const transport::Session>& session);
+ const boost::optional<std::string>& getClusterMembership() const {
+ return _clusterMembership;
+ }
+
+ void setClusterMembership(boost::optional<std::string> clusterMembership) {
+ _clusterMembership = std::move(clusterMembership);
+ }
+
private:
/**
* This flag is used to indicate if the underlying socket is using TLS or not. A default
@@ -80,5 +90,6 @@ private:
SSLX509Name _subjectName;
boost::optional<std::string> _sniName;
stdx::unordered_set<RoleName> _roles;
+ boost::optional<std::string> _clusterMembership;
};
} // namespace mongo