jstests/auth/user-cache-invalidation.js
/**
 * Create a user with read permission and simply
 * auth and read in a parallel shell.
 *
 * We use FailPoint 'authLocalGetUser.resolveRolesDelayMS' to
 * give us time to invalidate the user mid-acquisition.
 *
 * We also use pauseBatchApplicationBeforeCompletion with replsets
 * to try to slip the parallel client into the wrong snapshot.
 *
 * When we call revokeRolesFromUser(), this invalidates the
 * user acquisition in progress and forces it to restart.
 *
 * If the restarted acquisition was on the same snapshot,
 * then we'd end up with a stale user being injected into
 * the cache anyway.
 *
 * If the snapshot is advanced, then our parallel shell user
 * sees testRole successfully revoked, and our query fails.
 */
Create a user with read permission and simply
auth and read in a parallel shell.

We use FailPoint 'authLocalGetUser.resolveRolesDelayMS' to
give us time to invalidate the user mid-acquisition.

We also use pauseBatchApplicationBeforeCompletion with replsets
to try to slip the parallel client into the wrong snapshot.

When we call revokeRolesFromUser(), this invalidates the
user acquisition in progress and forces it to restart.

If the restarted acquisition was on the same snapshot,
then we'd end up with a stale user being injected into
the cache anyway.

