diff options
author | Adam Cooper <adam.cooper@mongodb.com> | 2020-08-17 15:37:42 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-08-20 22:20:55 +0000 |
commit | ad83ad71c3c65e0a7e8dcb0073069dbf6299b0bb (patch) | |
tree | 434438c6f3a9c5191642eabff503e211fd8b4047 | |
parent | 504dee509b57ba039bcfe1130054aabc13839fa9 (diff) | |
download | mongo-ad83ad71c3c65e0a7e8dcb0073069dbf6299b0bb.tar.gz |
SERVER-48693 Add network counter for cluster authentication
-rw-r--r-- | jstests/auth/auth-counters.js | 85 | ||||
-rw-r--r-- | jstests/auth/speculative-auth-replset.js | 11 | ||||
-rw-r--r-- | jstests/auth/speculative-auth-sharding.js | 45 | ||||
-rw-r--r-- | jstests/auth/speculative-sasl-start.js | 34 | ||||
-rw-r--r-- | jstests/ssl/auth-counters.js | 65 | ||||
-rw-r--r-- | jstests/ssl/speculative-auth-replset.js | 11 | ||||
-rw-r--r-- | jstests/ssl/speculative-auth-sharding.js | 25 | ||||
-rw-r--r-- | jstests/ssl/speculative-authenticate.js | 32 | ||||
-rw-r--r-- | src/mongo/client/authenticate.h | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_commands.cpp | 32 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_mechanism_registry.h | 9 | ||||
-rw-r--r-- | src/mongo/db/commands/authentication_commands.cpp | 65 | ||||
-rw-r--r-- | src/mongo/db/stats/counters.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/stats/counters.h | 7 |
14 files changed, 335 insertions, 115 deletions
diff --git a/jstests/auth/auth-counters.js b/jstests/auth/auth-counters.js index bbb66a619ae..f3f2af6a758 100644 --- a/jstests/auth/auth-counters.js +++ b/jstests/auth/auth-counters.js @@ -3,9 +3,15 @@ (function() { 'use strict'; -const mongod = MongoRunner.runMongod({auth: ''}); -const admin = mongod.getDB('admin'); -const test = mongod.getDB('test'); +const keyfile = 'jstests/libs/key1'; +const badKeyfile = 'jstests/libs/key2'; +let replTest = new ReplSetTest({nodes: 1, keyFile: keyfile, nodeOptions: {auth: ""}}); +replTest.startSet(); +replTest.initiate(); +let primary = replTest.getPrimary(); + +const admin = primary.getDB('admin'); +const test = primary.getDB('test'); admin.createUser({user: 'admin', pwd: 'pwd', roles: ['root'], mechanisms: ['SCRAM-SHA-256']}); admin.auth('admin', 'pwd'); @@ -15,21 +21,22 @@ test.createUser({user: 'user256', pwd: 'pwd', roles: [], mechanisms: ['SCRAM-SHA test.createUser( {user: 'user', pwd: 'pwd', roles: [], mechanisms: ['SCRAM-SHA-1', 'SCRAM-SHA-256']}); -// admin.auth() above provides an initial count for SCRAM-SHA-256 -const expected = { - 'SCRAM-SHA-256': { - received: 1, - successful: 1, - }, -}; +// Count the number of authentications performed during setup +const expected = + assert.commandWorked(admin.runCommand({serverStatus: 1})).security.authentication.mechanisms; function assertStats() { const mechStats = assert.commandWorked(admin.runCommand({serverStatus: 1})) .security.authentication.mechanisms; Object.keys(expected).forEach(function(mech) { try { - assert.eq(mechStats[mech].authenticate.received, expected[mech].received); - assert.eq(mechStats[mech].authenticate.successful, expected[mech].successful); + assert.eq(mechStats[mech].authenticate.received, expected[mech].authenticate.received); + assert.eq(mechStats[mech].authenticate.successful, + expected[mech].authenticate.successful); + assert.eq(mechStats[mech].clusterAuthenticate.received, + expected[mech].clusterAuthenticate.received); + assert.eq(mechStats[mech].clusterAuthenticate.successful, + expected[mech].clusterAuthenticate.successful); } catch (e) { print("Mechanism: " + mech); print("mechStats: " + tojson(mechStats)); @@ -39,23 +46,42 @@ function assertStats() { }); } -function assertSuccess(creds, mech) { - if (expected[mech] === undefined) { - expected[mech] = {received: 0, successful: 0}; +function assertSuccess(creds, mech, db = test) { + assert.eq(db.auth(creds), true); + if (db !== admin) { + db.logout(); } - assert.eq(test.auth(creds), true); - test.logout(); - ++expected[mech].received; - ++expected[mech].successful; + ++expected[mech].authenticate.received; + ++expected[mech].authenticate.successful; assertStats(); } -function assertFailure(creds, mech) { - if (expected[mech] === undefined) { - expected[mech] = {received: 0, successful: 0}; - } - assert.eq(test.auth(creds), false); - ++expected[mech].received; +function assertFailure(creds, mech, db = test) { + assert.eq(db.auth(creds), false); + ++expected[mech].authenticate.received; + assertStats(); +} + +function assertSuccessInternal() { + const mech = "SCRAM-SHA-1"; + // asCluster exiting cleanly indicates successful auth + assert.eq(authutil.asCluster(replTest.nodes, keyfile, () => true), true); + ++expected[mech].authenticate.received; + ++expected[mech].authenticate.successful; + ++expected[mech].clusterAuthenticate.received; + ++expected[mech].clusterAuthenticate.successful; + // we have to re-auth as admin to get stats, which are validated at the end of assertSuccess + assertSuccess({user: 'admin', pwd: 'pwd'}, 'SCRAM-SHA-256', admin); +} + +function assertFailureInternal() { + const mech = "SCRAM-SHA-1"; + // If asCluster fails, it explodes. + assert.throws(authutil.asCluster, [replTest.nodes, badKeyfile, () => true]); + ++expected[mech].authenticate.received; + ++expected[mech].clusterAuthenticate.received; + // we have to re-auth as admin to get stats, which are validated at the end of assertSuccess + assertSuccess({user: 'admin', pwd: 'pwd'}, 'SCRAM-SHA-256', admin); assertStats(); } @@ -86,9 +112,16 @@ assertFailure({user: 'user', pwd: 'haxx', mechanism: 'SCRAM-SHA-1'}, 'SCRAM-SHA- assertFailure({user: 'user1', pwd: 'pwd', mechanism: 'SCRAM-SHA-256'}, 'SCRAM-SHA-256'); assertFailure({user: 'user256', pwd: 'pwd', mechanism: 'SCRAM-SHA-1'}, 'SCRAM-SHA-1'); +// Cluster auth counter checks. +assertSuccessInternal(); +assertFailureInternal(); + +// Need to auth as admin one more time to get final stats. +admin.auth('admin', 'pwd'); + const finalStats = assert.commandWorked(admin.runCommand({serverStatus: 1})).security.authentication.mechanisms; -MongoRunner.stopMongod(mongod); +replTest.stopSet(); printjson(finalStats); })(); diff --git a/jstests/auth/speculative-auth-replset.js b/jstests/auth/speculative-auth-replset.js index dfa985321a3..d6f9a52c03a 100644 --- a/jstests/auth/speculative-auth-replset.js +++ b/jstests/auth/speculative-auth-replset.js @@ -39,14 +39,17 @@ const mechStats = printjson(mechStats); assert(mechStats['SCRAM-SHA-256'] !== undefined); Object.keys(mechStats).forEach(function(mech) { - const stats = mechStats[mech].speculativeAuthenticate; + const specStats = mechStats[mech].speculativeAuthenticate; + const clusterStats = mechStats[mech].clusterAuthenticate; if (mech === 'SCRAM-SHA-256') { - assert.gte(stats.received, 2); + assert.gte(specStats.received, 2); + assert.gte(clusterStats.received, 2); } else { - assert.eq(stats.received, 0); + assert.eq(specStats.received, 0); } - assert.eq(stats.received, stats.successful); + assert.eq(specStats.received, specStats.successful); + assert.eq(clusterStats.received, clusterStats.successful); }); test(baseURI); diff --git a/jstests/auth/speculative-auth-sharding.js b/jstests/auth/speculative-auth-sharding.js index 008eafac08d..d8bcae94ed9 100644 --- a/jstests/auth/speculative-auth-sharding.js +++ b/jstests/auth/speculative-auth-sharding.js @@ -20,26 +20,37 @@ let lastStats = assert.commandWorked(admin.runCommand({serverStatus: 1})).security.authentication.mechanisms; jsTest.log('Inintial stats: ' + lastStats); -function test(uri, incrMech) { +function test(uri, incrMech, isClusterAuth = false) { jsTest.log('Connecting to: ' + uri); assert.eq(runMongoProgram('mongo', uri, '--eval', ';'), 0); const stats = assert.commandWorked(admin.runCommand({serverStatus: 1})) .security.authentication.mechanisms; - assert.eq(Object.keys(lastStats).length, Object.keys(stats).length); - Object.keys(lastStats).forEach(function(mech) { - const inc = (mech == incrMech) ? 1 : 0; - - const specBefore = lastStats[mech].speculativeAuthenticate; - const specAfter = stats[mech].speculativeAuthenticate; - assert.eq(specAfter.received, specBefore.received + inc); - assert.eq(specAfter.successful, specBefore.successful + inc); - - const allBefore = lastStats[mech].authenticate; - const allAfter = stats[mech].authenticate; - assert.eq(allAfter.received, allBefore.received + inc); - assert.eq(allAfter.successful, allBefore.successful + inc); - }); + try { + assert.eq(Object.keys(lastStats).length, Object.keys(stats).length); + Object.keys(lastStats).forEach(function(mech) { + const inc = (mech === incrMech) ? 1 : 0; + const clusterInc = (mech === incrMech && isClusterAuth) ? 1 : 0; + + const specBefore = lastStats[mech].speculativeAuthenticate; + const specAfter = stats[mech].speculativeAuthenticate; + assert.eq(specAfter.received, specBefore.received + inc); + assert.eq(specAfter.successful, specBefore.successful + inc); + + const clusterBefore = lastStats[mech].clusterAuthenticate; + const clusterAfter = stats[mech].clusterAuthenticate; + assert.eq(clusterAfter.received, clusterBefore.received + clusterInc); + assert.eq(clusterAfter.successful, clusterBefore.successful + clusterInc); + + const allBefore = lastStats[mech].authenticate; + const allAfter = stats[mech].authenticate; + assert.eq(allAfter.received, allBefore.received + inc); + assert.eq(allAfter.successful, allBefore.successful + inc); + }); + } catch (e) { + print("Stats: " + tojson(stats)); + throw e; + } lastStats = stats; } @@ -48,6 +59,10 @@ const baseURI = 'mongodb://admin:pwd@' + st.s.host + '/admin'; test(baseURI, fallbackMech); test(baseURI + '?authMechanism=SCRAM-SHA-1', 'SCRAM-SHA-1'); test(baseURI + '?authMechanism=SCRAM-SHA-256', 'SCRAM-SHA-256'); +const systemPass = cat(keyfile).replace(/\s/g, ''); +test('mongodb://__system:' + systemPass + '@' + st.s.host + '/admin?authMechanisms=SCRAM-SHA-256', + 'SCRAM-SHA-256', + true); admin.logout(); st.stop(); diff --git a/jstests/auth/speculative-sasl-start.js b/jstests/auth/speculative-sasl-start.js index 8db5d03dcb7..1518cceeb01 100644 --- a/jstests/auth/speculative-sasl-start.js +++ b/jstests/auth/speculative-sasl-start.js @@ -3,7 +3,8 @@ (function() { 'use strict'; -const mongod = MongoRunner.runMongod({auth: ''}); +const keyFile = 'jstests/libs/key1'; +const mongod = MongoRunner.runMongod({auth: '', keyFile: keyFile}); const admin = mongod.getDB('admin'); admin.createUser( @@ -35,10 +36,22 @@ assertStats(function(mechStats) { }); }); -function expectN(mechStats, mech, N, M) { - const stats = mechStats[mech].speculativeAuthenticate; - assert.eq(N, stats.received); - assert.eq(M, stats.successful); +// No "intra-cluster" auth attempts yet. +assertStats(function(mechStats) { + Object.keys(mechStats).forEach(function(mech) { + const stats = mechStats[mech].clusterAuthenticate; + assert.eq(stats.received, 0); + assert.eq(stats.successful, 0); + }); +}); + +function expectN(mechStats, mech, N1, M1, N2 = 0, M2 = 0) { + const specStats = mechStats[mech].speculativeAuthenticate; + const clusterStats = mechStats[mech].clusterAuthenticate; + assert.eq(N1, specStats.received); + assert.eq(M1, specStats.successful); + assert.eq(N2, clusterStats.received); + assert.eq(M2, clusterStats.successful); } const baseOKURI = 'mongodb://admin:pwd@localhost:' + mongod.port + '/admin'; @@ -97,5 +110,16 @@ mongod.getDB('test').createUser({user: 'alice', pwd: 'secret', roles: []}); test('mongodb://alice:secret@localhost:' + mongod.port + '/test', true); assertStats((s) => expectN(s, 'SCRAM-SHA-256', 7, 3)); +// Test "intra-cluster" speculative authentication. +const systemPass = cat(keyFile).replace(/\s/g, ''); +test('mongodb://__system:' + systemPass + '@localhost:' + mongod.port + '/admin' + + '?authMechanism=SCRAM-SHA-256', + true); +assertStats((s) => expectN(s, 'SCRAM-SHA-256', 8, 4, 1, 1)); +test('mongodb://__system:hunter2@localhost:' + mongod.port + '/admin' + + '?authMechanism=SCRAM-SHA-256', + false); +assertStats((s) => expectN(s, 'SCRAM-SHA-256', 9, 4, 3, 1)); + MongoRunner.stopMongod(mongod); })(); diff --git a/jstests/ssl/auth-counters.js b/jstests/ssl/auth-counters.js index 6eaafa3735e..04274ef8578 100644 --- a/jstests/ssl/auth-counters.js +++ b/jstests/ssl/auth-counters.js @@ -3,11 +3,13 @@ (function() { 'use strict'; +const x509 = "MONGODB-X509"; const mongod = MongoRunner.runMongod({ auth: '', tlsMode: 'requireTLS', tlsCertificateKeyFile: 'jstests/libs/server.pem', tlsCAFile: 'jstests/libs/ca.pem', + clusterAuthMode: "x509", }); const admin = mongod.getDB('admin'); const external = mongod.getDB('$external'); @@ -20,46 +22,79 @@ external.createUser({user: X509USER, roles: []}); // This test ignores counters for SCRAM-SHA-*. // For those, see jstests/auth/auth-counters.js -const expected = { - received: 0, - successful: 0 -}; +const expected = assert.commandWorked(admin.runCommand({serverStatus: 1})) + .security.authentication.mechanisms[x509]; function assertStats() { const mechStats = assert.commandWorked(admin.runCommand({serverStatus: 1})) - .security.authentication.mechanisms['MONGODB-X509'] - .authenticate; - assert.eq(mechStats.received, expected.received); - assert.eq(mechStats.successful, expected.successful); + .security.authentication.mechanisms[x509]; + try { + assert.eq(mechStats.authenticate.received, expected.authenticate.received); + assert.eq(mechStats.authenticate.successful, expected.authenticate.successful); + assert.eq(mechStats.clusterAuthenticate.received, expected.clusterAuthenticate.received); + assert.eq(mechStats.clusterAuthenticate.successful, + expected.clusterAuthenticate.successful); + } catch (e) { + print("mechStats: " + tojson(mechStats)); + print("expected: " + tojson(expected)); + throw e; + } } function assertSuccess(creds) { assert.eq(external.auth(creds), true); external.logout(); - ++expected.received; - ++expected.successful; + ++expected.authenticate.received; + ++expected.authenticate.successful; assertStats(); } function assertFailure(creds) { assert.eq(external.auth(creds), false); - ++expected.received; + ++expected.authenticate.received; + assertStats(); +} + +function assertSuccessInternal() { + assert.eq(runMongoProgram("mongo", + "--tls", + "--port", + mongod.port, + "--tlsCertificateKeyFile", + "jstests/libs/server.pem", + "--tlsCAFile", + "jstests/libs/ca.pem", + "--authenticationDatabase", + "$external", + "--authenticationMechanism", + "MONGODB-X509", + "--eval", + ";"), + 0); + ++expected.authenticate.received; + ++expected.authenticate.successful; + ++expected.clusterAuthenticate.received; + ++expected.clusterAuthenticate.successful; assertStats(); } // User from certificate should work. -assertSuccess({mechanism: 'MONGODB-X509'}); +assertSuccess({mechanism: x509}); // Explicitly named user. -assertSuccess({user: X509USER, mechanism: 'MONGODB-X509'}); +assertSuccess({user: X509USER, mechanism: x509}); + +// Cluster auth counter checks. +// We can't test failures with the __system user without the handshake failing, +// which won't increment the counters. +assertSuccessInternal(); // Fails once the user no longer exists. external.dropUser(X509USER); -assertFailure({mechanism: 'MONGODB-X509'}); +assertFailure({mechanism: x509}); const finalStats = assert.commandWorked(admin.runCommand({serverStatus: 1})).security.authentication.mechanisms; MongoRunner.stopMongod(mongod); - printjson(finalStats); })(); diff --git a/jstests/ssl/speculative-auth-replset.js b/jstests/ssl/speculative-auth-replset.js index 3c10b53b678..51f9e2c8154 100644 --- a/jstests/ssl/speculative-auth-replset.js +++ b/jstests/ssl/speculative-auth-replset.js @@ -37,13 +37,16 @@ const mechStats = printjson(mechStats); assert(mechStats['MONGODB-X509'] !== undefined); Object.keys(mechStats).forEach(function(mech) { - const stats = mechStats[mech].speculativeAuthenticate; + const specStats = mechStats[mech].speculativeAuthenticate; + const clusterStats = mechStats[mech].clusterAuthenticate; if (mech === 'MONGODB-X509') { - assert.gte(stats.received, 2); + assert.gte(specStats.received, 2); + assert.gte(clusterStats.received, 2); } else { - assert.eq(stats.received, 0); + assert.eq(specStats.received, 0); } - assert.eq(stats.received, stats.successful); + assert.eq(specStats.received, specStats.successful); + assert.gte(clusterStats.received, clusterStats.successful); }); admin.logout(); diff --git a/jstests/ssl/speculative-auth-sharding.js b/jstests/ssl/speculative-auth-sharding.js index 56af5fddaca..7a198c7983b 100644 --- a/jstests/ssl/speculative-auth-sharding.js +++ b/jstests/ssl/speculative-auth-sharding.js @@ -55,6 +55,17 @@ assert.eq(runMongoProgram('mongo', '--eval', ';'), 0); +assert.eq(runMongoProgram('mongo', + uri, + '--tls', + '--tlsCertificateKeyFile', + SERVER_CERT, + '--tlsCAFile', + CA_CERT, + '--tlsAllowInvalidHostnames', + '--eval', + ';'), + 0); const authStats = assert.commandWorked(admin.runCommand({serverStatus: 1})) .security.authentication.mechanisms['MONGODB-X509']; @@ -63,14 +74,20 @@ jsTest.log('Authenticated stats: ' + tojson(authStats)); // Got and succeeded an additional speculation. const initSpec = initialStats.speculativeAuthenticate; const authSpec = authStats.speculativeAuthenticate; -assert.eq(authSpec.received, initSpec.received + 1); -assert.eq(authSpec.successful, initSpec.successful + 1); +assert.eq(authSpec.received, initSpec.received + 2); +assert.eq(authSpec.successful, initSpec.successful + 2); // Got and succeeded an additional auth. const initAuth = initialStats.authenticate; const authAuth = authStats.authenticate; -assert.eq(authAuth.received, initAuth.received + 1); -assert.eq(authAuth.successful, initAuth.successful + 1); +assert.eq(authAuth.received, initAuth.received + 2); +assert.eq(authAuth.successful, initAuth.successful + 2); + +// Got and succeeded intra-cluster auth. +const initCluster = initialStats.clusterAuthenticate; +const authCluster = authStats.clusterAuthenticate; +assert.eq(authCluster.received, initCluster.received + 1); +assert.eq(authCluster.successful, initCluster.successful + 1); ///////////////////////////////////////////////////////////////////////////// diff --git a/jstests/ssl/speculative-authenticate.js b/jstests/ssl/speculative-authenticate.js index 41b7139230f..492469466df 100644 --- a/jstests/ssl/speculative-authenticate.js +++ b/jstests/ssl/speculative-authenticate.js @@ -8,6 +8,7 @@ const mongod = MongoRunner.runMongod({ tlsMode: 'requireTLS', tlsCertificateKeyFile: 'jstests/libs/server.pem', tlsCAFile: 'jstests/libs/ca.pem', + clusterAuthMode: "x509", }); const admin = mongod.getDB('admin'); const external = mongod.getDB('$external'); @@ -32,6 +33,19 @@ function test(uri) { assert.eq(0, x509); } +function testInternal(uri) { + const x509 = runMongoProgram('mongo', + '--tls', + '--tlsCAFile', + 'jstests/libs/ca.pem', + '--tlsCertificateKeyFile', + 'jstests/libs/server.pem', + uri, + '--eval', + ';'); + assert.eq(0, x509); +} + function assertStats(cb) { const mechStats = assert.commandWorked(admin.runCommand({serverStatus: 1})) .security.authentication.mechanisms; @@ -64,5 +78,23 @@ assertStats(function(mechStats) { assert.eq(stats.successful, 1); }); +// We haven't done any cluster auth yet, so clusterAuthenticate counts should be 0 +assertStats(function(mechStats) { + const stats = mechStats['MONGODB-X509'].clusterAuthenticate; + assert.eq(stats.received, 0); + assert.eq(stats.successful, 0); +}); + +// Connect intra-cluster with speculation. +testInternal(baseURI + '?authMechanism=MONGODB-X509'); +assertStats(function(mechStats) { + const specStats = mechStats['MONGODB-X509'].speculativeAuthenticate; + const clusterStats = mechStats['MONGODB-X509'].clusterAuthenticate; + assert.eq(specStats.received, 2); + assert.eq(specStats.successful, 2); + assert.eq(clusterStats.received, 1); + assert.eq(clusterStats.successful, 1); +}); + MongoRunner.stopMongod(mongod); })(); diff --git a/src/mongo/client/authenticate.h b/src/mongo/client/authenticate.h index ee50f8c8ade..44d90eae612 100644 --- a/src/mongo/client/authenticate.h +++ b/src/mongo/client/authenticate.h @@ -72,6 +72,7 @@ constexpr auto kMechanismMongoAWS = "MONGODB-AWS"_sd; constexpr auto kInternalAuthFallbackMechanism = kMechanismScramSha1; constexpr auto kSpeculativeAuthenticate = "speculativeAuthenticate"_sd; +constexpr auto kClusterAuthenticate = "clusterAuthenticate"_sd; constexpr auto kAuthenticateCommand = "authenticate"_sd; /** diff --git a/src/mongo/db/auth/sasl_commands.cpp b/src/mongo/db/auth/sasl_commands.cpp index 7f4b1010462..81d784e64a3 100644 --- a/src/mongo/db/auth/sasl_commands.cpp +++ b/src/mongo/db/auth/sasl_commands.cpp @@ -335,18 +335,26 @@ bool runSaslStart(OperationContext* opCtx, } std::string principalName; - auto swSession = doSaslStart(opCtx, db, cmdObj, &result, &principalName, speculative); - - if (!swSession.isOK() || swSession.getValue()->getMechanism().isSuccess()) { - audit::logAuthentication( - client, mechanismName, UserName(principalName, db), swSession.getStatus().code()); - uassertStatusOK(swSession.getStatus()); - if (swSession.getValue()->getMechanism().isSuccess()) { + try { + auto session = + uassertStatusOK(doSaslStart(opCtx, db, cmdObj, &result, &principalName, speculative)); + const bool isClusterMember = session->getMechanism().isClusterMember(); + if (isClusterMember) { + uassertStatusOK(authCounter.incClusterAuthenticateReceived(mechanismName)); + } + if (session->getMechanism().isSuccess()) { uassertStatusOK(authCounter.incAuthenticateSuccessful(mechanismName)); + if (isClusterMember) { + uassertStatusOK(authCounter.incClusterAuthenticateSuccessful(mechanismName)); + } + audit::logAuthentication( + client, mechanismName, UserName(principalName, db), Status::OK().code()); + } else { + AuthenticationSession::swap(client, session); } - } else { - auto session = std::move(swSession.getValue()); - AuthenticationSession::swap(client, session); + } catch (const AssertionException& ex) { + audit::logAuthentication(client, mechanismName, UserName(principalName, db), ex.code()); + throw; } return true; @@ -408,6 +416,10 @@ bool CmdSaslContinue::run(OperationContext* opCtx, if (mechanism.isSuccess()) { uassertStatusOK( authCounter.incAuthenticateSuccessful(mechanism.mechanismName().toString())); + if (mechanism.isClusterMember()) { + uassertStatusOK(authCounter.incClusterAuthenticateSuccessful( + mechanism.mechanismName().toString())); + } } } else { AuthenticationSession::swap(client, sessionGuard); diff --git a/src/mongo/db/auth/sasl_mechanism_registry.h b/src/mongo/db/auth/sasl_mechanism_registry.h index 98f2d8ddae9..0215328d9cb 100644 --- a/src/mongo/db/auth/sasl_mechanism_registry.h +++ b/src/mongo/db/auth/sasl_mechanism_registry.h @@ -155,6 +155,15 @@ public: } /** + * Provides logic for determining if a user is a cluster member or an actual client for SASL + * authentication mechanisms + */ + bool isClusterMember() const { + return _principalName == internalSecurity.user->getName().getUser().toString() && + getAuthenticationDatabase() == internalSecurity.user->getName().getDB(); + }; + + /** * Performs a single step of a SASL exchange. Takes an input provided by a client, * and either returns an error, or a response to be sent back. */ diff --git a/src/mongo/db/commands/authentication_commands.cpp b/src/mongo/db/commands/authentication_commands.cpp index 77f014207fb..4bdb7f67b62 100644 --- a/src/mongo/db/commands/authentication_commands.cpp +++ b/src/mongo/db/commands/authentication_commands.cpp @@ -44,6 +44,7 @@ #include "mongo/client/sasl_client_authenticate.h" #include "mongo/config.h" #include "mongo/db/audit.h" +#include "mongo/db/auth/authentication_session.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/sasl_options.h" @@ -100,6 +101,10 @@ Status _authenticateX509(OperationContext* opCtx, const UserName& user, const BS } else { // Handle internal cluster member auth, only applies to server-server connections if (sslConfiguration->isClusterMember(clientName)) { + Status status = authCounter.incClusterAuthenticateReceived("MONGODB-X509"); + if (!status.isOK()) { + return status; + } int clusterAuthMode = serverGlobalParams.clusterAuthMode.load(); if (clusterAuthMode == ServerGlobalParams::ClusterAuthMode_undefined || clusterAuthMode == ServerGlobalParams::ClusterAuthMode_keyFile) { @@ -123,6 +128,10 @@ Status _authenticateX509(OperationContext* opCtx, const UserName& user, const BS "Client isn't a mongod or mongos, but is connecting with a " "certificate with cluster membership"); } + status = authCounter.incClusterAuthenticateSuccessful("MONGODB-X509"); + if (!status.isOK()) { + return status; + } } authorizationSession->grantInternalAuthorization(client); @@ -292,48 +301,40 @@ bool CmdAuthenticate::run(OperationContext* opCtx, user = internalSecurity.user->getName(); } - Status status = authCounter.incAuthenticateReceived(mechanism); - if (status.isOK()) { - status = _authenticate(opCtx, mechanism, user, cmdObj); - } - audit::logAuthentication(Client::getCurrent(), mechanism, user, status.code()); + try { + uassertStatusOK(authCounter.incAuthenticateReceived(mechanism)); + + uassertStatusOK(_authenticate(opCtx, mechanism, user, cmdObj)); + audit::logAuthentication(opCtx->getClient(), mechanism, user, ErrorCodes::OK); - if (!status.isOK()) { if (!serverGlobalParams.quiet.load()) { - auto const client = opCtx->getClient(); + LOGV2(20429, + "Successfully authenticated as principal {user} on {db} from client {client}", + "Successfully authenticated", + "user"_attr = user.getUser(), + "db"_attr = user.getDB(), + "client"_attr = opCtx->getClient()->session()->remote()); + } + + uassertStatusOK(authCounter.incAuthenticateSuccessful(mechanism)); + + result.append("dbname", user.getDB()); + result.append("user", user.getUser()); + return true; + } catch (const AssertionException& ex) { + auto status = ex.toStatus(); + auto const client = opCtx->getClient(); + audit::logAuthentication(client, mechanism, user, status.code()); + if (!serverGlobalParams.quiet.load()) { LOGV2(20428, - "Failed to authenticate {user} from client {client} with mechanism " - "{mechanism}: {error}", "Failed to authenticate", "user"_attr = user, "client"_attr = client->getRemote(), "mechanism"_attr = mechanism, "error"_attr = status); } - sleepmillis(saslGlobalParams.authFailedDelay.load()); - if (status.code() == ErrorCodes::AuthenticationFailed) { - // Statuses with code AuthenticationFailed may contain messages we do not wish to - // reveal to the user, so we return a status with the message "auth failed". - uasserted(ErrorCodes::AuthenticationFailed, "auth failed"); - } else { - uassertStatusOK(status); - } - return false; + throw; } - - if (!serverGlobalParams.quiet.load()) { - LOGV2(20429, - "Successfully authenticated as principal {user} on {db} from client {client}", - "Successfully authenticated", - "user"_attr = user.getUser(), - "db"_attr = user.getDB(), - "client"_attr = opCtx->getClient()->session()->remote()); - } - - uassertStatusOK(authCounter.incAuthenticateSuccessful(mechanism)); - result.append("dbname", user.getDB()); - result.append("user", user.getUser()); - return true; } Status CmdAuthenticate::_authenticate(OperationContext* opCtx, diff --git a/src/mongo/db/stats/counters.cpp b/src/mongo/db/stats/counters.cpp index 63a0b6eb66d..4180275393b 100644 --- a/src/mongo/db/stats/counters.cpp +++ b/src/mongo/db/stats/counters.cpp @@ -244,6 +244,24 @@ Status AuthCounter::incAuthenticateSuccessful(const std::string& mechanism) try << " which is not enabled"}; } +Status AuthCounter::incClusterAuthenticateReceived(const std::string& mechanism) try { + _mechanisms.at(mechanism).clusterAuthenticate.received.fetchAndAddRelaxed(1); + return Status::OK(); +} catch (const std::out_of_range&) { + return {ErrorCodes::BadValue, + str::stream() << "Received authentication for mechanism " << mechanism + << " which is unknown or not enabled"}; +} + +Status AuthCounter::incClusterAuthenticateSuccessful(const std::string& mechanism) try { + _mechanisms.at(mechanism).clusterAuthenticate.successful.fetchAndAddRelaxed(1); + return Status::OK(); +} catch (const std::out_of_range&) { + return {ErrorCodes::BadValue, + str::stream() << "Received authentication for mechanism " << mechanism + << " which is not enabled"}; +} + /** * authentication: { * "mechanisms": { @@ -275,6 +293,16 @@ void AuthCounter::append(BSONObjBuilder* b) { } { + const auto received = it.second.clusterAuthenticate.received.load(); + const auto successful = it.second.clusterAuthenticate.successful.load(); + + BSONObjBuilder clusterAuthBuilder(mechBuilder.subobjStart(auth::kClusterAuthenticate)); + clusterAuthBuilder.append("received", received); + clusterAuthBuilder.append("successful", successful); + clusterAuthBuilder.done(); + } + + { const auto received = it.second.authenticate.received.load(); const auto successful = it.second.authenticate.successful.load(); diff --git a/src/mongo/db/stats/counters.h b/src/mongo/db/stats/counters.h index 9b10cb2a049..a202746be03 100644 --- a/src/mongo/db/stats/counters.h +++ b/src/mongo/db/stats/counters.h @@ -226,6 +226,9 @@ public: Status incAuthenticateReceived(const std::string& mechanism); Status incAuthenticateSuccessful(const std::string& mechanism); + Status incClusterAuthenticateReceived(const std::string& mechanism); + Status incClusterAuthenticateSuccessful(const std::string& mechanism); + void append(BSONObjBuilder*); void initializeMechanismMap(const std::vector<std::string>&); @@ -240,6 +243,10 @@ private: AtomicWord<long long> received; AtomicWord<long long> successful; } authenticate; + struct { + AtomicWord<long long> received; + AtomicWord<long long> successful; + } clusterAuthenticate; }; using MechanismMap = std::map<std::string, MechanismData>; |