mongo/jstests/auth/resource_pattern_matching.js

281 lines
8.3 KiB
JavaScript

/*
* Tests that resource pattern matching rules work as expected.
* @tags: [requires_replication, requires_sharding]
*/
import {ReplSetTest} from "jstests/libs/replsettest.js";
import {ShardingTest} from "jstests/libs/shardingtest.js";
// This test logs in users on the admin database, but doesn't log them out, which can fail with
// implicit sessions and ReplSetTest when the fixture attempts to verify data hashes at shutdown by
// authenticating as the __system user.
TestData.disableImplicitSessions = true;
function setup_users(granter) {
const admin = granter.getSiblingDB("admin");
assert.commandWorked(
admin.runCommand({
createUser: "admin",
pwd: "admin",
roles: ["userAdminAnyDatabase", "dbAdminAnyDatabase", "clusterAdmin", "readWriteAnyDatabase"],
}),
);
assert(admin.auth("admin", "admin"));
printjson(admin.runCommand({createRole: "test_role", privileges: [], roles: []}));
printjson(admin.runCommand({createUser: "test_user", pwd: "password", roles: ["test_role"]}));
admin.logout();
}
function setup_dbs_and_cols(db) {
const admin = db.getSiblingDB("admin");
const test_db_a = db.getSiblingDB("a");
const test_db_b = db.getSiblingDB("b");
assert(admin.auth("admin", "admin"));
assert.commandWorked(test_db_a.dropDatabase({w: "majority"}));
assert.commandWorked(test_db_b.dropDatabase({w: "majority"}));
assert.commandWorked(test_db_a.createCollection("a", {writeConcern: {w: "majority"}}));
assert.commandWorked(test_db_a.createCollection("b", {writeConcern: {w: "majority"}}));
assert.commandWorked(test_db_b.createCollection("a", {writeConcern: {w: "majority"}}));
assert.commandWorked(test_db_b.createCollection("b", {writeConcern: {w: "majority"}}));
admin.logout();
}
function grant_privileges(granter, privileges) {
const admin = granter.getSiblingDB("admin");
assert(admin.auth("admin", "admin"));
const result = admin.runCommand({
grantPrivilegesToRole: "test_role",
privileges: privileges,
writeConcern: {w: "majority"},
});
admin.logout();
return result;
}
function revoke_privileges(granter, privileges) {
const admin = granter.getSiblingDB("admin");
assert(admin.auth("admin", "admin"));
const result = admin.runCommand({
revokePrivilegesFromRole: "test_role",
privileges: privileges,
writeConcern: {w: "majority"},
});
admin.logout();
return result;
}
function invalidateUserCache(verifier) {
const admin = verifier.getSiblingDB("admin");
assert(admin.auth("admin", "admin"));
assert.commandWorked(admin.runCommand("invalidateUserCache"));
admin.logout();
}
function run_test(name, granter, verifier, privileges, collections, rst) {
print("\n=== testing " + name + "() ===\n");
grant_privileges(granter, privileges);
invalidateUserCache(verifier);
if (rst) {
rst.awaitReplication();
}
const verifierDB = verifier.getSiblingDB("admin");
assert(verifierDB.auth("test_user", "password"));
for (let key in collections) {
const parts = key.split(".");
const testdb = verifier.getSiblingDB(parts[0]);
const col = testdb.getCollection(parts[1]);
const cb = collections[key];
cb(testdb, col);
}
verifierDB.logout();
revoke_privileges(granter, privileges);
}
function run_test_bad_resource(name, granter, resource) {
print("\n=== testing resource fail " + name + "() ===\n");
assert.commandFailed(grant_privileges(granter, [{resource: resource, actions: ["find"]}]));
}
function should_insert(testdb, testcol) {
assert.doesNotThrow(function () {
testcol.insert({a: "b"});
});
}
function should_find(testdb, testcol) {
assert.doesNotThrow(function () {
testcol.findOne();
});
}
function should_fail_find(testdb, testcol) {
assert.throws(function () {
testcol.findOne();
});
}
function run_tests(granter, verifier, rst) {
setup_users(granter);
setup_dbs_and_cols(granter);
run_test(
"specific",
granter,
verifier,
[{resource: {db: "a", collection: "a"}, actions: ["find"]}],
{
"a.a": should_find,
"a.b": should_fail_find,
"b.a": should_fail_find,
"b.b": should_fail_find,
},
rst,
);
run_test(
"glob_collection",
granter,
verifier,
[{resource: {db: "a", collection: ""}, actions: ["find"]}],
{"a.a": should_find, "a.b": should_find, "b.a": should_fail_find, "b.b": should_fail_find},
rst,
);
run_test(
"glob_database",
granter,
verifier,
[{resource: {db: "", collection: "a"}, actions: ["find"]}],
{"a.a": should_find, "a.b": should_fail_find, "b.a": should_find, "b.b": should_fail_find},
rst,
);
run_test(
"glob_all",
granter,
verifier,
[{resource: {db: "", collection: ""}, actions: ["find"]}],
{"a.a": should_find, "a.b": should_find, "b.a": should_find, "b.b": should_find},
rst,
);
run_test(
"any_resource",
granter,
verifier,
[{resource: {anyResource: true}, actions: ["find"]}],
{
"a.a": should_find,
"a.b": should_find,
"b.a": should_find,
"b.b": should_find,
"c.a": should_find,
},
rst,
);
run_test(
"no_global_access",
granter,
verifier,
[{resource: {db: "$", collection: "cmd"}, actions: ["find"]}],
{
"a.a": function (testdb, testcol) {
let r = testdb.stats();
if (r["ok"]) throw "db.$.cmd shouldn't give a.stats()";
},
},
rst,
);
run_test_bad_resource("empty_resource", granter, {});
run_test_bad_resource("users_collection_any_db", granter, {collection: "users"});
run_test_bad_resource("bad_key", granter, {myResource: "users"});
run_test_bad_resource("extra_key", granter, {db: "test", collection: "users", cluster: true});
run_test_bad_resource("bad_value_type", granter, {cluster: "false"});
run_test_bad_resource("bad_collection", granter, {db: "test", collection: "$$$$"});
run_test(
"mixed_find_write",
granter,
verifier,
[
{resource: {db: "a", collection: "a"}, actions: ["find"]},
{resource: {db: "", collection: ""}, actions: ["insert"]},
],
{
"a.a": function (testdb, testcol) {
should_insert(testdb, testcol);
should_find(testdb, testcol);
},
"a.b": function (testdb, testcol) {
should_insert(testdb, testcol);
should_fail_find(testdb, testcol);
},
"b.a": function (testdb, testcol) {
should_insert(testdb, testcol);
should_fail_find(testdb, testcol);
},
"b.b": function (testdb, testcol) {
should_insert(testdb, testcol);
should_fail_find(testdb, testcol);
},
},
rst,
);
}
const keyfile = "jstests/libs/key1";
{
print("--- standalone node test ---");
const conn = MongoRunner.runMongod({auth: null, keyFile: keyfile});
run_tests(conn.getDB("test"), conn.getDB("test"));
MongoRunner.stopMongod(conn);
print("--- done standalone node test ---");
}
{
print("--- replica set test ---");
const rst = new ReplSetTest({name: "testset", nodes: 2, nodeOptions: {"auth": null}, keyFile: keyfile});
rst.startSet();
rst.initiate();
const primary = rst.getPrimary().getDB("admin");
rst.awaitSecondaryNodes();
const secondary = rst.getSecondaries()[0].getDB("admin");
run_tests(primary, secondary, rst);
rst.stopSet();
print("--- done with the rs tests ---");
}
{
print("--- sharding test ---");
const st = new ShardingTest({
mongos: 2,
shard: 1,
keyFile: keyfile,
other: {
mongosOptions: {"auth": null},
configOptions: {"auth": null},
rsOptions: {"auth": null},
},
});
run_tests(st.s0.getDB("admin"), st.s1.getDB("admin"));
st.stop();
print("--- sharding test done ---");
}