If the snapshot is advanced, then our parallel shell user
sees testRole successfully revoked, and our query fails.
Create a user with read permission and simply auth and read in a parallel shell . We use FailPoint ' authLocalGetUser . resolveRolesDelayMS ' to give us time to invalidate the user mid - acquisition . We also use pauseBatchApplicationBeforeCompletion with replsets to try to slip the parallel client into the wrong snapshot . When we call revokeRolesFromUser () this invalidates the user acquisition in progress and forces it to restart . If the restarted acquisition was on the same snapshot then we ' d end up with a stale user being injected into the cache anyway . If the snapshot is advanced then our parallel shell user sees testRole successfully revoked and our query fails .
function runTest(writeNode, readNode, awaitReplication, lock, unlock) {
    const writeAdmin = writeNode.getDB('admin');
    const readAdmin = readNode.getDB('admin');
    writeAdmin.createUser({user: 'admin', pwd: 'pwd', roles: ['root']});
    assert(writeAdmin.auth('admin', 'pwd'));
    assert.soon(() => readAdmin.auth('admin', 'pwd'));
    assert.commandWorked(readNode.setLogLevel(3, 'accessControl'));
    const writeTest = writeNode.getDB(testDB);
    writeTest.createUser({user: testUser, pwd: 'pwd', roles: [testRole]});
    assert.writeOK(writeTest.coll.insert({x: 1}));
    awaitReplication();
    lock();
    const startTime = Date.now();
    let currentTime = startTime;
    function assertHasLogAndAdvance(conn, cond) {
        const entry = assertHasLog(conn, cond, currentTime);
        currentTime = entry.t;
        return entry;
    }
    const fp = configureFailPoint(
        readNode, 'waitForUserCacheInvalidation', {userName: {db: testDB, user: testUser}});
    const kResolveRolesDelayMS = 5 * 1000;
    assert.commandWorked(readAdmin.runCommand({
        configureFailPoint: 'authLocalGetUser',
        mode: 'alwaysOn',
        data: {resolveRolesDelayMS: NumberInt(kResolveRolesDelayMS)}
    }));
    const thread = new Thread(function(port, testUser, testDB) {
        const mongo = new Mongo('localhost:' + port);
        assert(mongo);
        const test = mongo.getDB(testDB);
        assert(test);
        jsTest.log('Starting auth');
        assert(test.auth(testUser, 'pwd'));
        jsTest.log('Completed auth');
        assert.throws(() => test.coll.findOne({}), [], "Find succeeded despite revokeRoleFromUser");
        jsTest.log('Ran command');
    }, readNode.port, testUser, testDB);
    thread.start();
    jsTest.log('Waiting for initial resolve roles');
    {
        const entry = assertHasLogAndAdvance(readNode, resolveRolesDelayEvent);
        assert.eq(entry.attr.userName.db, testDB);
        assert.eq(entry.attr.userName.user, testUser);
        assert.eq(entry.attr.directRoles.length, 1);
        assert.eq(entry.attr.directRoles[0].role, testRole);
        assert.eq(entry.attr.directRoles[0].db, testDB);
    }
    assertLacksLog(readNode, invalidateUserEvent, startTime, currentTime);
    fp.wait();
    jsTest.log('Mutating');
    writeTest.revokeRolesFromUser(testUser, [testRole], {w: 1});
    jsTest.log('Looking for invalidation');
    assertHasLogAndAdvance(readNode, invalidateUserEvent);
    jsTest.log('Looking for new acquisiiton');
    assertHasLogAndAdvance(readNode, acquireUserEvent);
    unlock();
    jsTest.log('Waiting for second resolve roles');
    assertHasLogAndAdvance(readNode, function(entry) {
        if (!resolveRolesDelayEvent(entry)) {
            return false;
        }
        return entry.attr.directRoles.length == 0;
    });
    jsTest.log('Looking for authZ failure for read after revokeRolesFromUser');
    assertHasLogAndAdvance(readNode, authFailureEvent);
    fp.off();
    thread.join();
    jsTest.log('Thread complete');
    writeAdmin.logout();
    readAdmin.logout();
}
function runTest ( writeNode , readNode , awaitReplication , lock , unlock ) { const writeAdmin = writeNode . getDB ( 'admin' ) ; const readAdmin = readNode . getDB ( 'admin' ) ; writeAdmin . createUser ( { user : 'admin' , pwd : 'pwd' , roles : [ 'root' ] } ) ; assert ( writeAdmin . auth ( 'admin' , 'pwd' ) ) ; assert . soon ( ( ) => readAdmin . auth ( 'admin' , 'pwd' ) ) ; assert . commandWorked ( readNode . setLogLevel ( 3 , 'accessControl' ) ) ; const writeTest = writeNode . getDB ( testDB ) ; writeTest . createUser ( { user : testUser , pwd : 'pwd' , roles : [ testRole ] } ) ; assert . writeOK ( writeTest . coll . insert ( { x : 1 } ) ) ; awaitReplication ( ) ; lock ( ) ; const startTime = Date . now ( ) ; let currentTime = startTime ; function assertHasLogAndAdvance ( conn , cond ) { const entry = assertHasLog ( conn , cond , currentTime ) ; currentTime = entry . t ; return entry ; } const fp = configureFailPoint ( readNode , 'waitForUserCacheInvalidation' , { userName : { db : testDB , user : testUser } } ) ; const kResolveRolesDelayMS = 5 * 1000 ; assert . commandWorked ( readAdmin . runCommand ( { configureFailPoint : 'authLocalGetUser' , mode : 'alwaysOn' , data : { resolveRolesDelayMS : NumberInt ( kResolveRolesDelayMS ) } } ) ) ; const thread = new Thread ( function ( port , testUser , testDB ) { const mongo = new Mongo ( 'localhost:' + port ) ; assert ( mongo ) ; const test = mongo . getDB ( testDB ) ; assert ( test ) ; jsTest . log ( 'Starting auth' ) ; assert ( test . auth ( testUser , 'pwd' ) ) ; jsTest . log ( 'Completed auth' ) ; assert . throws ( ( ) => test . coll . findOne ( { } ) , [ ] , "Find succeeded despite revokeRoleFromUser" ) ; jsTest . log ( 'Ran command' ) ; } , readNode . port , testUser , testDB ) ; thread . start ( ) ; jsTest . log ( 'Waiting for initial resolve roles' ) ; { const entry = assertHasLogAndAdvance ( readNode , resolveRolesDelayEvent ) ; assert . eq ( entry . attr . userName . db , testDB ) ; assert . eq ( entry . attr . userName . user , testUser ) ; assert . eq ( entry . attr . directRoles . length , 1 ) ; assert . eq ( entry . attr . directRoles [ 0 ] . role , testRole ) ; assert . eq ( entry . attr . directRoles [ 0 ] . db , testDB ) ; } assertLacksLog ( readNode , invalidateUserEvent , startTime , currentTime ) ; fp . wait ( ) ; jsTest . log ( 'Mutating' ) ; writeTest . revokeRolesFromUser ( testUser , [ testRole ] , { w : 1 } ) ; jsTest . log ( 'Looking for invalidation' ) ; assertHasLogAndAdvance ( readNode , invalidateUserEvent ) ; jsTest . log ( 'Looking for new acquisiiton' ) ; assertHasLogAndAdvance ( readNode , acquireUserEvent ) ; unlock ( ) ; jsTest . log ( 'Waiting for second resolve roles' ) ; assertHasLogAndAdvance ( readNode , function ( entry ) { if ( ! resolveRolesDelayEvent ( entry ) ) { return false ; } return entry . attr . directRoles . length == 0 ; } ) ; jsTest . log ( 'Looking for authZ failure for read after revokeRolesFromUser' ) ; assertHasLogAndAdvance ( readNode , authFailureEvent ) ; fp . off ( ) ; thread . join ( ) ; jsTest . log ( 'Thread complete' ) ; writeAdmin . logout ( ) ; readAdmin . logout ( ) ; }
Create a user with read permission and simply
 auth and read in a parallel shell.
Create a user with read permission and simply auth and read in a parallel shell .
// Set the failpoint before we start the parallel thread. // We need some time to mutate the auth state before the acquisition completes. // Wait for initial auth to start. // Our initial acquisition has the read role. // Wait for our find to hit the fail point. // Mutate the user to cause an invalidation. // Use writeConcern 1 to avoid blocking on the secondary applications. // This acquisition comes from a later snapshot which has no roles.