summaryrefslogtreecommitdiff
path: root/jstests/replsets/rollback_auth.js
diff options
context:
space:
mode:
Diffstat (limited to 'jstests/replsets/rollback_auth.js')
-rw-r--r--jstests/replsets/rollback_auth.js191
1 files changed, 191 insertions, 0 deletions
diff --git a/jstests/replsets/rollback_auth.js b/jstests/replsets/rollback_auth.js
new file mode 100644
index 00000000000..bc12268d95b
--- /dev/null
+++ b/jstests/replsets/rollback_auth.js
@@ -0,0 +1,191 @@
+// 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.
+
+var authzErrorCode = 13;
+
+jsTestLog("Setting up replica set");
+
+var replTest = new ReplSetTest({ name: 'rollbackAuth', nodes: 3, keyFile: 'jstests/libs/key1' });
+var nodes = replTest.nodeList();
+var conns = replTest.startSet();
+replTest.initiate({ "_id": "rollbackAuth",
+ "members": [
+ { "_id": 0, "host": nodes[0] },
+ { "_id": 1, "host": nodes[1] },
+ { "_id": 2, "host": nodes[2], arbiterOnly: true}]
+ });
+
+// Make sure we have a master
+var master = replTest.getMaster();
+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(master == conns[0], "conns[0] assumed to be master");
+assert(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");
+
+// Blind A
+A.runCommand({ replSetTest: 1, blind: true });
+reconnect(a);
+reconnect(b);
+
+// Wait for B to be master
+replTest.waitForState(b_conn, replTest.PRIMARY, 60000);
+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);
+
+// a should not have the new data as it was in blind state.
+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);
+
+// Now blind B instead of A
+B.runCommand({ replSetTest: 1, blind: true });
+reconnect(a);
+reconnect(b);
+
+A.runCommand({ replSetTest: 1, blind: false });
+reconnect(a);
+reconnect(b);
+
+replTest.waitForState(a_conn, replTest.PRIMARY, 60000);
+
+
+jsTestLog("Doing writes that should persist after the rollback");
+
+// Modify the user and role in a way that will persist.
+a.grantPrivilegesToRole('myRole',
+ [{resource: {db: 'test', collection: 'baz'}, actions: ['collStats']}],
+ {}); // Default write concern will wait for majority, which will time out.
+a.createRole({role: 'persistentRole',
+ roles: [],
+ privileges: [{resource: {db: 'test', collection: 'foobar'}, actions: ['collStats']}]},
+ {}); // Default write concern will wait for majority, which will time out.
+a.grantRolesToUser('spencer',
+ ['persistentRole'],
+ {}); // Default write concern will wait for majority, which will time out.
+
+// A has the data we just wrote, but not what B wrote before
+assert.commandWorked(a.runCommand({dbStats: 1}));
+assert.commandFailedWithCode(a.runCommand({collStats: 'foo'}), authzErrorCode);
+assert.commandFailedWithCode(a.runCommand({collStats: 'bar'}), authzErrorCode);
+assert.commandWorked(a.runCommand({collStats: 'baz'}));
+assert.commandWorked(a.runCommand({collStats: 'foobar'}));
+
+// B has what it wrote before, but not what A just wrote, since it's still blind
+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);
+
+
+jsTestLog("Triggering rollback");
+
+// bring B back online
+// as A is primary, B will roll back and then catch up
+B.runCommand({ replSetTest: 1, blind: false });
+reconnect(a);
+reconnect(b);
+replTest.awaitReplication();
+replTest.waitForState(a_conn, replTest.PRIMARY, 60000);
+replTest.waitForState(b_conn, replTest.SECONDARY, 60000);
+
+// Now both A and B should agree
+assert.commandWorked(a.runCommand({dbStats: 1}));
+assert.commandFailedWithCode(a.runCommand({collStats: 'foo'}), authzErrorCode);
+assert.commandFailedWithCode(a.runCommand({collStats: 'bar'}), authzErrorCode);
+assert.commandWorked(a.runCommand({collStats: 'baz'}));
+assert.commandWorked(a.runCommand({collStats: 'foobar'}));
+
+assert.commandWorked(b.runCommand({dbStats: 1}));
+assert.commandFailedWithCode(b.runCommand({collStats: 'foo'}), authzErrorCode);
+assert.commandFailedWithCode(b.runCommand({collStats: 'bar'}), authzErrorCode);
+assert.commandWorked(b.runCommand({collStats: 'baz'}));
+assert.commandWorked(b.runCommand({collStats: 'foobar'}));
+
+replTest.stopSet();