mongo/jstests/auth/authentication_restrictions.js

292 lines
11 KiB
JavaScript

/**
* This test checks that authentication restrictions can be set and respected.
* @tags: [requires_sharding, requires_replication]
*/
import {get_ipaddr, getIpv6addr} from "jstests/libs/host_ipaddr.js";
import {ReplSetTest} from "jstests/libs/replsettest.js";
import {ShardingTest} from "jstests/libs/shardingtest.js";
// Multiple users cannot be authenticated on one connection within a session.
TestData.disableImplicitSessions = true;
function testConnection(conn, eventuallyConsistentConn, sleepUntilUserDataPropagated, sleepUntilUserDataRefreshed) {
// Create a session which observes an eventually consistent view of user data
const eventualDb = eventuallyConsistentConn.getDB("admin");
// Create a session for modifying user data during the life of the test
const adminSession = new Mongo("localhost:" + conn.port);
const admin = adminSession.getDB("admin");
assert.commandWorked(admin.runCommand({createUser: "admin", pwd: "admin", roles: [{role: "root", db: "admin"}]}));
assert(admin.auth("admin", "admin"));
admin.logout();
// Create a strongly consistent session for consuming user data
const db = conn.getDB("admin");
let ipv6addr = getIpv6addr(true, false);
// Create a strongly consistent session for consuming user data, with a non-localhost
// source IP.
const externalMongo = new Mongo(get_ipaddr() + ":" + conn.port);
const externalDb = externalMongo.getDB("admin");
// Create a connection which remains authenticated as 'admin'
// so that we can create/mutate users/roles while we do
// multiple authentications.
const adminMongo = new Mongo(conn.host);
const adminDB = adminMongo.getDB("admin");
assert(adminDB.auth("admin", "admin"));
assert.commandWorked(
adminDB.runCommand({
createUser: "user2",
pwd: "user",
roles: [],
authenticationRestrictions: [{clientSource: ["127.0.0.1"]}],
}),
);
assert.commandWorked(adminDB.runCommand({createUser: "user3", pwd: "user", roles: []}));
assert.commandWorked(
adminDB.runCommand({updateUser: "user3", authenticationRestrictions: [{serverAddress: ["127.0.0.1"]}]}),
);
print("=== User creation tests");
print(
"When a client creates users with empty authenticationRestrictions, the operation succeeds, though it has no effect",
);
assert.commandWorked(
adminDB.runCommand({createUser: "user4", pwd: "user", roles: [], authenticationRestrictions: []}),
);
assert(!Object.keys(adminDB.system.users.findOne({user: "user4"})).includes("authenticationRestrictions"));
print(
"When a client updates a user's authenticationRestrictions to be empty, the operation succeeds, and removes the authenticationRestrictions field",
);
assert.commandWorked(adminDB.runCommand({createUser: "user5", pwd: "user", roles: []}));
assert.commandWorked(adminDB.runCommand({updateUser: "user5", authenticationRestrictions: []}));
assert(!Object.keys(adminDB.system.users.findOne({user: "user5"})).includes("authenticationRestrictions"));
assert.commandWorked(
adminDB.runCommand({updateUser: "user5", authenticationRestrictions: [{clientSource: ["127.0.0.1"]}]}),
);
assert(Object.keys(adminDB.system.users.findOne({user: "user5"})).includes("authenticationRestrictions"));
assert.commandWorked(adminDB.runCommand({updateUser: "user5", authenticationRestrictions: []}));
assert(!Object.keys(adminDB.system.users.findOne({user: "user5"})).includes("authenticationRestrictions"));
print("When a client updates a user's authenticationRestrictions to be null or undefined, the operation fails");
assert.commandWorked(
adminDB.runCommand({updateUser: "user5", authenticationRestrictions: [{clientSource: ["127.0.0.1"]}]}),
);
assert(Object.keys(adminDB.system.users.findOne({user: "user5"})).includes("authenticationRestrictions"));
assert.commandFailed(adminDB.runCommand({updateUser: "user5", authenticationRestrictions: null}));
assert(Object.keys(adminDB.system.users.findOne({user: "user5"})).includes("authenticationRestrictions"));
assert.commandFailed(adminDB.runCommand({updateUser: "user5", authenticationRestrictions: undefined}));
assert(Object.keys(adminDB.system.users.findOne({user: "user5"})).includes("authenticationRestrictions"));
print("When a client creates users, it may use clientSource and serverAddress authenticationRestrictions");
assert.commandWorked(
adminDB.runCommand({
createUser: "user6",
pwd: "user",
roles: [],
authenticationRestrictions: [{clientSource: ["127.0.0.1"]}],
}),
);
assert.commandWorked(
adminDB.runCommand({
createUser: "user7",
pwd: "user",
roles: [],
authenticationRestrictions: [{serverAddress: ["127.0.0.1"]}],
}),
);
assert.commandWorked(
adminDB.runCommand({
createUser: "user8",
pwd: "user",
roles: [],
authenticationRestrictions: [{clientSource: ["127.0.0.1"], serverAddress: ["127.0.0.1"]}],
}),
);
assert.commandWorked(
adminDB.runCommand({
createUser: "user9",
pwd: "user",
roles: [],
authenticationRestrictions: [{clientSource: ["127.0.0.1"]}, {serverAddress: ["127.0.0.1"]}],
}),
);
assert.commandFailed(
adminDB.runCommand({
createUser: "user10",
pwd: "user",
roles: [],
authenticationRestrictions: [{invalidRestriction: ["127.0.0.1"]}],
}),
);
if (ipv6addr != "" && ipv6addr != null) {
assert.commandWorked(
adminDB.runCommand({
createUser: "ipv6user1",
pwd: "user",
roles: [],
authenticationRestrictions: [{clientSource: [ipv6addr]}],
}),
);
print(
'When a client on a link-local IPv6 address authenticates to a user with {clientSource: "' +
ipv6addr +
'"}, it will succeed',
);
clearRawMongoProgramOutput();
const ipv6Command = [
"mongo",
"--ipv6",
"--host",
"[" + ipv6addr + "]",
"--port",
conn.port,
"-u",
"ipv6user1",
"-p",
"user",
"--authenticationDatabase",
"admin",
];
let clientPID = _startMongoProgram({args: ipv6Command});
assert.soon(() => {
const output = rawMongoProgramOutput(".*");
if (output.includes("Successfully authenticated")) {
stopMongoProgramByPid(clientPID);
return true;
}
if (output.includes("Error:")) {
print("ipv6 auth attempt failed with output: " + output);
return false;
}
return false;
});
}
print("=== Localhost access tests");
print('When a client on the loopback authenticates to a user with {clientSource: "127.0.0.1"}, it will succeed');
assert(db.auth("user6", "user"));
db.logout();
print('When a client on the loopback authenticates to a user with {serverAddress: "127.0.0.1"}, it will succeed');
assert(db.auth("user7", "user"));
db.logout();
print(
'When a client on the loopback authenticates to a user with {clientSource: "127.0.0.1", serverAddress: "127.0.0.1"}, it will succeed',
);
assert(db.auth("user8", "user"));
db.logout();
print("=== Remote access tests");
print(
'When a client on the external interface authenticates to a user with {clientSource: "127.0.0.1"}, it will fail',
);
assert(!externalDb.auth("user6", "user"));
print(
'When a client on the external interface authenticates to a user with {serverAddress: "127.0.0.1"}, it will fail',
);
assert(!externalDb.auth("user7", "user"));
print(
'When a client on the external interface authenticates to a user with {clientSource: "127.0.0.1", serverAddress: "127.0.0.1"}, it will fail',
);
assert(!externalDb.auth("user8", "user"));
print("=== Invalidation tests");
print("When a client removes all authenticationRestrictions from a user, authentication will succeed");
assert.commandWorked(
adminDB.runCommand({
createUser: "user11",
pwd: "user",
roles: [],
authenticationRestrictions: [{clientSource: ["127.0.0.1"], serverAddress: ["127.0.0.1"]}],
}),
);
assert(!externalDb.auth("user11", "user"));
assert.commandWorked(adminDB.runCommand({updateUser: "user11", authenticationRestrictions: []}));
assert(externalDb.auth("user11", "user"));
externalDb.logout();
print("When a client sets authenticationRestrictions on a user, authorization privileges are revoked");
assert.commandWorked(
adminDB.runCommand({createUser: "user12", pwd: "user", roles: [{role: "readWrite", db: "test"}]}),
);
assert(db.auth("user12", "user"));
assert.commandWorked(db.getSiblingDB("test").runCommand({find: "foo", batchSize: 0}));
db.logout();
sleepUntilUserDataPropagated();
assert(eventualDb.auth("user12", "user"));
assert.commandWorked(eventualDb.getSiblingDB("test").runCommand({find: "foo", batchSize: 0}));
assert.commandWorked(
adminDB.runCommand({updateUser: "user12", authenticationRestrictions: [{clientSource: ["192.0.2.0"]}]}),
);
assert(!db.auth("user12", "user"));
sleepUntilUserDataRefreshed();
assert.commandFailed(eventualDb.getSiblingDB("test").runCommand({find: "foo", batchSize: 0}));
}
print("Testing standalone");
const conn = MongoRunner.runMongod({bind_ip_all: "", ipv6: "", auth: ""});
testConnection(
conn,
conn,
function () {},
function () {},
);
MongoRunner.stopMongod(conn);
const keyfile = "jstests/libs/key1";
print("Testing replicaset");
const rst = new ReplSetTest({
name: "testset",
nodes: 2,
nodeOptions: {bind_ip_all: "", ipv6: "", auth: ""},
keyFile: keyfile,
});
const nodes = rst.startSet();
rst.initiate();
rst.awaitSecondaryNodes();
const awaitReplication = function () {
authutil.asCluster(nodes, "jstests/libs/key1", function () {
rst.awaitReplication();
});
};
testConnection(rst.getPrimary(), rst.getSecondary(), awaitReplication, awaitReplication);
rst.stopSet();
print("Testing sharded cluster");
const st = new ShardingTest({
mongos: 2,
config: 3,
shard: 1,
keyFile: keyfile,
other: {
mongosOptions: {bind_ip_all: "", auth: null},
configOptions: {auth: null},
rsOptions: {auth: null},
},
});
testConnection(
st.s0,
st.s1,
function () {},
function () {
sleep(40 * 1000); // Wait for mongos user cache invalidation
},
);
st.stop();