// Test restricting role authorization via X509 extensions. import {requireSSLProvider} from "jstests/ssl/libs/ssl_helpers.js"; requireSSLProvider("openssl", function () { const SERVER_CERT = "jstests/libs/server.pem"; const COMBINED_CA_CERT = "jstests/ssl/x509/root-and-trusted-ca.pem"; const CA_HASH = cat("jstests/libs/ca.pem.digest.sha256"); const TRUSTED_CA_HASH = cat("jstests/libs/trusted-ca.pem.digest.sha256"); // Common suffix, keep the lines short. const RDN_SUFFIX = ",O=MongoDB,L=New York City,ST=New York,C=US"; const USERS = []; const CLIENT = { cert: "jstests/libs/client.pem", roles: [], }; USERS.push("CN=client,OU=KernelUser"); const CLIENT_ROLES = { cert: "jstests/libs/client_roles.pem", roles: [ {role: "backup", db: "admin"}, {role: "readAnyDatabase", db: "admin"}, ], }; USERS.push("CN=Kernel Client Peer Role,OU=Kernel Users"); const TRUSTED_CLIENT_TESTDB_ROLES = { cert: "jstests/ssl/x509/trusted-client-testdb-roles.pem", roles: [ {role: "role1", db: "testDB"}, {role: "role2", db: "testDB"}, ], }; USERS.push("CN=Trusted Kernel Test Client With Roles,OU=Kernel Users"); function test(tlsCATrusts, success, failure) { const options = { auth: "", tlsMode: "requireTLS", tlsCertificateKeyFile: SERVER_CERT, tlsCAFile: COMBINED_CA_CERT, }; if (tlsCATrusts !== null) { options.setParameter = { tlsCATrusts: tojson(tlsCATrusts), }; } const mongod = MongoRunner.runMongod(options); const admin = mongod.getDB("admin"); admin.createUser({user: "admin", pwd: "pwd", roles: ["root"]}); admin.auth({user: "admin", pwd: "pwd"}); const external = mongod.getDB("$external"); USERS.forEach((u) => external.createUser({user: u + RDN_SUFFIX, roles: []})); const testDB = mongod.getDB("test"); testDB.createRole({role: "role1", privileges: [], roles: []}); testDB.createRole({role: "role2", privileges: [], roles: []}); // Sorting JS arrays of objects with arbitrary order is... complex. const serverTrusts = assert.commandWorked(admin.runCommand({getParameter: 1, tlsCATrusts: 1})).tlsCATrusts; function sortAndNormalizeRoles(roles) { return roles .map((r) => r.role + "." + r.db) .sort() .join("/"); } function sortAndNormalizeTrusts(trusts) { if (trusts === null) { return "(unconfigured)"; } return trusts.map((t) => t.sha256 + "/" + sortAndNormalizeRoles(t.roles)).sort(); } assert.eq(sortAndNormalizeTrusts(tlsCATrusts), sortAndNormalizeTrusts(serverTrusts)); function impl(user, expect) { const snRoles = tojson(sortAndNormalizeRoles(user.roles)); const uri = "mongodb://localhost:" + mongod.port + "/admin"; const script = tojson(sortAndNormalizeRoles) + 'assert(db.getSiblingDB("$external").auth({mechanism: "MONGODB-X509"}));' + "const status = assert.commandWorked(db.runCommand({connectionStatus: 1}));" + "const roles = status.authInfo.authenticatedUserRoles;" + "assert.eq(" + snRoles + ", sortAndNormalizeRoles(roles));"; const mongo = runMongoProgram( "mongo", "--tls", "--tlsCertificateKeyFile", user.cert, "--tlsCAFile", COMBINED_CA_CERT, uri, "--eval", script, ); expect(mongo, 0); } success.forEach((u) => impl(u, assert.eq)); failure.forEach((u) => impl(u, assert.neq)); MongoRunner.stopMongod(mongod); } // Positive tests. const unconfigured = null; test(unconfigured, [CLIENT, CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES], []); const allRoles = [ {sha256: CA_HASH, roles: [{role: "", db: ""}]}, {sha256: TRUSTED_CA_HASH, roles: [{role: "", db: ""}]}, ]; test(allRoles, [CLIENT, CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES], []); const allRolesOnAdmin = [{sha256: CA_HASH, roles: [{role: "", db: "admin"}]}]; test(allRolesOnAdmin, [CLIENT, CLIENT_ROLES], [TRUSTED_CLIENT_TESTDB_ROLES]); const specificRolesOnAnyDB = [ { sha256: CA_HASH, roles: [ {role: "backup", db: ""}, {role: "readAnyDatabase", db: ""}, ], }, ]; test(specificRolesOnAnyDB, [CLIENT, CLIENT_ROLES], [TRUSTED_CLIENT_TESTDB_ROLES]); const exactRoles = [ { sha256: CA_HASH, roles: [ {role: "backup", db: "admin"}, {role: "readAnyDatabase", db: "admin"}, ], }, ]; test(exactRoles, [CLIENT, CLIENT_ROLES], [TRUSTED_CLIENT_TESTDB_ROLES]); const extraRoles = [ { sha256: CA_HASH, roles: [ {role: "backup", db: "admin"}, {role: "readAnyDatabase", db: "admin"}, {role: "readWrite", db: "admin"}, ], }, ]; test(extraRoles, [CLIENT, CLIENT_ROLES], [TRUSTED_CLIENT_TESTDB_ROLES]); const similarRoles = [ { sha256: CA_HASH, roles: [ {role: "backup", db: "test"}, {role: "readAnyDatabase", db: ""}, {role: "backup", db: "admin"}, ], }, { sha256: TRUSTED_CA_HASH, roles: [ {role: "role1", db: "admin"}, {role: "role2", db: "testDB"}, {role: "role1", db: "testDB"}, ], }, ]; test(similarRoles, [CLIENT, CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES], []); const withUntrusted = [ {sha256: CA_HASH, roles: [{role: "", db: ""}]}, {sha256: TRUSTED_CA_HASH, roles: []}, ]; test(withUntrusted, [CLIENT, CLIENT_ROLES], [TRUSTED_CLIENT_TESTDB_ROLES]); const customRoles = [ { sha256: TRUSTED_CA_HASH, roles: [ {role: "role1", db: "testDB"}, {role: "role2", db: "testDB"}, ], }, ]; test(customRoles, [CLIENT, TRUSTED_CLIENT_TESTDB_ROLES], [CLIENT_ROLES]); // Negative tests. CLIENT_CERT is okay because it doesn't ask for roles. const noTrustedCAs = []; test(noTrustedCAs, [CLIENT], [CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES]); const noRoles = [{sha256: CA_HASH, roles: []}]; test(noRoles, [CLIENT], [CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES]); const insufficientRoles1 = [ {sha256: CA_HASH, roles: [{role: "backup", db: ""}]}, {sha256: TRUSTED_CA_HASH, roles: [{role: "role1", db: "testDB"}]}, ]; test(insufficientRoles1, [CLIENT], [CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES]); const insufficientRoles2 = [ {sha256: CA_HASH, roles: [{role: "readWriteAnyDatabase", db: ""}]}, {sha256: TRUSTED_CA_HASH, roles: [{role: "role2", db: "testDB"}]}, ]; test(insufficientRoles2, [CLIENT], [CLIENT_ROLES, TRUSTED_CLIENT_TESTDB_ROLES]); const withTrusted = [ {sha256: CA_HASH, roles: []}, {sha256: TRUSTED_CA_HASH, roles: [{role: "", db: ""}]}, ]; test(withTrusted, [CLIENT, TRUSTED_CLIENT_TESTDB_ROLES], [CLIENT_ROLES]); });