mongo/jstests/auth/change_stream_pre_image_coll_role_auth.js
2023-04-26 19:19:19 +00:00

203 lines
8.0 KiB
JavaScript

/**
* Tests that when RBAC (Role-Based Access Control) is enabled on the cluster, then the user of the
* root role can issue 'find' and 'remove' commands on the 'config.system.preimages' collections.
* Verify that if pre-image for the collection has been deleted by the 'root' user.
* @tags: [
* requires_fcv_60,
* uses_change_streams,
* assumes_read_preference_unchanged,
* requires_replication,
* ]
*/
(function() {
'use strict';
const password = "password";
const keyFile = "jstests/libs/key1";
// A dictionary of users against which authorization of pre-image collection will be verified.
// The format of dictionary is:
// <key>: user-name,
// <value>:
// 'db': authentication db for the user
// 'roles': roles assigned to the user
const users = {
admin: {db: "admin", roles: [{role: "userAdminAnyDatabase", db: "admin"}]},
clusterAdmin: {db: "admin", roles: [{"role": "clusterAdmin", "db": "admin"}]},
test: {
db: "test",
roles: [{"role": "readWrite", "db": "test"}, {"role": "readWrite", "db": "config"}]
},
root: {db: "admin", roles: ["root"]}
};
// Helper to authenticate and login the user on the provided connection.
function login(conn, userName) {
const user = users[userName];
const db = conn.getDB(user.db);
assert(db.auth(userName, password));
return db;
}
// Helper to create users from the 'users' dictionary on the provided connection.
function createUsers(conn) {
function createUser(userName) {
// A user-creation will happen by 'admin' user. If 'userName' is not 'admin', then login
// first as an 'admin'.
const adminDb = userName !== "admin" ? login(conn, "admin") : null;
// Get the details of user to create and issue create command.
const user = users[userName];
conn.getDB(user.db).createUser({user: userName, pwd: password, roles: user.roles});
// Logout if the current authenticated user is 'admin'
if (adminDb !== null) {
adminDb.logout();
}
// Verify that the user has been created by logging-in and then logging out.
const db = login(conn, userName);
db.logout();
}
// Create the list of users on the specified connection.
createUser("admin");
createUser("clusterAdmin");
createUser("test");
createUser("root");
}
// Helper to verify if the logged-in user is authorized to invoke 'actionFunc'.
// The parameter 'isAuthorized' determines if the authorization should pass or fail.
function assertActionAuthorized(actionFunc, isAuthorized) {
try {
actionFunc();
// Verify if the authorization is expected to pass.
assert.eq(isAuthorized, true, "authorization passed unexpectedly");
} catch (ex) {
// Verify if the authorization is expected to fail.
assert.eq(isAuthorized, false, "authorization failed unexpectedly, details: " + ex);
// Verify that the authorization failed with the expected error code.
const unauthorized = 13;
assert.eq(ex.code,
unauthorized,
"expected operation should fail with code: " + unauthorized +
", found: " + ex.code + ", details: " + ex);
}
}
// Helper to verify if the logged-in user is authorized to issue 'find' command.
// The parameter 'authDb' is the authentication db for the user, and 'numDocs' determines the
// expected number of documents to be retrieved.
function findPreImage(authDb, numDocs = 1) {
const db = authDb.getSiblingDB("config");
const result = db.getCollection("system.preimages").find().toArray();
assert.eq(result.length, numDocs, result);
}
// Helper to verify if the logged-in user is authorized to issue 'remove' command.
// The parameter 'authDb' is the authentication db for the user.
function removePreImage(authDb) {
const db = authDb.getSiblingDB("config");
assert.commandWorked(db.getCollection("system.preimages").remove({"preImage._id": 0}));
findPreImage(authDb, 0 /* numDocs */);
}
// Verify that the expected number of change stream events are observed using the supplied resume
// token.
function verifyChangeStreamEvents(authDb, resumeToken) {
const csCursor = authDb.getSiblingDB("test").watch([], {
resumeAfter: resumeToken,
fullDocument: "whenAvailable",
fullDocumentBeforeChange: "whenAvailable"
});
assert.soon(() => csCursor.hasNext());
const event1 = csCursor.next();
assert.eq(event1.documentKey._id, 0, event1);
assert.eq(event1.fullDocument, {_id: 0, annotation: "inserted"}, event1);
assert.soon(() => csCursor.hasNext());
const event2 = csCursor.next();
assert.eq(event2.documentKey._id, 0, event2);
assert.eq(event2.fullDocument, {_id: 0, annotation: "updated"}, event2);
assert.eq(event2.fullDocumentBeforeChange, {_id: 0, annotation: "inserted"}, event2);
}
// Start a replica-set test with one-node and authentication enabled. Connect to the primary node
// and create users.
const replSetTest =
new ReplSetTest({name: "shard", nodes: 1, useHostName: true, waitForKeys: false});
replSetTest.startSet({keyFile: keyFile});
replSetTest.initiate();
const primary = replSetTest.getPrimary();
createUsers(primary);
// Connect to the 'test' user.
let testPrimary = login(primary, "test");
// Get the resume token of the change streams.
const csResumeToken = testPrimary.watch([]).getResumeToken();
// Create a collection, insert a document and then update to create pre-image.
testPrimary.createCollection("testColl", {changeStreamPreAndPostImages: {enabled: true}});
assert.commandWorked(testPrimary.testColl.insert({_id: 0, annotation: "inserted"}));
assert.commandWorked(testPrimary.testColl.update({_id: 0}, {$set: {annotation: "updated"}}));
// Verify that with RBAC enabled, the change streams observes the events with pre-and-post images.
// Verify that the 'test' user is not authorized to issue 'find' command on the pre-image
// collection.
assertActionAuthorized(verifyChangeStreamEvents.bind(null, testPrimary, csResumeToken), true);
assertActionAuthorized(findPreImage.bind(null, testPrimary), false);
testPrimary.logout();
// User 'clusterAdmin' should not be authorized to find the pre-images and open change-streams with
// pre-and-post images.
let clusterAdminPrimary = login(primary, "clusterAdmin");
assertActionAuthorized(verifyChangeStreamEvents.bind(null, clusterAdminPrimary, csResumeToken),
false);
assertActionAuthorized(findPreImage.bind(null, clusterAdminPrimary), false);
clusterAdminPrimary.logout();
// User 'admin' should not be authorized to find the pre-images and open change-streams with
// pre-and-post images.
let adminPrimary = login(primary, "admin");
assertActionAuthorized(verifyChangeStreamEvents.bind(null, clusterAdminPrimary, csResumeToken),
false);
assertActionAuthorized(findPreImage.bind(null, adminPrimary), false);
adminPrimary.logout();
// User 'root' should be authorized to find the pre-images and open change-streams with pre-and-post
// images.
let rootPrimary = login(primary, "root");
assertActionAuthorized(verifyChangeStreamEvents.bind(null, clusterAdminPrimary, csResumeToken),
true);
assertActionAuthorized(findPreImage.bind(null, rootPrimary), true);
rootPrimary.logout();
// User 'test' should not be authorized to remove pre-images.
testPrimary = login(primary, "test");
assertActionAuthorized(removePreImage.bind(null, testPrimary), false);
testPrimary.logout();
// User 'clusterAdmin' should not be authorized to remove pre-images.
clusterAdminPrimary = login(primary, "clusterAdmin");
assertActionAuthorized(removePreImage.bind(null, clusterAdminPrimary), false);
clusterAdminPrimary.logout();
// User 'admin' should not be authorized to remove pre-images.
adminPrimary = login(primary, "admin");
assertActionAuthorized(removePreImage.bind(null, adminPrimary), false);
adminPrimary.logout();
// User 'root' should be authorized to remove pre-images.
rootPrimary = login(primary, "root");
assertActionAuthorized(removePreImage.bind(null, rootPrimary), true);
rootPrimary.logout();
replSetTest.stopSet();
})();