summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jstests/auth/security_token.js7
-rw-r--r--src/mongo/client/dbclient_base.cpp7
-rw-r--r--src/mongo/db/SConscript6
-rw-r--r--src/mongo/db/auth/security_token.cpp20
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp3
-rw-r--r--src/mongo/db/commands/user_management_commands_common.cpp10
-rw-r--r--src/mongo/db/dollar_tenant_decoration_test.cpp151
-rw-r--r--src/mongo/db/multitenancy.cpp45
-rw-r--r--src/mongo/db/multitenancy.h6
-rw-r--r--src/mongo/db/tenant_namespace_test.cpp2
-rw-r--r--src/mongo/executor/network_interface_tl.cpp3
-rw-r--r--src/mongo/rpc/SConscript1
-rw-r--r--src/mongo/rpc/metadata.cpp3
14 files changed, 240 insertions, 25 deletions
diff --git a/jstests/auth/security_token.js b/jstests/auth/security_token.js
index e0972118af7..4abf08e3cbf 100644
--- a/jstests/auth/security_token.js
+++ b/jstests/auth/security_token.js
@@ -43,8 +43,11 @@ function makeTokenAndExpect(user, db) {
function runTest(conn, enabled, rst = undefined) {
const admin = conn.getDB('admin');
const tenantAdmin = conn.getDB(tenantID.str + '_admin');
- assert.commandWorked(admin.runCommand({createUser: 'admin', pwd: 'admin', roles: ['root']}));
- assert(admin.auth('admin', 'admin'));
+
+ // Must be authenticated as the internal __system user in order to use $tenant
+ assert.commandWorked(
+ admin.runCommand({createUser: 'system', pwd: 'system', roles: ['__system']}));
+ assert(admin.auth('system', 'system'));
// Create a tenant-local user.
const createUserCmd =
diff --git a/src/mongo/client/dbclient_base.cpp b/src/mongo/client/dbclient_base.cpp
index 98c49806f40..70c500fd412 100644
--- a/src/mongo/client/dbclient_base.cpp
+++ b/src/mongo/client/dbclient_base.cpp
@@ -186,8 +186,11 @@ void appendMetadata(OperationContext* opCtx,
}
request.body = bob.obj();
- if (auto securityToken = auth::getSecurityToken(opCtx)) {
- request.securityToken = securityToken->toBSON();
+
+ if (opCtx) {
+ if (auto securityToken = auth::getSecurityToken(opCtx)) {
+ request.securityToken = securityToken->toBSON();
+ }
}
}
} // namespace
diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript
index 2c6e794531f..e126fafcda4 100644
--- a/src/mongo/db/SConscript
+++ b/src/mongo/db/SConscript
@@ -85,7 +85,9 @@ env.Library(
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
+ 'auth/auth',
'auth/security_token',
+ 'multitenancy_params',
],
)
@@ -1013,6 +1015,7 @@ env.Library(
'commands/server_status_core',
'initialize_api_parameters',
'introspect',
+ 'multitenancy',
'not_primary_error_tracker',
'query_exec',
'repl/repl_server_parameters',
@@ -2485,6 +2488,7 @@ if wiredtiger:
'dbmessage_test.cpp',
'db_raii_test.cpp',
'db_raii_multi_collection_test.cpp',
+ 'dollar_tenant_decoration_test.cpp',
"explain_test.cpp",
'field_parser_test.cpp',
'field_ref_set_test.cpp',
@@ -2537,6 +2541,7 @@ if wiredtiger:
'$BUILD_DIR/mongo/bson/util/bson_extract',
'$BUILD_DIR/mongo/client/read_preference',
'$BUILD_DIR/mongo/db/auth/auth',
+ '$BUILD_DIR/mongo/db/auth/security_token',
'$BUILD_DIR/mongo/db/catalog/catalog_test_fixture',
'$BUILD_DIR/mongo/db/catalog/import_collection_oplog_entry',
'$BUILD_DIR/mongo/db/catalog/index_build_entry_idl',
@@ -2574,6 +2579,7 @@ if wiredtiger:
'logical_session_id_helpers',
'logical_time',
'mirror_maestro',
+ 'multitenancy',
'multitenancy_params',
'namespace_string',
'op_observer',
diff --git a/src/mongo/db/auth/security_token.cpp b/src/mongo/db/auth/security_token.cpp
index b38fe1e0b9b..7ab41457a21 100644
--- a/src/mongo/db/auth/security_token.cpp
+++ b/src/mongo/db/auth/security_token.cpp
@@ -52,13 +52,19 @@ MONGO_INITIALIZER(SecurityTokenOptionValidate)(InitializerContext*) {
if (gMultitenancySupport) {
logv2::detail::setGetTenantIDCallback([]() -> boost::optional<OID> {
auto* client = Client::getCurrent();
- auto* opCtx = client ? client->getOperationContext() : nullptr;
- auto token = getSecurityToken(opCtx);
- if (token) {
- return token->getAuthenticatedUser().getTenant();
- } else {
+ if (!client)
return boost::none;
+
+ if (auto* opCtx = client->getOperationContext()) {
+ auto token = getSecurityToken(opCtx);
+ if (token) {
+ return token->getAuthenticatedUser().getTenant();
+ } else {
+ return boost::none;
+ }
}
+
+ return boost::none;
});
}
}
@@ -114,10 +120,6 @@ void readSecurityTokenMetadata(OperationContext* opCtx, BSONObj securityToken) t
}
MaybeSecurityToken getSecurityToken(OperationContext* opCtx) {
- if (!opCtx) {
- return boost::none;
- }
-
return securityTokenDecoration(opCtx);
}
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index c8e94e111c9..4735b9f4080 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -530,6 +530,7 @@ env.Library(
'$BUILD_DIR/mongo/db/exec/sbe_cmd',
'$BUILD_DIR/mongo/db/exec/stagedebug_cmd',
'$BUILD_DIR/mongo/db/index_builds_coordinator_interface',
+ '$BUILD_DIR/mongo/db/multitenancy',
'$BUILD_DIR/mongo/db/pipeline/pipeline',
'$BUILD_DIR/mongo/db/pipeline/process_interface/mongo_process_interface',
'$BUILD_DIR/mongo/db/repl/dbcheck',
diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp
index 2a5f37ae267..a9a08d44856 100644
--- a/src/mongo/db/commands/user_management_commands.cpp
+++ b/src/mongo/db/commands/user_management_commands.cpp
@@ -65,6 +65,7 @@
#include "mongo/db/curop.h"
#include "mongo/db/dbdirectclient.h"
#include "mongo/db/jsobj.h"
+#include "mongo/db/multitenancy.h"
#include "mongo/db/operation_context.h"
#include "mongo/db/ops/write_ops.h"
#include "mongo/db/pipeline/aggregation_request_helper.h"
@@ -1020,7 +1021,7 @@ void CmdUMCTyped<CreateUserCommand>::Invocation::typedRun(OperationContext* opCt
uassert(ErrorCodes::BadValue,
"Username cannot contain NULL characters",
cmd.getCommandParameter().find('\0') == std::string::npos);
- UserName userName(cmd.getCommandParameter(), dbname, cmd.getTenantOverride());
+ UserName userName(cmd.getCommandParameter(), dbname, getActiveTenant(opCtx));
uassert(ErrorCodes::BadValue,
"Must provide a 'pwd' field for all user documents, except those"
diff --git a/src/mongo/db/commands/user_management_commands_common.cpp b/src/mongo/db/commands/user_management_commands_common.cpp
index e6c4539a724..4645c22d506 100644
--- a/src/mongo/db/commands/user_management_commands_common.cpp
+++ b/src/mongo/db/commands/user_management_commands_common.cpp
@@ -191,16 +191,6 @@ void checkAuthForTypedCommand(OperationContext* opCtx, const CreateUserCommand&
as->isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(dbname),
ActionType::createUser));
- if (request.getTenantOverride() != boost::none) {
- const bool isNotTokenAuth = (as->getAuthenticationMode() !=
- AuthorizationSession::AuthenticationMode::kSecurityToken);
-
- uassert(ErrorCodes::Unauthorized,
- "$tenant parameter to createUser command only accepted in "
- "test mode with security tokens enabled but not in use",
- getTestCommandsEnabled() && gMultitenancySupport && isNotTokenAuth);
- }
-
auto resolvedRoles = resolveRoleNames(request.getRoles(), dbname);
uassertStatusOK(checkAuthorizedToGrantRoles(as, resolvedRoles));
diff --git a/src/mongo/db/dollar_tenant_decoration_test.cpp b/src/mongo/db/dollar_tenant_decoration_test.cpp
new file mode 100644
index 00000000000..161164710cc
--- /dev/null
+++ b/src/mongo/db/dollar_tenant_decoration_test.cpp
@@ -0,0 +1,151 @@
+/**
+ * Copyright (C) 2022-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/platform/basic.h"
+
+#include "mongo/bson/oid.h"
+#include "mongo/db/auth/authorization_manager_impl.h"
+#include "mongo/db/auth/authorization_session.h"
+#include "mongo/db/auth/authz_manager_external_state_mock.h"
+#include "mongo/db/auth/security_token.h"
+#include "mongo/db/multitenancy.h"
+#include "mongo/db/multitenancy_gen.h"
+#include "mongo/db/service_context_test_fixture.h"
+#include "mongo/unittest/unittest.h"
+
+namespace mongo {
+namespace {
+
+class DollarTenantDecorationTest : public ScopedGlobalServiceContextForTest, public unittest::Test {
+protected:
+ void setUp() final {
+ auto authzManagerState = std::make_unique<AuthzManagerExternalStateMock>();
+ auto authzManager = std::make_unique<AuthorizationManagerImpl>(
+ getServiceContext(), std::move(authzManagerState));
+ authzManager->setAuthEnabled(true);
+ AuthorizationManager::set(getServiceContext(), std::move(authzManager));
+
+ client = getServiceContext()->makeClient("test");
+ opCtxPtr = getServiceContext()->makeOperationContext(client.get());
+ opCtx = opCtxPtr.get();
+ }
+
+ BSONObj makeSecurityToken(const UserName& userName) {
+ auto authUser = userName.toBSON(true /* serialize token */);
+ ASSERT_EQ(authUser["tenant"_sd].type(), jstOID);
+
+ BSONObjBuilder token;
+ token.append("authenticatedUser", authUser);
+
+ auto block =
+ SHA256Block::computeHash({ConstDataRange(authUser.objdata(), authUser.objsize())});
+ token.appendBinData("sig", block.size(), BinDataGeneral, block.data());
+
+ return token.obj();
+ }
+
+ ServiceContext::UniqueClient client;
+ ServiceContext::UniqueOperationContext opCtxPtr;
+ OperationContext* opCtx;
+};
+
+TEST_F(DollarTenantDecorationTest, ParseDollarTenantFromRequestSecurityTokenAlreadySet) {
+ gMultitenancySupport = true;
+
+ // Ensure the security token is set on the opCtx.
+ const auto kOid = OID::gen();
+ auto token = makeSecurityToken(UserName("user", "admin", kOid));
+ auth::readSecurityTokenMetadata(opCtx, token);
+ ASSERT(getActiveTenant(opCtx));
+ ASSERT_EQ(*getActiveTenant(opCtx), kOid);
+
+ // TODO SERVER-62406 use the new ActionType for use with $tenant.
+ // Grant internal auth so that we're authenticated as the internal __system user.
+ AuthorizationSession::get(opCtx->getClient())->grantInternalAuthorization(opCtx);
+
+ // The dollarTenantDecoration should not be set because the security token is already set.
+ const auto kOidParameter = OID::gen();
+ auto opMsgRequest = OpMsgRequest::fromDBAndBody("test", BSON("$tenant" << kOidParameter));
+ ASSERT_THROWS_CODE(
+ parseDollarTenantFromRequest(opCtx, opMsgRequest), AssertionException, 6223901);
+
+ // getActiveTenant should still return the tenantId in the security token.
+ ASSERT(getActiveTenant(opCtx));
+ ASSERT_EQ(*getActiveTenant(opCtx), kOid);
+}
+
+TEST_F(DollarTenantDecorationTest, ParseDollarTenantFromRequestNotInternalSecurityUser) {
+ gMultitenancySupport = true;
+ const auto kOid = OID::gen();
+
+ // We are not authenticated as the internal security user.
+ auto opMsgRequest = OpMsgRequest::fromDBAndBody("test", BSON("$tenant" << kOid));
+ ASSERT_THROWS_CODE(parseDollarTenantFromRequest(opCtx, opMsgRequest),
+ AssertionException,
+ ErrorCodes::Unauthorized);
+ ASSERT(!getActiveTenant(opCtx));
+}
+
+TEST_F(DollarTenantDecorationTest, ParseDollarTenantMultitenancySupportDisabled) {
+ gMultitenancySupport = false;
+ const auto kOid = OID::gen();
+
+ // TODO SERVER-62406 use the new ActionType for use with $tenant.
+ // Grant internal auth so that we're authenticated as the internal __system user.
+ AuthorizationSession::get(opCtx->getClient())->grantInternalAuthorization(opCtx);
+
+ // TenantId is passed as the '$tenant' parameter. "multitenancySupport" is disabled, so we
+ // should throw when attempting to set this tenantId on the opCtx.
+ auto opMsgRequestParameter = OpMsgRequest::fromDBAndBody("test", BSON("$tenant" << kOid));
+ ASSERT_THROWS_CODE(parseDollarTenantFromRequest(opCtx, opMsgRequestParameter),
+ AssertionException,
+ ErrorCodes::InvalidOptions);
+ ASSERT(!getActiveTenant(opCtx));
+}
+
+TEST_F(DollarTenantDecorationTest, ParseDollarTenantFromRequestSuccess) {
+ gMultitenancySupport = true;
+ const auto kOid = OID::gen();
+
+ // TODO SERVER-62406 use the new ActionType for use with $tenant.
+ // Grant internal auth so that we're authenticated as the internal __system user.
+ AuthorizationSession::get(opCtx->getClient())->grantInternalAuthorization(opCtx);
+
+ // The tenantId should be successfully set because "multitenancySupport" is enabled and we're
+ // authenticated as the internal __system user.
+ auto opMsgRequest = OpMsgRequest::fromDBAndBody("test", BSON("$tenant" << kOid));
+ parseDollarTenantFromRequest(opCtx, opMsgRequest);
+
+ auto tenantId = getActiveTenant(opCtx);
+ ASSERT(tenantId);
+ ASSERT_EQ(*tenantId, kOid);
+}
+
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/multitenancy.cpp b/src/mongo/db/multitenancy.cpp
index fbe5d6e651a..ddc849ae3e5 100644
--- a/src/mongo/db/multitenancy.cpp
+++ b/src/mongo/db/multitenancy.cpp
@@ -26,10 +26,15 @@
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
+#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault
#include "mongo/db/multitenancy.h"
+#include "mongo/bson/oid.h"
+#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/auth/security_token.h"
+#include "mongo/db/multitenancy_gen.h"
+#include "mongo/logv2/log.h"
namespace mongo {
@@ -38,12 +43,50 @@ const OID kSystemTenantID(
"0102030405" /* process id */
"060708" /* counter */);
+// Holds the tenantId for the operation if it was provided in the request on the $tenant field only
+// if the tenantId was not also provided in the security token.
+const auto dollarTenantDecoration =
+ OperationContext::declareDecoration<boost::optional<mongo::OID>>();
+
+void parseDollarTenantFromRequest(OperationContext* opCtx, const OpMsg& request) {
+ // The internal security user is allowed to run commands on behalf of a tenant by passing
+ // the tenantId in the "$tenant" field.
+ auto tenantElem = request.body["$tenant"];
+ if (!tenantElem)
+ return;
+
+ uassert(ErrorCodes::InvalidOptions,
+ "Multitenancy not enabled, cannot set $tenant in command body",
+ gMultitenancySupport);
+
+ // TODO SERVER-62406 check for the new ActionType for use with $tenant.
+ uassert(ErrorCodes::Unauthorized,
+ "'$tenant' may only be specified with the internal action type",
+ AuthorizationSession::get(opCtx->getClient())
+ ->isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(),
+ ActionType::internal));
+
+ auto tenantId = tenantElem.OID();
+
+ uassert(6223901,
+ str::stream() << "Cannot pass $tenant id if also passing securityToken, securityToken: "
+ << auth::getSecurityToken(opCtx)->getAuthenticatedUser().getTenant()
+ << " $tenant: " << tenantId,
+ !auth::getSecurityToken(opCtx));
+
+
+ dollarTenantDecoration(opCtx) = std::move(tenantId);
+ LOGV2_DEBUG(
+ 6223900, 4, "Setting tenantId from $tenant request parameter", "tenantId"_attr = tenantId);
+}
+
boost::optional<OID> getActiveTenant(OperationContext* opCtx) {
auto token = auth::getSecurityToken(opCtx);
if (!token) {
- return boost::none;
+ return dollarTenantDecoration(opCtx);
}
+ invariant(!dollarTenantDecoration(opCtx));
return token->getAuthenticatedUser().getTenant();
}
diff --git a/src/mongo/db/multitenancy.h b/src/mongo/db/multitenancy.h
index f107bb6dec0..ba537108d79 100644
--- a/src/mongo/db/multitenancy.h
+++ b/src/mongo/db/multitenancy.h
@@ -46,6 +46,12 @@ namespace mongo {
extern const OID kSystemTenantID;
/**
+ * Parses the tenantId from the '$tenant' field in the request if it exists and
+ * "multitenancySupport" is enabled. Then, sets the parsed tenantId on the opCtx.
+ */
+void parseDollarTenantFromRequest(OperationContext* opCtx, const OpMsg& request);
+
+/**
* Extract the active TenantID for this operation.
*/
boost::optional<OID> getActiveTenant(OperationContext* opCtx);
diff --git a/src/mongo/db/tenant_namespace_test.cpp b/src/mongo/db/tenant_namespace_test.cpp
index d3f3f418ba5..6b4917422af 100644
--- a/src/mongo/db/tenant_namespace_test.cpp
+++ b/src/mongo/db/tenant_namespace_test.cpp
@@ -40,6 +40,7 @@ namespace mongo {
namespace {
TEST(TenantNamespaceTest, TenantNamespaceMultitenancySupportDisabledBasic) {
+ RAIIServerParameterControllerForTest multitenanyController("multitenancySupport", false);
TenantNamespace tenantNs(boost::none, NamespaceString("a.b"));
ASSERT(!tenantNs.tenantId());
ASSERT_EQUALS(std::string("a"), tenantNs.db());
@@ -48,6 +49,7 @@ TEST(TenantNamespaceTest, TenantNamespaceMultitenancySupportDisabledBasic) {
}
TEST(TenantNamespaceTest, TenantNamespaceParseFromDiskMultitenancySupportDisabled) {
+ RAIIServerParameterControllerForTest multitenanyController("multitenancySupport", false);
TenantNamespace tenantNs = TenantNamespace::parseTenantNamespaceFromDisk("a.b");
ASSERT(!tenantNs.tenantId());
ASSERT_EQUALS(std::string("a"), tenantNs.db());
diff --git a/src/mongo/executor/network_interface_tl.cpp b/src/mongo/executor/network_interface_tl.cpp
index 8b749126060..5fa9a00e6cd 100644
--- a/src/mongo/executor/network_interface_tl.cpp
+++ b/src/mongo/executor/network_interface_tl.cpp
@@ -64,6 +64,9 @@ Status appendMetadata(RemoteCommandRequestOnAny* request,
request->metadata = bob.obj();
}
+ if (!request->opCtx)
+ return Status::OK();
+
if (auto securityToken = auth::getSecurityToken(request->opCtx)) {
request->securityToken = securityToken->toBSON();
}
diff --git a/src/mongo/rpc/SConscript b/src/mongo/rpc/SConscript
index e8a35204e4d..1b07ad5c2f3 100644
--- a/src/mongo/rpc/SConscript
+++ b/src/mongo/rpc/SConscript
@@ -130,6 +130,7 @@ env.Library(
'metadata_impersonated_user',
],
LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/db/multitenancy',
'$BUILD_DIR/mongo/db/vector_clock',
],
)
diff --git a/src/mongo/rpc/metadata.cpp b/src/mongo/rpc/metadata.cpp
index fa7911f59b5..ba002cdc748 100644
--- a/src/mongo/rpc/metadata.cpp
+++ b/src/mongo/rpc/metadata.cpp
@@ -37,6 +37,7 @@
#include "mongo/db/dbmessage.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/logical_time_validator.h"
+#include "mongo/db/multitenancy.h"
#include "mongo/db/vector_clock.h"
#include "mongo/rpc/metadata/client_metadata.h"
#include "mongo/rpc/metadata/impersonated_user_metadata.h"
@@ -92,6 +93,8 @@ void readRequestMetadata(OperationContext* opCtx, const OpMsg& opMsg, bool cmdRe
readImpersonatedUserMetadata(impersonationElem, opCtx);
auth::readSecurityTokenMetadata(opCtx, opMsg.securityToken);
+ parseDollarTenantFromRequest(opCtx, opMsg);
+
// We check for "$client" but not "client" here, because currentOp can filter on "client" as
// a top-level field.
if (clientElem) {