mongo/jstests/auth/basic_role_auth.js

527 lines
17 KiB
JavaScript

/**
* This file tests basic authorization for different roles in stand alone and sharded
* environment. This file covers all types of operations except commands.
*/
/**
* Data structure that contains all the users that are going to be used in the tests.
* The structure is as follows:
* 1st level field name: database names.
* 2nd level field name: user names.
* 3rd level is an object that has the format:
* { pwd: <password>, roles: [<list of roles>] }
* @tags: [requires_sharding]
*/
var AUTH_INFO = {
admin: {
root: {pwd: 'root', roles: ['root']},
cluster: {pwd: 'cluster', roles: ['clusterAdmin']},
anone: {pwd: 'none', roles: []},
aro: {pwd: 'ro', roles: ['read']},
arw: {pwd: 'rw', roles: ['readWrite']},
aadmin: {pwd: 'admin', roles: ['dbAdmin']},
auadmin: {pwd: 'uadmin', roles: ['userAdmin']},
any_ro: {pwd: 'ro', roles: ['readAnyDatabase']},
any_rw: {pwd: 'rw', roles: ['readWriteAnyDatabase']},
any_admin: {pwd: 'admin', roles: ['dbAdminAnyDatabase']},
any_uadmin: {pwd: 'uadmin', roles: ['userAdminAnyDatabase']}
},
test: {
none: {pwd: 'none', roles: []},
ro: {pwd: 'ro', roles: ['read']},
rw: {pwd: 'rw', roles: ['readWrite']},
roadmin: {pwd: 'roadmin', roles: ['read', 'dbAdmin']},
admin: {pwd: 'admin', roles: ['dbAdmin']},
uadmin: {pwd: 'uadmin', roles: ['userAdmin']}
}
};
// Constants that lists the privileges of a given role.
var READ_PERM = {query: 1, index_r: 1, killCursor: 1};
var READ_WRITE_PERM =
{insert: 1, update: 1, remove: 1, query: 1, index_r: 1, index_w: 1, killCursor: 1};
var ADMIN_PERM = {index_r: 1, index_w: 1, profile_r: 1};
var UADMIN_PERM = {user_r: 1, user_w: 1};
var CLUSTER_PERM =
{killOp: 1, currentOp: 1, fsync_unlock: 1, killCursor: 1, killAnyCursor: 1, profile_r: 1};
/**
* Checks whether an error occurs after running an operation.
*
* @param shouldPass {Boolean} true means that the operation should succeed.
* @param opFunc {function()} a function object which contains the operation to perform.
*/
var checkErr = function(shouldPass, opFunc) {
var success = true;
var exception = null;
try {
opFunc();
} catch (x) {
exception = x;
success = false;
}
assert(success == shouldPass,
'expected shouldPass: ' + shouldPass + ', got: ' + success + ', op: ' + tojson(opFunc) +
', exception: ' + tojson(exception));
};
/**
* Runs a series of operations against the db provided.
*
* @param db {DB} the database object to use.
* @param allowedActions {Object} the lists of operations that are allowed for the
* current user. The data structure is represented as a map with the presence of
* a field name means that the operation is allowed and not allowed if it is
* not present. The list of field names are: insert, update, remove, query, killOp,
* currentOp, index_r, index_w, profile_r, profile_w, user_r, user_w, killCursor,
* fsync_unlock.
*/
var testOps = function(db, allowedActions) {
checkErr(allowedActions.hasOwnProperty('insert'), function() {
var res = db.user.insert({y: 1});
if (res.hasWriteError())
throw Error("insert failed: " + tojson(res.getRawResponse()));
});
checkErr(allowedActions.hasOwnProperty('update'), function() {
var res = db.user.update({y: 1}, {z: 3});
if (res.hasWriteError())
throw Error("update failed: " + tojson(res.getRawResponse()));
});
checkErr(allowedActions.hasOwnProperty('remove'), function() {
var res = db.user.remove({y: 1});
if (res.hasWriteError())
throw Error("remove failed: " + tojson(res.getRawResponse()));
});
checkErr(allowedActions.hasOwnProperty('query'), function() {
db.user.findOne({y: 1});
});
checkErr(allowedActions.hasOwnProperty('killOp'), function() {
var errorCodeUnauthorized = 13;
var res = db.killOp(1);
if (res.code == errorCodeUnauthorized) {
throw Error("unauthorized killOp");
}
});
checkErr(allowedActions.hasOwnProperty('currentOp'), function() {
var errorCodeUnauthorized = 13;
var res = db.currentOp();
if (res.code == errorCodeUnauthorized) {
throw Error("unauthorized currentOp");
}
});
checkErr(allowedActions.hasOwnProperty('index_r'), function() {
var errorCodeUnauthorized = 13;
var res = db.runCommand({"listIndexes": "user"});
if (res.code == errorCodeUnauthorized) {
throw Error("unauthorized listIndexes");
}
});
checkErr(allowedActions.hasOwnProperty('index_w'), function() {
var res = db.user.createIndex({x: 1});
if (res.code == 13) { // Unauthorized
throw Error("unauthorized currentOp");
}
});
checkErr(allowedActions.hasOwnProperty('profile_r'), function() {
db.system.profile.findOne();
});
checkErr(allowedActions.hasOwnProperty('profile_w'), function() {
var res = db.system.profile.insert({x: 1});
if (res.hasWriteError()) {
throw Error("profile insert failed: " + tojson(res.getRawResponse()));
}
});
checkErr(allowedActions.hasOwnProperty('user_r'), function() {
var result = db.runCommand({usersInfo: 1});
if (!result.ok) {
throw new Error(tojson(result));
}
});
checkErr(allowedActions.hasOwnProperty('user_w'), function() {
db.createUser({user: 'a', pwd: 'a', roles: jsTest.basicUserRoles});
assert(db.dropUser('a'));
});
// Test for kill cursor
(function() {
var newConn = new Mongo(db.getMongo().host);
var dbName = db.getName();
var db2 = newConn.getDB(dbName);
if (db2.getName() == 'admin') {
assert.eq(1, db2.auth('aro', AUTH_INFO.admin.aro.pwd));
} else {
assert.eq(1, db2.auth('ro', AUTH_INFO.test.ro.pwd));
}
// Create cursor from db2.
var cmdRes = db2.runCommand({find: db2.kill_cursor.getName(), batchSize: 2});
assert.commandWorked(cmdRes);
var cursorId = cmdRes.cursor.id;
assert(!bsonBinaryEqual({cursorId: cursorId}, {cursorId: NumberLong(0)}),
"find command didn't return a cursor: " + tojson(cmdRes));
const shouldSucceed = (function() {
// admin users can do anything they want.
if (allowedActions.hasOwnProperty('killAnyCursor')) {
return true;
}
// users can kill their own cursors
const users = assert.commandWorked(db.runCommand({connectionStatus: 1}))
.authInfo.authenticatedUsers;
const users2 = assert.commandWorked(db2.runCommand({connectionStatus: 1}))
.authInfo.authenticatedUsers;
if (!users.length && !users2.length) {
// Special case, no-auth
return true;
}
return users.some(function(u) {
return users2.some(function(u2) {
return ((u.db === u2.db) && (u.user === u2.user));
});
});
})();
checkErr(shouldSucceed, function() {
// Issue killCursor command from db.
cmdRes = db.runCommand({killCursors: db2.kill_cursor.getName(), cursors: [cursorId]});
assert.commandWorked(cmdRes);
assert(bsonBinaryEqual({cursorId: cmdRes.cursorsKilled}, {cursorId: [cursorId]}),
"unauthorized to kill cursor: " + tojson(cmdRes));
});
})();
var isMongos = db.runCommand({isdbgrid: 1}).isdbgrid;
// Note: fsyncUnlock is not supported in mongos.
if (!isMongos) {
checkErr(allowedActions.hasOwnProperty('fsync_unlock'), function() {
var res = db.fsyncUnlock();
var errorCodeUnauthorized = 13;
if (res.code == errorCodeUnauthorized) {
throw Error("unauthorized fsyncUnlock");
}
});
}
};
// List of tests to run. Has the format:
//
// {
// name: {String} description of the test
// test: {function(Mongo)} the test function to run which accepts a Mongo connection
// object.
// }
var TESTS = [
{
name: 'Test multiple user login separate connection',
test: function(conn) {
var testDB = conn.getDB('test');
assert.eq(1, testDB.auth('ro', AUTH_INFO.test.ro.pwd));
var conn2 = new Mongo(conn.host);
var testDB2 = conn2.getDB('test');
assert.eq(1, testDB2.auth('uadmin', AUTH_INFO.test.uadmin.pwd));
testOps(testDB, READ_PERM);
testOps(testDB2, UADMIN_PERM);
}
},
{
name: 'Test user with no role',
test: function(conn) {
var testDB = conn.getDB('test');
assert.eq(1, testDB.auth('none', AUTH_INFO.test.none.pwd));
testOps(testDB, {});
}
},
{
name: 'Test read only user',
test: function(conn) {
var testDB = conn.getDB('test');
assert.eq(1, testDB.auth('ro', AUTH_INFO.test.ro.pwd));
testOps(testDB, READ_PERM);
}
},
{
name: 'Test read/write user',
test: function(conn) {
var testDB = conn.getDB('test');
assert.eq(1, testDB.auth('rw', AUTH_INFO.test.rw.pwd));
testOps(testDB, READ_WRITE_PERM);
}
},
{
name: 'Test read + dbAdmin user',
test: function(conn) {
var testDB = conn.getDB('test');
assert.eq(1, testDB.auth('roadmin', AUTH_INFO.test.roadmin.pwd));
var combinedPerm = Object.extend({}, READ_PERM);
combinedPerm = Object.extend(combinedPerm, ADMIN_PERM);
testOps(testDB, combinedPerm);
}
},
{
name: 'Test dbAdmin user',
test: function(conn) {
var testDB = conn.getDB('test');
assert.eq(1, testDB.auth('admin', AUTH_INFO.test.admin.pwd));
testOps(testDB, ADMIN_PERM);
}
},
{
name: 'Test userAdmin user',
test: function(conn) {
var testDB = conn.getDB('test');
assert.eq(1, testDB.auth('uadmin', AUTH_INFO.test.uadmin.pwd));
testOps(testDB, UADMIN_PERM);
}
},
{
name: 'Test cluster user',
test: function(conn) {
var adminDB = conn.getDB('admin');
assert.eq(1, adminDB.auth('cluster', AUTH_INFO.admin.cluster.pwd));
testOps(conn.getDB('test'), CLUSTER_PERM);
}
},
{
name: 'Test admin user with no role',
test: function(conn) {
var adminDB = conn.getDB('admin');
assert.eq(1, adminDB.auth('anone', AUTH_INFO.admin.anone.pwd));
testOps(adminDB, {});
testOps(conn.getDB('test'), {});
}
},
{
name: 'Test read only admin user',
test: function(conn) {
var adminDB = conn.getDB('admin');
assert.eq(1, adminDB.auth('aro', AUTH_INFO.admin.aro.pwd));
testOps(adminDB, READ_PERM);
testOps(conn.getDB('test'), {});
}
},
{
name: 'Test read/write admin user',
test: function(conn) {
var adminDB = conn.getDB('admin');
assert.eq(1, adminDB.auth('arw', AUTH_INFO.admin.arw.pwd));
testOps(adminDB, READ_WRITE_PERM);
testOps(conn.getDB('test'), {});
}
},
{
name: 'Test dbAdmin admin user',
test: function(conn) {
var adminDB = conn.getDB('admin');
assert.eq(1, adminDB.auth('aadmin', AUTH_INFO.admin.aadmin.pwd));
testOps(adminDB, ADMIN_PERM);
testOps(conn.getDB('test'), {});
}
},
{
name: 'Test userAdmin admin user',
test: function(conn) {
var adminDB = conn.getDB('admin');
assert.eq(1, adminDB.auth('auadmin', AUTH_INFO.admin.auadmin.pwd));
testOps(adminDB, UADMIN_PERM);
testOps(conn.getDB('test'), {});
}
},
{
name: 'Test read only any db user',
test: function(conn) {
var adminDB = conn.getDB('admin');
assert.eq(1, adminDB.auth('any_ro', AUTH_INFO.admin.any_ro.pwd));
testOps(adminDB, READ_PERM);
testOps(conn.getDB('test'), READ_PERM);
}
},
{
name: 'Test read/write any db user',
test: function(conn) {
var adminDB = conn.getDB('admin');
assert.eq(1, adminDB.auth('any_rw', AUTH_INFO.admin.any_rw.pwd));
testOps(adminDB, READ_WRITE_PERM);
testOps(conn.getDB('test'), READ_WRITE_PERM);
}
},
{
name: 'Test dbAdmin any db user',
test: function(conn) {
var adminDB = conn.getDB('admin');
assert.eq(1, adminDB.auth('any_admin', AUTH_INFO.admin.any_admin.pwd));
testOps(adminDB, ADMIN_PERM);
testOps(conn.getDB('test'), ADMIN_PERM);
}
},
{
name: 'Test userAdmin any db user',
test: function(conn) {
var adminDB = conn.getDB('admin');
assert.eq(1, adminDB.auth('any_uadmin', AUTH_INFO.admin.any_uadmin.pwd));
testOps(adminDB, UADMIN_PERM);
testOps(conn.getDB('test'), UADMIN_PERM);
}
},
{
name: 'Test change role',
test: function(conn) {
var testDB = conn.getDB('test');
assert.eq(1, testDB.auth('rw', AUTH_INFO.test.rw.pwd));
var newConn = new Mongo(conn.host);
assert.eq(1, newConn.getDB('admin').auth('any_uadmin', AUTH_INFO.admin.any_uadmin.pwd));
newConn.getDB('test').updateUser('rw', {roles: ['read']});
var origSpec = newConn.getDB("test").getUser("rw");
// role change should affect users already authenticated.
testOps(testDB, READ_PERM);
// role change should affect active connections.
testDB.runCommand({logout: 1});
assert.eq(1, testDB.auth('rw', AUTH_INFO.test.rw.pwd));
testOps(testDB, READ_PERM);
// role change should also affect new connections.
var newConn3 = new Mongo(conn.host);
var testDB3 = newConn3.getDB('test');
assert.eq(1, testDB3.auth('rw', AUTH_INFO.test.rw.pwd));
testOps(testDB3, READ_PERM);
newConn.getDB('test').updateUser('rw', {roles: origSpec.roles});
}
},
{
name: 'Test override user',
test: function(conn) {
var testDB = conn.getDB('test');
assert.eq(1, testDB.auth('rw', AUTH_INFO.test.rw.pwd));
testDB.logout();
assert.eq(1, testDB.auth('ro', AUTH_INFO.test.ro.pwd));
testOps(testDB, READ_PERM);
testDB.runCommand({logout: 1});
testOps(testDB, {});
}
}
];
/**
* Driver method for setting up the test environment, running them, cleanup
* after every test and keeping track of test failures.
*
* @param conn {Mongo} a connection to a mongod or mongos to test.
*/
var runTests = function(conn) {
var setup = function() {
var testDB = conn.getDB('test');
var adminDB = conn.getDB('admin');
adminDB.createUser(
{user: 'root', pwd: AUTH_INFO.admin.root.pwd, roles: AUTH_INFO.admin.root.roles});
adminDB.auth('root', AUTH_INFO.admin.root.pwd);
for (var x = 0; x < 10; x++) {
testDB.kill_cursor.insert({x: x});
adminDB.kill_cursor.insert({x: x});
}
for (var dbName in AUTH_INFO) {
var dbObj = AUTH_INFO[dbName];
for (var userName in dbObj) {
if (dbName == 'admin' && userName == 'root') {
// We already registered this user.
continue;
}
var info = dbObj[userName];
conn.getDB(dbName).createUser({user: userName, pwd: info.pwd, roles: info.roles});
}
}
adminDB.runCommand({logout: 1});
};
var teardown = function() {
var adminDB = conn.getDB('admin');
adminDB.auth('root', AUTH_INFO.admin.root.pwd);
conn.getDB('test').dropDatabase();
adminDB.dropDatabase();
};
var failures = [];
setup();
TESTS.forEach(function(testFunc) {
try {
jsTest.log(testFunc.name);
var newConn = new Mongo(conn.host);
newConn.host = conn.host;
testFunc.test(newConn);
} catch (x) {
failures.push(testFunc.name);
jsTestLog(tojson(x));
}
});
teardown();
if (failures.length > 0) {
var list = '';
failures.forEach(function(test) {
list += (test + '\n');
});
throw Error('Tests failed:\n' + list);
}
};
var conn = MongoRunner.runMongod({auth: ''});
runTests(conn);
MongoRunner.stopMongod(conn);
jsTest.log('Test sharding');
var st = new ShardingTest({shards: 1, keyFile: 'jstests/libs/key1'});
runTests(st.s);
st.stop();
print('SUCCESS! Completed basic_role_auth.js');