diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2023-03-21 16:34:12 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-04-03 18:55:21 +0000 |
commit | dc2ff7aa916ef1412ea939b0eaf49a063665d309 (patch) | |
tree | 1008fdea299ed3f78c22e945a214d0a84ea32051 | |
parent | fe3275102df92e99c7e83a860c63e6ac54218f35 (diff) | |
download | mongo-dc2ff7aa916ef1412ea939b0eaf49a063665d309.tar.gz |
SERVER-74999 Determine cluster membership based on X.509 extension
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 |