mirror of https://github.com/mongodb/mongo
275 lines
12 KiB
JavaScript
275 lines
12 KiB
JavaScript
/**
|
|
* This test checks that tenants can access their data before, during and after enabling
|
|
* multitenancySupport in a rolling fashion in a replica set.
|
|
*/
|
|
|
|
import {arrayEq} from "jstests/aggregation/extras/utils.js";
|
|
|
|
// In production, we will upgrade to start using multitenancySupport before enabling this feature
|
|
// flag, and this test is meant to exercise that upgrade behavior, so don't run if the feature flag
|
|
// is enabled.
|
|
const featureFlagRequireTenantId = TestData.setParameters.featureFlagRequireTenantID;
|
|
if (featureFlagRequireTenantId) {
|
|
quit();
|
|
}
|
|
|
|
/*
|
|
* Runs a find using a prefixed db, and asserts the find returns 'expectedDocsReturned'. Also
|
|
* checks that the "ns" returned in the cursor result is serialized as expected, including the
|
|
* tenantId.
|
|
*/
|
|
function runFindOnPrefixedDb(conn, prefixedDb, collName, expectedDocsReturned) {
|
|
conn._setSecurityToken(undefined);
|
|
const res =
|
|
assert.commandWorked(conn.getDB(prefixedDb).runCommand({find: collName, filter: {}}));
|
|
assert(arrayEq(expectedDocsReturned, res.cursor.firstBatch), tojson(res));
|
|
const prefixedNamespace = prefixedDb + "." + collName;
|
|
assert.eq(res.cursor.ns, prefixedNamespace);
|
|
}
|
|
|
|
/*
|
|
* Runs a findAndModify using a prefixed db.
|
|
*/
|
|
function runFindAndModOnPrefixedDb(conn, prefixedDb, collName, query, update, expectedDocReturned) {
|
|
conn._setSecurityToken(undefined);
|
|
const res = assert.commandWorked(
|
|
conn.getDB(prefixedDb).runCommand({findAndModify: collName, query: query, update: update}));
|
|
assert.eq(res.value, expectedDocReturned);
|
|
}
|
|
|
|
/*
|
|
* Runs a find using unsigned security token, and asserts the find returns 'expectedDocsReturned'.
|
|
* Also checks that the "ns" returned in the cursor result is serialized as expected, without the
|
|
* tenantId.
|
|
*/
|
|
function runFindUsingSecurityToken(conn, db, collName, token, expectedDocsReturned) {
|
|
conn._setSecurityToken(token);
|
|
const res = assert.commandWorked(conn.getDB(db).runCommand({find: collName, filter: {}}));
|
|
assert(arrayEq(expectedDocsReturned, res.cursor.firstBatch), tojson(res));
|
|
const namespace = db + "." + collName;
|
|
assert.eq(res.cursor.ns, namespace);
|
|
}
|
|
|
|
/*
|
|
* Runs a find using unsigned security token and prefixed db, and asserts the find returns
|
|
* 'expectedDocsReturned'. Also checks that the "ns" returned in the cursor result is serialized
|
|
* as expected, including the tenantId.
|
|
*/
|
|
function runFindUsingSecurityTokenAndPrefix(
|
|
conn, prefixedDb, collName, token, expectedDocsReturned) {
|
|
conn._setSecurityToken(token);
|
|
const res =
|
|
assert.commandWorked(conn.getDB(prefixedDb).runCommand({find: collName, filter: {}}));
|
|
assert(arrayEq(expectedDocsReturned, res.cursor.firstBatch), tojson(res));
|
|
const prefixedNamespace = prefixedDb + "." + collName;
|
|
assert.eq(res.cursor.ns, prefixedNamespace);
|
|
}
|
|
|
|
function assertTokenMustBeUsed(conn, tenant1DbPrefixed, tenant2DbPrefixed, kCollName) {
|
|
conn._setSecurityToken(undefined);
|
|
assert.commandFailedWithCode(
|
|
conn.getDB(tenant1DbPrefixed).runCommand({find: kCollName, filter: {}}), 8233503);
|
|
assert.commandFailedWithCode(
|
|
conn.getDB(tenant2DbPrefixed).runCommand({find: kCollName, filter: {}}), 8233503);
|
|
}
|
|
|
|
/*
|
|
* Runs a find for both tenants using a prefixed db, and asserts the find returns
|
|
* 'expectedDocsReturned'.
|
|
*/
|
|
function assertFindBothTenantsPrefixedDb(
|
|
conn, tenant1DbPrefixed, tenant2DbPrefixed, kCollName, tenant1Docs, tenant2Docs) {
|
|
runFindOnPrefixedDb(conn, tenant1DbPrefixed, kCollName, tenant1Docs);
|
|
runFindOnPrefixedDb(conn, tenant2DbPrefixed, kCollName, tenant2Docs);
|
|
}
|
|
|
|
/*
|
|
* Runs a find for both tenants using a prefixed db, and asserts the find returns
|
|
* 'expectedDocsReturned'.
|
|
*/
|
|
function assertFindBothTenantsUsingSecurityToken(
|
|
conn, db, collName, token1, token2, expectedDocsReturnedTenant1, expectedDocsReturnedTenant2) {
|
|
runFindUsingSecurityToken(conn, db, collName, token1, expectedDocsReturnedTenant1);
|
|
runFindUsingSecurityToken(conn, db, collName, token2, expectedDocsReturnedTenant2);
|
|
}
|
|
|
|
function awaitReplication(rst) {
|
|
// Reset the security token on connections as rst.awaitReplication calls commands against
|
|
// interal "local" db which is not tenant aware.
|
|
rst.nodes.forEach(node => node._setSecurityToken(undefined));
|
|
rst.awaitReplication();
|
|
}
|
|
|
|
const rst = new ReplSetTest({
|
|
nodes: 2,
|
|
nodeOptions: {
|
|
auth: '',
|
|
}
|
|
});
|
|
rst.startSet({keyFile: 'jstests/libs/key1'});
|
|
rst.initiate();
|
|
|
|
let primary = rst.getPrimary();
|
|
let secondary = rst.getSecondary();
|
|
|
|
const kTenant1 = ObjectId();
|
|
const kTenant2 = ObjectId();
|
|
const kDbName = "test";
|
|
const kCollName = "foo";
|
|
|
|
const kToken1 = _createTenantToken({tenant: kTenant1});
|
|
const kToken2 = _createTenantToken({tenant: kTenant2});
|
|
|
|
const kExpectPrefixToken1 = _createTenantToken({tenant: kTenant1, expectPrefix: true});
|
|
const kExpectPrefixToken2 = _createTenantToken({tenant: kTenant2, expectPrefix: true});
|
|
|
|
// Create a root user and login on both the primary and secondary.
|
|
const primaryAdminDb = primary.getDB('admin');
|
|
let secondaryAdminDb = secondary.getDB('admin');
|
|
assert.commandWorked(primaryAdminDb.runCommand({createUser: 'admin', pwd: 'pwd', roles: ['root']}));
|
|
assert(primaryAdminDb.auth('admin', 'pwd'));
|
|
awaitReplication(rst);
|
|
assert(secondaryAdminDb.auth('admin', 'pwd'));
|
|
|
|
// Insert data for two different tenants - multitenancySupport is not yet enabled, so we use a
|
|
// prefixed db. Then, check that we find the correct docs for both tenants, reading from both
|
|
// the primary and secondary.
|
|
const tenant1DbPrefixed = kTenant1 + "_" + kDbName;
|
|
const tenant1Docs = [{_id: 0, x: 1, y: 1}, {_id: 1, x: 2, y: 3}];
|
|
assert.commandWorked(
|
|
primary.getDB(tenant1DbPrefixed).runCommand({insert: kCollName, documents: tenant1Docs}));
|
|
|
|
const tenant2DbPrefixed = kTenant2 + "_" + kDbName;
|
|
const tenant2Docs = [{_id: 10, a: 10, b: 10}, {_id: 11, a: 20, b: 30}];
|
|
assert.commandWorked(
|
|
primary.getDB(tenant2DbPrefixed).runCommand({insert: kCollName, documents: tenant2Docs}));
|
|
|
|
assertFindBothTenantsPrefixedDb(
|
|
primary, tenant1DbPrefixed, tenant2DbPrefixed, kCollName, tenant1Docs, tenant2Docs);
|
|
awaitReplication(rst);
|
|
assertFindBothTenantsPrefixedDb(
|
|
secondary, tenant1DbPrefixed, tenant2DbPrefixed, kCollName, tenant1Docs, tenant2Docs);
|
|
|
|
// Now, restart the secondary and enable multitenancySupport. The primary still does not have
|
|
// multitenancySupport enabled.
|
|
jsTest.log("Restart the secondary and enable multitenancySupport.");
|
|
secondary =
|
|
rst.restart(secondary, {startClean: false, setParameter: {'multitenancySupport': true}});
|
|
rst.waitForState(secondary, ReplSetTest.State.SECONDARY);
|
|
secondary.setSecondaryOk();
|
|
assert(secondary.getDB("admin").auth('admin', 'pwd'));
|
|
|
|
// Make sure the primary is still primary after restarting secondary.
|
|
rst.stepUp(primary);
|
|
|
|
// Get another connecton of secondary as connection protocol cannot change once set.
|
|
let prefixedSecondary = new Mongo(secondary.host);
|
|
prefixedSecondary.setSecondaryOk();
|
|
assert(prefixedSecondary.getDB("admin").auth('admin', 'pwd'));
|
|
|
|
// Check that we can still find the docs when using a prefixed db on both the primary and
|
|
// secondary.
|
|
assertFindBothTenantsPrefixedDb(
|
|
primary, tenant1DbPrefixed, tenant2DbPrefixed, kCollName, tenant1Docs, tenant2Docs);
|
|
|
|
// Once multitenancy is set we must use a token.
|
|
assertTokenMustBeUsed(secondary, tenant1DbPrefixed, tenant2DbPrefixed, kCollName);
|
|
|
|
// Now check that we find the docs for both tenants when reading from the secondary using
|
|
// a security token. The primary does not yet support a security token
|
|
// since it does not have multitenancySupport enabled.
|
|
assertFindBothTenantsUsingSecurityToken(
|
|
secondary, kDbName, kCollName, kToken1, kToken2, tenant1Docs, tenant2Docs);
|
|
|
|
// Also assert both tenants find the new doc on the secondary using token and a prefixed db.
|
|
runFindUsingSecurityTokenAndPrefix(
|
|
prefixedSecondary, tenant1DbPrefixed, kCollName, kExpectPrefixToken1, tenant1Docs);
|
|
runFindUsingSecurityTokenAndPrefix(
|
|
prefixedSecondary, tenant2DbPrefixed, kCollName, kExpectPrefixToken2, tenant2Docs);
|
|
|
|
// Now insert a new doc for both tenants using the prefixed db, and assert that we can find it
|
|
// on both the primary and secondary.
|
|
const newTenant1Doc = [{_id: 2, x: 3}];
|
|
const newTenant2Doc = [{_id: 12, a: 30}];
|
|
assert.commandWorked(
|
|
primary.getDB(tenant1DbPrefixed).runCommand({insert: kCollName, documents: newTenant1Doc}));
|
|
assert.commandWorked(
|
|
primary.getDB(tenant2DbPrefixed).runCommand({insert: kCollName, documents: newTenant2Doc}));
|
|
|
|
const allTenant1Docs = tenant1Docs.concat(newTenant1Doc);
|
|
const allTenant2Docs = tenant2Docs.concat(newTenant2Doc);
|
|
|
|
// Assert both tenants find the new doc on both the primary.
|
|
assertFindBothTenantsPrefixedDb(
|
|
primary, tenant1DbPrefixed, tenant2DbPrefixed, kCollName, allTenant1Docs, allTenant2Docs);
|
|
// The token must be used on the secondary.
|
|
assertTokenMustBeUsed(secondary, tenant1DbPrefixed, tenant2DbPrefixed, kCollName);
|
|
awaitReplication(rst);
|
|
// Assert both tenants find the new doc on the secondary using token.
|
|
assertFindBothTenantsUsingSecurityToken(
|
|
secondary, kDbName, kCollName, kToken1, kToken2, allTenant1Docs, allTenant2Docs);
|
|
|
|
// Assert both tenants find the new doc on the secondary using token and a prefixed db.
|
|
runFindUsingSecurityTokenAndPrefix(
|
|
prefixedSecondary, tenant1DbPrefixed, kCollName, kExpectPrefixToken1, allTenant1Docs);
|
|
runFindUsingSecurityTokenAndPrefix(
|
|
prefixedSecondary, tenant2DbPrefixed, kCollName, kExpectPrefixToken2, allTenant2Docs);
|
|
|
|
// Now run findAndModify on one doc using a prefixed db and check that we can read from the
|
|
// secondary using just token and a prefix.
|
|
runFindAndModOnPrefixedDb(
|
|
primary, tenant1DbPrefixed, kCollName, newTenant1Doc[0], {$set: {x: 4}}, newTenant1Doc[0]);
|
|
runFindAndModOnPrefixedDb(
|
|
primary, tenant2DbPrefixed, kCollName, newTenant2Doc[0], {$set: {a: 40}}, newTenant2Doc[0]);
|
|
|
|
const modifiedTenant1Docs = tenant1Docs.concat([{_id: 2, x: 4}]);
|
|
const modifiedTenant2Docs = tenant2Docs.concat([{_id: 12, a: 40}]);
|
|
awaitReplication(rst);
|
|
assertFindBothTenantsUsingSecurityToken(
|
|
secondary, kDbName, kCollName, kToken1, kToken2, modifiedTenant1Docs, modifiedTenant2Docs);
|
|
|
|
runFindUsingSecurityTokenAndPrefix(
|
|
prefixedSecondary, tenant1DbPrefixed, kCollName, kExpectPrefixToken1, modifiedTenant1Docs);
|
|
runFindUsingSecurityTokenAndPrefix(
|
|
prefixedSecondary, tenant2DbPrefixed, kCollName, kExpectPrefixToken2, modifiedTenant2Docs);
|
|
|
|
jsTest.log("Restart the primary and enable multitenancySupport.");
|
|
rst.restart(primary, {startClean: false, setParameter: {'multitenancySupport': true}});
|
|
|
|
primary = rst.waitForPrimary();
|
|
assert(primary.getDB("admin").auth('admin', 'pwd'));
|
|
secondary = rst.getSecondary();
|
|
assert(secondary.getDB("admin").auth('admin', 'pwd'));
|
|
secondary.setSecondaryOk();
|
|
|
|
// Get another connection of primary and secondary as connection protocol cannot change once set.
|
|
let prefixedPrimary = new Mongo(primary.host);
|
|
assert(prefixedPrimary.getDB("admin").auth('admin', 'pwd'));
|
|
prefixedSecondary = new Mongo(secondary.host);
|
|
assert(prefixedSecondary.getDB("admin").auth('admin', 'pwd'));
|
|
prefixedSecondary.setSecondaryOk();
|
|
|
|
// The token must now be used on both primary and secocndary.
|
|
assertTokenMustBeUsed(primary, tenant1DbPrefixed, tenant2DbPrefixed, kCollName);
|
|
assertTokenMustBeUsed(secondary, tenant1DbPrefixed, tenant2DbPrefixed, kCollName);
|
|
|
|
// Now check that we find the docs for both tenants when reading from both the primary and
|
|
// secondary using token.
|
|
assertFindBothTenantsUsingSecurityToken(
|
|
primary, kDbName, kCollName, kToken1, kToken2, modifiedTenant1Docs, modifiedTenant2Docs);
|
|
assertFindBothTenantsUsingSecurityToken(
|
|
secondary, kDbName, kCollName, kToken1, kToken2, modifiedTenant1Docs, modifiedTenant2Docs);
|
|
|
|
// Also check that both tenants find the new doc on the primary and secondary using token and a
|
|
// prefixed db.
|
|
runFindUsingSecurityTokenAndPrefix(
|
|
prefixedPrimary, tenant1DbPrefixed, kCollName, kExpectPrefixToken1, modifiedTenant1Docs);
|
|
runFindUsingSecurityTokenAndPrefix(
|
|
prefixedSecondary, tenant2DbPrefixed, kCollName, kExpectPrefixToken2, modifiedTenant2Docs);
|
|
|
|
primary._setSecurityToken(undefined);
|
|
secondary._setSecurityToken(undefined);
|
|
prefixedPrimary._setSecurityToken(undefined);
|
|
prefixedSecondary._setSecurityToken(undefined);
|
|
rst.stopSet();
|