Files
mongo/jstests/replsets/rollback_auth.js
Kaloian Manassiev 6745d91683 SERVER-21050 Introduce ReplSetTest.State enumeration everywhere
Makes all JS tests access the replica set member state from the class
itself instead of the object instance. Also removes some unused code.
2015-12-11 13:32:56 -05:00

196 lines
8.6 KiB
JavaScript

// Tests rollback of auth data in replica sets.
// This test creates a user and then does two different sets of updates to that user's privileges
// using the replSetTest command to trigger a rollback and verify that at the end the access control
// data is rolled back correctly and the user only has access to the expected collections.
//
// If all data-bearing nodes in a replica set are using an ephemeral storage engine, the set will
// not be able to survive a scenario where all data-bearing nodes are down simultaneously. In such a
// scenario, none of the members will have any data, and upon restart will each look for a member to
// inital sync from, so no primary will be elected. This test induces such a scenario, so cannot be
// run on ephemeral storage engines.
// @tags: [requires_persistence]
(function () {
"use strict";
// helper function for verifying contents at the end of the test
var checkFinalResults = function(db) {
assert.commandWorked(db.runCommand({dbStats: 1}));
assert.commandFailedWithCode(db.runCommand({collStats: 'foo'}), authzErrorCode);
assert.commandFailedWithCode(db.runCommand({collStats: 'bar'}), authzErrorCode);
assert.commandWorked(db.runCommand({collStats: 'baz'}));
assert.commandWorked(db.runCommand({collStats: 'foobar'}));
}
var authzErrorCode = 13;
jsTestLog("Setting up replica set");
var name = "rollbackAuth";
var replTest = new ReplSetTest({name: name,
nodes: 3,
keyFile: 'jstests/libs/key1' });
var nodes = replTest.nodeList();
var conns = replTest.startSet();
replTest.initiate({ "_id": "rollbackAuth",
"members": [
{ "_id": 0, "host": nodes[0], "priority": 3 },
{ "_id": 1, "host": nodes[1] },
{ "_id": 2, "host": nodes[2], arbiterOnly: true}
]});
// Make sure we have a master
replTest.waitForState(replTest.nodes[0], ReplSetTest.State.PRIMARY, 60 * 1000);
var master = replTest.getPrimary();
var a_conn = conns[0];
var b_conn = conns[1];
a_conn.setSlaveOk();
b_conn.setSlaveOk();
var A = a_conn.getDB("admin");
var B = b_conn.getDB("admin");
var a = a_conn.getDB("test");
var b = b_conn.getDB("test");
assert.eq(master, conns[0], "conns[0] assumed to be master");
assert.eq(a_conn, master);
// Make sure we have an arbiter
assert.soon(function () {
var res = conns[2].getDB("admin").runCommand({ replSetGetStatus: 1 });
return res.myState == 7;
}, "Arbiter failed to initialize.");
jsTestLog("Creating initial data");
// Create collections that will be used in test
A.createUser({user: 'admin', pwd: 'pwd', roles: ['root']});
A.auth('admin', 'pwd');
a.foo.insert({a:1});
a.bar.insert({a:1});
a.baz.insert({a:1});
a.foobar.insert({a:1});
// Set up user admin user
A.createUser({user: 'userAdmin', pwd: 'pwd', roles: ['userAdminAnyDatabase']});
A.auth('userAdmin', 'pwd'); // Logs out of admin@admin user
B.auth('userAdmin', 'pwd');
// Create a basic user and role
A.createRole({role: 'replStatusRole', // To make awaitReplication() work
roles: [],
privileges: [{resource: {cluster: true}, actions: ['replSetGetStatus']},
{resource: {db: 'local', collection: ''}, actions: ['find']},
{resource: {db: 'local', collection: 'system.replset'},
actions: ['find']}]});
a.createRole({role: 'myRole', roles: [], privileges: [{resource: {db: 'test', collection: ''},
actions: ['dbStats']}]});
a.createUser({user: 'spencer',
pwd: 'pwd',
roles: ['myRole', {role: 'replStatusRole', db: 'admin'}]});
assert(a.auth('spencer', 'pwd'));
// wait for secondary to get this data
assert.soon(function() {
return b.auth('spencer', 'pwd');
});
assert.commandWorked(a.runCommand({dbStats: 1}));
assert.commandFailedWithCode(a.runCommand({collStats: 'foo'}), authzErrorCode);
assert.commandFailedWithCode(a.runCommand({collStats: 'bar'}), authzErrorCode);
assert.commandFailedWithCode(a.runCommand({collStats: 'baz'}), authzErrorCode);
assert.commandFailedWithCode(a.runCommand({collStats: 'foobar'}), authzErrorCode);
assert.commandWorked(b.runCommand({dbStats: 1}));
assert.commandFailedWithCode(b.runCommand({collStats: 'foo'}), authzErrorCode);
assert.commandFailedWithCode(b.runCommand({collStats: 'bar'}), authzErrorCode);
assert.commandFailedWithCode(b.runCommand({collStats: 'baz'}), authzErrorCode);
assert.commandFailedWithCode(b.runCommand({collStats: 'foobar'}), authzErrorCode);
jsTestLog("Doing writes that will eventually be rolled back");
// down A and wait for B to become master
replTest.stop(0);
assert.soon(function () { try { return B.isMaster().ismaster; } catch(e) { return false; } },
"B didn't become master",
60000,
1000);
printjson(b.adminCommand('replSetGetStatus'));
// Modify the the user and role in a way that will be rolled back.
b.grantPrivilegesToRole('myRole',
[{resource: {db: 'test', collection: 'foo'}, actions: ['collStats']}],
{}); // Default write concern will wait for majority, which will time out.
b.createRole({role: 'temporaryRole',
roles: [],
privileges: [{resource: {db: 'test', collection: 'bar'}, actions: ['collStats']}]},
{}); // Default write concern will wait for majority, which will time out.
b.grantRolesToUser('spencer',
['temporaryRole'],
{}); // Default write concern will wait for majority, which will time out.
assert.commandWorked(b.runCommand({dbStats: 1}));
assert.commandWorked(b.runCommand({collStats: 'foo'}));
assert.commandWorked(b.runCommand({collStats: 'bar'}));
assert.commandFailedWithCode(b.runCommand({collStats: 'baz'}), authzErrorCode);
assert.commandFailedWithCode(b.runCommand({collStats: 'foobar'}), authzErrorCode);
// down B, bring A back up, then wait for A to become master
// insert new data into A so that B will need to rollback when it reconnects to A
replTest.stop(1);
replTest.restart(0);
assert.soon(function () { try { return A.isMaster().ismaster; } catch(e) { return false; } },
"A didn't become master",
60000,
1000);
// A should not have the new data as it was down
assert.commandWorked(a.runCommand({dbStats: 1}));
assert.commandFailedWithCode(a.runCommand({collStats: 'foo'}), authzErrorCode);
assert.commandFailedWithCode(a.runCommand({collStats: 'bar'}), authzErrorCode);
assert.commandFailedWithCode(a.runCommand({collStats: 'baz'}), authzErrorCode);
assert.commandFailedWithCode(a.runCommand({collStats: 'foobar'}), authzErrorCode);
jsTestLog("Doing writes that should persist after the rollback");
// Modify the user and role in a way that will persist.
A.auth('userAdmin', 'pwd');
// Default write concern will wait for majority, which would time out
// so we override it with an empty write concern
a.grantPrivilegesToRole('myRole',
[{resource: {db: 'test', collection: 'baz'}, actions: ['collStats']}],
{});
a.createRole({role: 'persistentRole',
roles: [],
privileges: [{resource: {db: 'test', collection: 'foobar'},
actions: ['collStats']}]},
{});
a.grantRolesToUser('spencer',
['persistentRole'],
{});
A.logout();
a.auth('spencer', 'pwd');
// A has the data we just wrote, but not what B wrote before
checkFinalResults(a);
jsTestLog("Triggering rollback");
// bring B back in contact with A
// as A is primary, B will roll back and then catch up
replTest.restart(1);
authutil.asCluster(replTest.nodes,
'jstests/libs/key1',
function() { replTest.awaitReplication(); });
assert.soon(function() {
return b.auth('spencer', 'pwd');
});
// Now both A and B should agree
checkFinalResults(a);
checkFinalResults(b);
replTest.stopSet();
}());