/** * 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();