diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2022-06-08 22:22:08 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2022-06-15 17:51:25 +0000 |
commit | d762bb7bc5e99c387fe16468c562132de24c5a45 (patch) | |
tree | 7c84a777cf15c3d90a6e2ed5c4010496a222efa7 /src/mongo | |
parent | 631ca9943ca90732a0bd7ff8454a13fc78948c10 (diff) | |
download | mongo-d762bb7bc5e99c387fe16468c562132de24c5a45.tar.gz |
SERVER-67148 Refactor ValidatedTenantId into ValidatedSecurityToken
Diffstat (limited to 'src/mongo')
28 files changed, 410 insertions, 372 deletions
diff --git a/src/mongo/client/async_client.cpp b/src/mongo/client/async_client.cpp index 3a568fa7e31..568b93b7e81 100644 --- a/src/mongo/client/async_client.cpp +++ b/src/mongo/client/async_client.cpp @@ -319,7 +319,7 @@ Future<executor::RemoteCommandResponse> AsyncDBClient::runCommandRequest( auto startTimer = Timer(); auto opMsgRequest = OpMsgRequest::fromDBAndBody( std::move(request.dbname), std::move(request.cmdObj), std::move(request.metadata)); - opMsgRequest.securityToken = request.securityToken; + opMsgRequest.validatedTenancyScope = request.validatedTenancyScope; return runCommand(std::move(opMsgRequest), baton, request.options.fireAndForget) .then([this, startTimer = std::move(startTimer)](rpc::UniqueReply response) { return executor::RemoteCommandResponse(*response, startTimer.elapsed()); @@ -358,7 +358,7 @@ Future<executor::RemoteCommandResponse> AsyncDBClient::beginExhaustCommandReques executor::RemoteCommandRequest request, const BatonHandle& baton) { auto opMsgRequest = OpMsgRequest::fromDBAndBody( std::move(request.dbname), std::move(request.cmdObj), std::move(request.metadata)); - opMsgRequest.securityToken = request.securityToken; + opMsgRequest.validatedTenancyScope = request.validatedTenancyScope; return runExhaustCommand(std::move(opMsgRequest), baton); } diff --git a/src/mongo/client/dbclient_base.cpp b/src/mongo/client/dbclient_base.cpp index cbef18c6867..5c2238ebec9 100644 --- a/src/mongo/client/dbclient_base.cpp +++ b/src/mongo/client/dbclient_base.cpp @@ -49,7 +49,7 @@ #include "mongo/client/dbclient_cursor.h" #include "mongo/config.h" #include "mongo/db/api_parameters_gen.h" -#include "mongo/db/auth/security_token.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/commands.h" #include "mongo/db/json.h" #include "mongo/db/namespace_string.h" @@ -190,8 +190,8 @@ void appendMetadata(OperationContext* opCtx, request.body = bob.obj(); if (opCtx) { - if (auto securityToken = auth::getSecurityToken(opCtx)) { - request.securityToken = securityToken->toBSON(); + if (auto validatedTenancyScope = auth::ValidatedTenancyScope::get(opCtx)) { + request.validatedTenancyScope = validatedTenancyScope; } } } diff --git a/src/mongo/db/SConscript b/src/mongo/db/SConscript index 923e64998cb..40a8c0687f9 100644 --- a/src/mongo/db/SConscript +++ b/src/mongo/db/SConscript @@ -84,7 +84,6 @@ env.Library( target='multitenancy', source=[ 'multitenancy.cpp', - 'validated_tenant_id.cpp', ], LIBDEPS=[ 'multitenancy_params', @@ -2746,7 +2745,6 @@ if wiredtiger: 'ttl_test.cpp', 'update_index_data_test.cpp', 'user_write_block_mode_op_observer_test.cpp', - 'validated_tenant_id_test.cpp', 'vector_clock_mongod_test.cpp', 'vector_clock_test.cpp', 'wire_version_test.cpp', diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 3df9d6922f2..0f4dcbc61bb 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -7,8 +7,9 @@ env = env.Clone() env.Library( target='security_token', source=[ - 'security_token.cpp', + 'security_token_authentication_guard.cpp', 'security_token.idl', + 'validated_tenancy_scope.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', @@ -540,6 +541,7 @@ env.CppUnitTest( 'sasl_scram_test.cpp', 'security_key_test.cpp', 'user_document_parser_test.cpp', + 'validated_tenancy_scope_test.cpp', ], LIBDEPS=[ '$BUILD_DIR/mongo/base', diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp index 173a19cfd58..5ebc878a07c 100644 --- a/src/mongo/db/auth/authorization_session_impl.cpp +++ b/src/mongo/db/auth/authorization_session_impl.cpp @@ -43,7 +43,7 @@ #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authz_session_external_state.h" #include "mongo/db/auth/privilege.h" -#include "mongo/db/auth/security_token.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/bson/dotted_path_support.h" #include "mongo/db/client.h" #include "mongo/db/namespace_string.h" @@ -245,14 +245,15 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, stdx::lock_guard<Client> lk(*opCtx->getClient()); - if (auto token = auth::getSecurityToken(opCtx)) { + auto validatedTenancyScope = auth::ValidatedTenancyScope::get(opCtx); + if (validatedTenancyScope && validatedTenancyScope->hasAuthenticatedUser()) { uassert( 6161501, "Attempt to authorize via security token on connection with established authentication", _authenticationMode != AuthenticationMode::kConnection); uassert(6161502, "Attempt to authorize a user other than that present in the security token", - token->getAuthenticatedUser() == userName); + validatedTenancyScope->authenticatedUser() == userName); validateSecurityTokenUserPrivileges(user->getPrivileges()); _authenticationMode = AuthenticationMode::kSecurityToken; } else { diff --git a/src/mongo/db/auth/security_token.h b/src/mongo/db/auth/security_token_authentication_guard.cpp index 315e6efcbf4..5be6de3dc75 100644 --- a/src/mongo/db/auth/security_token.h +++ b/src/mongo/db/auth/security_token_authentication_guard.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021-present MongoDB, Inc. + * 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, @@ -27,51 +27,40 @@ * it in the license file. */ -#pragma once -#include <boost/optional.hpp> +#include "mongo/db/auth/security_token_authentication_guard.h" -#include "mongo/bson/bsonobj.h" -#include "mongo/db/auth/security_token_gen.h" -#include "mongo/db/client.h" -#include "mongo/db/operation_context.h" +#include "mongo/db/auth/authorization_session.h" +#include "mongo/logv2/log.h" + +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kAccessControl namespace mongo { namespace auth { -class SecurityTokenAuthenticationGuard { -public: - SecurityTokenAuthenticationGuard() = delete; - SecurityTokenAuthenticationGuard(OperationContext* opCtx); - ~SecurityTokenAuthenticationGuard(); - -private: - Client* _client; -}; - -/** - * Takes an unsigned security token as input and applies - * the temporary signature algorithm to extend it into a full SecurityToken. - */ -BSONObj signSecurityToken(BSONObj obj); - -/** - * Verify the contents of the provided security token - * using the temporary signing algorithm, - */ -SecurityToken verifySecurityToken(BSONObj obj); +SecurityTokenAuthenticationGuard::SecurityTokenAuthenticationGuard( + OperationContext* opCtx, const ValidatedTenancyScope& token) { + if (token.hasAuthenticatedUser()) { + const auto& userName = token.authenticatedUser(); + auto* client = opCtx->getClient(); + uassertStatusOK(AuthorizationSession::get(client)->addAndAuthorizeUser(opCtx, userName)); + _client = client; -/** - * Parse the validated SecurityToken from the OpMsg and place it as a decoration - * on OperationContext. - */ -void setSecurityToken(OperationContext* opCtx, const OpMsg& opMsg); + LOGV2_DEBUG(5838100, + 4, + "Authenticated with security token", + "token"_attr = token.getOriginalToken()); + } else { + _client = nullptr; + } +} -/** - * Retrieve the Security Token associated with this operation context - */ -using MaybeSecurityToken = boost::optional<SecurityToken>; -MaybeSecurityToken getSecurityToken(OperationContext* opCtx); +SecurityTokenAuthenticationGuard::~SecurityTokenAuthenticationGuard() { + if (_client) { + // SecurityToken based users are "logged out" at the end of their request. + AuthorizationSession::get(_client)->logoutSecurityTokenUser(_client); + } +} } // namespace auth } // namespace mongo diff --git a/src/mongo/db/validated_tenant_id.h b/src/mongo/db/auth/security_token_authentication_guard.h index d5843de05f9..c73e0324e5f 100644 --- a/src/mongo/db/validated_tenant_id.h +++ b/src/mongo/db/auth/security_token_authentication_guard.h @@ -29,35 +29,27 @@ #pragma once -#include "mongo/db/database_name.h" -#include "mongo/db/tenant_id.h" +#include "mongo/db/auth/validated_tenancy_scope.h" +#include "mongo/db/client.h" +#include "mongo/db/operation_context.h" namespace mongo { +namespace auth { -class Client; -struct OpMsg; - -class ValidatedTenantId { +/** + * If ValidatedTenancyScope represents an AuthenticatedUser, + * that user will be authenticated against the client until this guard dies. + * This is used in ServiceEntryPoint to scope authentication to a single operation. + */ +class SecurityTokenAuthenticationGuard { public: - ValidatedTenantId(const ValidatedTenantId& validatedTenant) = default; - - /** - * Constructs a ValidatedTenantId by parsing tenant from $tenant or security token of opMsg - * and validating it with the auth module. - */ - ValidatedTenantId(const OpMsg& opMsg, Client& client); - - /** - * Constructs a ValidatedTenantId by treating the tenantId on DatabaseName as validated. - */ - ValidatedTenantId(const DatabaseName& dbName); - - const boost::optional<TenantId>& tenantId() const { - return _tenant; - } + SecurityTokenAuthenticationGuard() = delete; + SecurityTokenAuthenticationGuard(OperationContext*, const ValidatedTenancyScope&); + ~SecurityTokenAuthenticationGuard(); private: - boost::optional<TenantId> _tenant = boost::none; + Client* _client; }; +} // namespace auth } // namespace mongo diff --git a/src/mongo/db/auth/security_token.cpp b/src/mongo/db/auth/validated_tenancy_scope.cpp index f20cffe04c2..2ab66b3abd3 100644 --- a/src/mongo/db/auth/security_token.cpp +++ b/src/mongo/db/auth/validated_tenancy_scope.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2021-present MongoDB, Inc. + * 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, @@ -27,26 +27,23 @@ * it in the license file. */ - -#include "mongo/db/auth/security_token.h" - -#include <boost/optional.hpp> +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/base/init.h" #include "mongo/db/auth/authorization_session.h" +#include "mongo/db/auth/security_token_gen.h" +#include "mongo/db/multitenancy.h" #include "mongo/db/multitenancy_gen.h" #include "mongo/db/server_feature_flags_gen.h" -#include "mongo/db/tenant_id.h" #include "mongo/logv2/log.h" #include "mongo/logv2/log_detail.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kAccessControl - -namespace mongo { -namespace auth { +namespace mongo::auth { namespace { -const auto securityTokenDecoration = OperationContext::declareDecoration<MaybeSecurityToken>(); +const auto validatedTenancyScopeDecoration = + OperationContext::declareDecoration<boost::optional<ValidatedTenancyScope>>(); MONGO_INITIALIZER(SecurityTokenOptionValidate)(InitializerContext*) { uassert(ErrorCodes::BadValue, "multitenancySupport may not be specified if featureFlagMongoStore is not enabled", @@ -54,15 +51,13 @@ MONGO_INITIALIZER(SecurityTokenOptionValidate)(InitializerContext*) { if (gMultitenancySupport) { logv2::detail::setGetTenantIDCallback([]() -> boost::optional<TenantId> { auto* client = Client::getCurrent(); - if (!client) + if (!client) { return boost::none; + } if (auto* opCtx = client->getOperationContext()) { - auto token = getSecurityToken(opCtx); - if (token) { - return token->getAuthenticatedUser().getTenant(); - } else { - return boost::none; + if (auto token = ValidatedTenancyScope::get(opCtx)) { + return token->tenantId(); } } @@ -72,45 +67,10 @@ MONGO_INITIALIZER(SecurityTokenOptionValidate)(InitializerContext*) { } } // namespace -SecurityTokenAuthenticationGuard::SecurityTokenAuthenticationGuard(OperationContext* opCtx) { - auto token = getSecurityToken(opCtx); - if (token == boost::none) { - _client = nullptr; - return; - } - - auto client = opCtx->getClient(); - uassertStatusOK(AuthorizationSession::get(client)->addAndAuthorizeUser( - opCtx, token->getAuthenticatedUser())); - _client = client; -} - -SecurityTokenAuthenticationGuard::~SecurityTokenAuthenticationGuard() { - if (_client) { - // SecurityToken based users are "logged out" at the end of their request. - AuthorizationSession::get(_client)->logoutSecurityTokenUser(_client); - } -} - -BSONObj signSecurityToken(BSONObj obj) { - auto authUserElem = obj[SecurityToken::kAuthenticatedUserFieldName]; - uassert(ErrorCodes::BadValue, - "Invalid field(s) in token being signed", - (authUserElem.type() == Object) && (obj.nFields() == 1)); - - auto authUserObj = authUserElem.Obj(); - ConstDataRange authUserCDR(authUserObj.objdata(), authUserObj.objsize()); - - // Placeholder algorithm. - auto sig = SHA256Block::computeHash({authUserCDR}); - - BSONObjBuilder signedToken(obj); - signedToken.appendBinData(SecurityToken::kSigFieldName, sig.size(), BinDataGeneral, sig.data()); - return signedToken.obj(); -} - -SecurityToken verifySecurityToken(BSONObj obj) { - uassert(ErrorCodes::BadValue, "Multitenancy not enabled", gMultitenancySupport); +ValidatedTenancyScope::ValidatedTenancyScope(BSONObj obj, InitTag tag) : _originalToken(obj) { + uassert(ErrorCodes::InvalidOptions, + "Multitenancy not enabled, refusing to accept securityToken", + gMultitenancySupport || (tag == InitTag::kInitForShell)); auto token = SecurityToken::parse({"Security Token"}, obj); auto authenticatedUser = token.getAuthenticatedUser(); @@ -126,22 +86,97 @@ SecurityToken verifySecurityToken(BSONObj obj) { auto computed = SHA256Block::computeHash({authUserCDR}); uassert(ErrorCodes::Unauthorized, "Token signature invalid", computed == token.getSig()); - return token; + + _tenantOrUser = std::move(authenticatedUser); } -void setSecurityToken(OperationContext* opCtx, const OpMsg& opMsg) { - if (opMsg.validatedTenant && opMsg.securityToken.nFields() > 0) { - // Use the security token directly as it has been validated by ValdiatedTenantId - // constructor. - securityTokenDecoration(opCtx) = - SecurityToken::parse({"Security Token"}, opMsg.securityToken); - LOGV2_DEBUG(5838100, 4, "Accepted security token", "token"_attr = opMsg.securityToken); +ValidatedTenancyScope::ValidatedTenancyScope(Client* client, TenantId tenant) + : _tenantOrUser(std::move(tenant)) { + uassert(ErrorCodes::InvalidOptions, + "Multitenancy not enabled, refusing to accept $tenant parameter", + gMultitenancySupport); + + uassert(ErrorCodes::Unauthorized, + "'$tenant' may only be specified with the useTenant action type", + client && + AuthorizationSession::get(client)->isAuthorizedForActionsOnResource( + ResourcePattern::forClusterResource(), ActionType::useTenant)); +} + +boost::optional<ValidatedTenancyScope> ValidatedTenancyScope::create(Client* client, + BSONObj body, + BSONObj securityToken) { + if (!gMultitenancySupport) { + return boost::none; + } + + auto dollarTenantElem = body["$tenant"_sd]; + const bool hasToken = securityToken.nFields() > 0; + + uassert(6545800, + "Cannot pass $tenant id if also passing securityToken", + dollarTenantElem.eoo() || !hasToken); + uassert(ErrorCodes::OperationFailed, + "Cannot process $tenant id when no client is available", + dollarTenantElem.eoo() || client); + + // TODO SERVER-66822: Re-enable this uassert. + // uassert(ErrorCodes::Unauthorized, + // "Multitenancy is enabled, $tenant id or securityToken is required.", + // dollarTenantElem || opMsg.securityToken.nFields() > 0); + + if (dollarTenantElem) { + return ValidatedTenancyScope(client, TenantId::parseFromBSON(dollarTenantElem)); + } else if (hasToken) { + return ValidatedTenancyScope(securityToken); + } else { + return boost::none; + } +} + +bool ValidatedTenancyScope::hasAuthenticatedUser() const { + return stdx::holds_alternative<UserName>(_tenantOrUser); +} + +const UserName& ValidatedTenancyScope::authenticatedUser() const { + invariant(hasAuthenticatedUser()); + return stdx::get<UserName>(_tenantOrUser); +} + +const TenantId& ValidatedTenancyScope::tenantId() const { + if (hasAuthenticatedUser()) { + return stdx::get<UserName>(_tenantOrUser).getTenant().get(); + } else { + invariant(stdx::holds_alternative<TenantId>(_tenantOrUser)); + return stdx::get<TenantId>(_tenantOrUser); } } -MaybeSecurityToken getSecurityToken(OperationContext* opCtx) { - return securityTokenDecoration(opCtx); +const boost::optional<ValidatedTenancyScope>& ValidatedTenancyScope::get(OperationContext* opCtx) { + return validatedTenancyScopeDecoration(opCtx); +} + +void ValidatedTenancyScope::set(OperationContext* opCtx, + boost::optional<ValidatedTenancyScope> token) { + validatedTenancyScopeDecoration(opCtx) = std::move(token); +} + +ValidatedTenancyScope::ValidatedTenancyScope(BSONObj obj, TokenForTestingTag) { + auto authUserElem = obj[SecurityToken::kAuthenticatedUserFieldName]; + uassert(ErrorCodes::BadValue, + "Invalid field(s) in token being signed", + (authUserElem.type() == Object) && (obj.nFields() == 1)); + + auto authUserObj = authUserElem.Obj(); + ConstDataRange authUserCDR(authUserObj.objdata(), authUserObj.objsize()); + + // Placeholder algorithm. + auto sig = SHA256Block::computeHash({authUserCDR}); + + BSONObjBuilder signedToken(obj); + signedToken.appendBinData(SecurityToken::kSigFieldName, sig.size(), BinDataGeneral, sig.data()); + _originalToken = signedToken.obj(); + _tenantOrUser = UserName::parseFromBSONObj(authUserObj); } -} // namespace auth -} // namespace mongo +} // namespace mongo::auth diff --git a/src/mongo/db/auth/validated_tenancy_scope.h b/src/mongo/db/auth/validated_tenancy_scope.h new file mode 100644 index 00000000000..302b3fdac5a --- /dev/null +++ b/src/mongo/db/auth/validated_tenancy_scope.h @@ -0,0 +1,116 @@ +/** + * 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. + */ + +#pragma once + +#include <boost/optional.hpp> + +#include "mongo/bson/bsonobj.h" +#include "mongo/db/auth/user_name.h" +#include "mongo/db/tenant_id.h" +#include "mongo/stdx/variant.h" + +namespace mongo { + +class Client; +class OperationContext; + +namespace auth { + +class ValidatedTenancyScope { +public: + ValidatedTenancyScope() = delete; + ValidatedTenancyScope(const ValidatedTenancyScope&) = default; + + // kInitForShell allows parsing a securityToken without multitenancy enabled. + // This is required in the shell since we do not enable this setting in non-servers. + enum class InitTag { + kNormal, + kInitForShell, + }; + + /** + * Constructs a ValidatedTenancyScope by parsing a SecurityToken from a BSON object + * and verifying its cryptographic signature. + */ + explicit ValidatedTenancyScope(BSONObj securityToken, InitTag tag = InitTag::kNormal); + + /** + * Constructs a ValidatedTenancyScope for tenant only by validating that the + * current client is permitted to specify a tenant via the $tenant field. + */ + ValidatedTenancyScope(Client* client, TenantId tenant); + + /** + * Parses the client provided command body and securityToken for tenantId, + * and for securityToken respectively, the authenticatedUser as well. + * + * Returns boost::none when multitenancy support is not enabled. + */ + static boost::optional<ValidatedTenancyScope> create(Client* client, + BSONObj body, + BSONObj securityToken); + + bool hasAuthenticatedUser() const; + const UserName& authenticatedUser() const; + const TenantId& tenantId() const; + + BSONObj getOriginalToken() const { + return _originalToken; + } + + /** + * Get/Set a ValidatedTenancyScope as a decoration on the OperationContext + */ + static const boost::optional<ValidatedTenancyScope>& get(OperationContext* opCtx); + static void set(OperationContext* opCtx, boost::optional<ValidatedTenancyScope> token); + + /** + * Transitional token generator, do not use outside of test code. + */ + struct TokenForTestingTag {}; + explicit ValidatedTenancyScope(BSONObj token, TokenForTestingTag); + + /** + * Backdoor API for use by FLE Query Analysis to setup a validated tenant without a security + * context. + */ + struct TrustedFLEQueryAnalysisTag {}; + explicit ValidatedTenancyScope(TenantId tenant, TrustedFLEQueryAnalysisTag) + : _tenantOrUser(std::move(tenant)) {} + +private: + // Preserve original token for serializing from MongoQ. + BSONObj _originalToken; + + stdx::variant<UserName, TenantId> _tenantOrUser; +}; + +} // namespace auth +} // namespace mongo diff --git a/src/mongo/db/validated_tenant_id_test.cpp b/src/mongo/db/auth/validated_tenancy_scope_test.cpp index b897a9cc927..f1942f757a6 100644 --- a/src/mongo/db/validated_tenant_id_test.cpp +++ b/src/mongo/db/auth/validated_tenancy_scope_test.cpp @@ -27,27 +27,17 @@ * it in the license file. */ -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kTest - -#include "mongo/rpc/op_msg_test.h" - #include "mongo/platform/basic.h" -#include "mongo/base/static_assert.h" -#include "mongo/bson/json.h" #include "mongo/db/auth/authorization_manager_impl.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/authorization_session_impl.h" #include "mongo/db/auth/authz_manager_external_state_mock.h" -#include "mongo/db/auth/security_token.h" -#include "mongo/db/jsobj.h" +#include "mongo/db/auth/security_token_gen.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/multitenancy_gen.h" #include "mongo/db/service_context_test_fixture.h" -#include "mongo/logv2/log.h" -#include "mongo/unittest/death_test.h" -#include "mongo/unittest/log_test.h" #include "mongo/unittest/unittest.h" -#include "mongo/util/hex.h" namespace mongo { @@ -70,8 +60,11 @@ public: } }; -class ValidatedTenantIdTestFixture : public mongo::ScopedGlobalServiceContextForTest, - public unittest::Test { +namespace auth { +namespace { + +class ValidatedTenancyScopeTestFixture : public mongo::ScopedGlobalServiceContextForTest, + public unittest::Test { protected: void setUp() final { auto authzManagerState = std::make_unique<AuthzManagerExternalStateMock>(); @@ -87,90 +80,98 @@ protected: constexpr auto authUserFieldName = auth::SecurityToken::kAuthenticatedUserFieldName; auto authUser = userName.toBSON(true /* serialize token */); ASSERT_EQ(authUser["tenant"_sd].type(), jstOID); - return auth::signSecurityToken(BSON(authUserFieldName << authUser)); + using VTS = auth::ValidatedTenancyScope; + return VTS(BSON(authUserFieldName << authUser), VTS::TokenForTestingTag{}) + .getOriginalToken(); } ServiceContext::UniqueClient client; }; -TEST_F(ValidatedTenantIdTestFixture, MultitenancySupportOffWithoutTenantOK) { +TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportOffWithoutTenantOK) { gMultitenancySupport = false; - auto body = fromjson("{$db: 'foo'}"); - OpMsg msg({body}); + auto body = BSON("$db" + << "foo"); - ValidatedTenantId validatedTenant(msg, *(client.get())); - ASSERT_TRUE(validatedTenant.tenantId() == boost::none); + auto validated = ValidatedTenancyScope::create(client.get(), body, {}); + ASSERT_TRUE(validated == boost::none); } -TEST_F(ValidatedTenantIdTestFixture, MultitenancySupportWithTenantOK) { +TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportWithTenantOK) { gMultitenancySupport = true; auto kOid = OID::gen(); auto body = BSON("ping" << 1 << "$tenant" << kOid); - OpMsg msg({body}); AuthorizationSessionImplTestHelper::grantUseTenant(*(client.get())); - ValidatedTenantId validatedTenant(msg, *(client.get())); - ASSERT(validatedTenant.tenantId() == TenantId(kOid)); + auto validated = ValidatedTenancyScope::create(client.get(), body, {}); + ASSERT_TRUE(validated != boost::none); + ASSERT_TRUE(validated->tenantId() == TenantId(kOid)); } -TEST_F(ValidatedTenantIdTestFixture, MultitenancySupportWithSecurityTokenOK) { +TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportWithSecurityTokenOK) { gMultitenancySupport = true; - const auto kTenantId = TenantId(OID::gen()); + const TenantId kTenantId(OID::gen()); auto body = BSON("ping" << 1); - auto token = makeSecurityToken(UserName("user", "admin", kTenantId)); - OpMsg msg({body, token}); - - ValidatedTenantId validatedTenant(msg, *(client.get())); - ASSERT(validatedTenant.tenantId() == kTenantId); + UserName user("user", "admin", kTenantId); + auto token = makeSecurityToken(user); + + auto validated = ValidatedTenancyScope::create(client.get(), body, token); + ASSERT_TRUE(validated != boost::none); + ASSERT_TRUE(validated->tenantId() == kTenantId); + ASSERT_TRUE(validated->hasAuthenticatedUser()); + ASSERT_TRUE(validated->authenticatedUser() == user); } -TEST_F(ValidatedTenantIdTestFixture, MultitenancySupportOffWithTenantNOK) { +TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportOffWithTenantNOK) { gMultitenancySupport = false; auto kOid = OID::gen(); auto body = BSON("ping" << 1 << "$tenant" << kOid); - OpMsg msg({body}); AuthorizationSessionImplTestHelper::grantUseTenant(*(client.get())); - ASSERT_THROWS_CODE( - ValidatedTenantId(msg, *(client.get())), DBException, ErrorCodes::InvalidOptions); + ASSERT_THROWS_CODE(ValidatedTenancyScope(client.get(), TenantId(kOid)), + DBException, + ErrorCodes::InvalidOptions); + ASSERT_TRUE(ValidatedTenancyScope::create(client.get(), body, {}) == boost::none); } -TEST_F(ValidatedTenantIdTestFixture, MultitenancySupportWithTenantNOK) { +TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportWithTenantNOK) { gMultitenancySupport = true; auto kOid = OID::gen(); auto body = BSON("ping" << 1 << "$tenant" << kOid); - OpMsg msg({body}); ASSERT_THROWS_CODE( - ValidatedTenantId(msg, *(client.get())), DBException, ErrorCodes::Unauthorized); + ValidatedTenancyScope(client.get(), TenantId(kOid)), DBException, ErrorCodes::Unauthorized); + ASSERT_THROWS_CODE(ValidatedTenancyScope::create(client.get(), body, {}), + DBException, + ErrorCodes::Unauthorized); } // TODO SERVER-66822: Re-enable this test case. -// TEST_F(ValidatedTenantIdTestFixture, MultitenancySupportWithoutTenantAndSecurityTokenNOK) { +// TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportWithoutTenantAndSecurityTokenNOK) { // gMultitenancySupport = true; - // auto body = BSON("ping" << 1); -// OpMsg msg({body}); - // AuthorizationSessionImplTestHelper::grantUseTenant(*(client.get())); -// ASSERT_THROWS_CODE( -// ValidatedTenantId(msg, *(client.get())), DBException, ErrorCodes::Unauthorized); +// ASSERT_THROWS_CODE(ValidatedTenancyScope::create(client.get(), body, {}), DBException, +// ErrorCodes::Unauthorized); // } -TEST_F(ValidatedTenantIdTestFixture, MultitenancySupportWithTenantAndSecurityTokenNOK) { +TEST_F(ValidatedTenancyScopeTestFixture, MultitenancySupportWithTenantAndSecurityTokenNOK) { gMultitenancySupport = true; auto kOid = OID::gen(); auto body = BSON("ping" << 1 << "$tenant" << kOid); - auto token = makeSecurityToken(UserName("user", "admin", TenantId(kOid))); - OpMsg msg({body, token}); + UserName user("user", "admin", TenantId(kOid)); + auto token = makeSecurityToken(user); AuthorizationSessionImplTestHelper::grantUseTenant(*(client.get())); - ASSERT_THROWS_CODE(ValidatedTenantId(msg, *(client.get())), DBException, 6545800); + ASSERT_THROWS_CODE( + ValidatedTenancyScope::create(client.get(), body, token), DBException, 6545800); } +} // namespace +} // namespace auth } // namespace mongo diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp index b102b7fd3e5..465bc8bb436 100644 --- a/src/mongo/db/catalog/collection_impl.cpp +++ b/src/mongo/db/catalog/collection_impl.cpp @@ -37,7 +37,6 @@ #include "mongo/bson/ordering.h" #include "mongo/bson/simple_bsonelement_comparator.h" #include "mongo/bson/simple_bsonobj_comparator.h" -#include "mongo/db/auth/security_token.h" #include "mongo/db/catalog/collection_catalog.h" #include "mongo/db/catalog/collection_options.h" #include "mongo/db/catalog/document_validation.h" diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index ab870a4818c..6ebe905b732 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -108,7 +108,8 @@ bool checkAuthorizationImplPreParse(OperationContext* opCtx, uassert(ErrorCodes::Unauthorized, str::stream() << "command " << command->getName() << " requires authentication", !command->requiresAuth() || authzSession->isAuthenticated() || - request.securityToken.nFields()); + (request.validatedTenancyScope && + request.validatedTenancyScope->hasAuthenticatedUser())); return false; } diff --git a/src/mongo/db/multitenancy.cpp b/src/mongo/db/multitenancy.cpp index cb7c88663f6..f12a4f7b55d 100644 --- a/src/mongo/db/multitenancy.cpp +++ b/src/mongo/db/multitenancy.cpp @@ -29,42 +29,17 @@ #include "mongo/db/multitenancy.h" -#include "mongo/db/auth/authorization_session.h" -#include "mongo/db/auth/security_token.h" -#include "mongo/db/multitenancy_gen.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/tenant_id.h" -#include "mongo/logv2/log.h" - -#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault - namespace mongo { -// 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::TenantId>>(); - boost::optional<TenantId> getActiveTenant(OperationContext* opCtx) { - auto token = auth::getSecurityToken(opCtx); - if (!token) { - return dollarTenantDecoration(opCtx); - } - - invariant(!dollarTenantDecoration(opCtx)); - return token->getAuthenticatedUser().getTenant(); -} - -void setDollarTenantOnOpCtx(OperationContext* opCtx, const OpMsg& opMsg) { - if (!opMsg.validatedTenant || !opMsg.validatedTenant->tenantId()) { - return; - } - - if (opMsg.securityToken.nFields() > 0) { - return; + if (auto token = auth::ValidatedTenancyScope::get(opCtx)) { + return token->tenantId(); } - dollarTenantDecoration(opCtx) = opMsg.validatedTenant->tenantId(); + return boost::none; } } // namespace mongo diff --git a/src/mongo/db/multitenancy.h b/src/mongo/db/multitenancy.h index 931074a33e0..b028286659d 100644 --- a/src/mongo/db/multitenancy.h +++ b/src/mongo/db/multitenancy.h @@ -41,9 +41,4 @@ namespace mongo { */ boost::optional<TenantId> getActiveTenant(OperationContext* opCtx); -/** - * Set the dollar TenantId on this OperationContext. - */ -void setDollarTenantOnOpCtx(OperationContext* opCtx, const OpMsg& opMsg); - } // namespace mongo diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index 64289c961cf..7b64af3ef83 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -41,7 +41,7 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/impersonation_session.h" #include "mongo/db/auth/ldap_cumulative_operation_stats.h" -#include "mongo/db/auth/security_token.h" +#include "mongo/db/auth/security_token_authentication_guard.h" #include "mongo/db/client.h" #include "mongo/db/command_can_run_here.h" #include "mongo/db/commands.h" @@ -655,13 +655,6 @@ private: _startOperationTime = getClientOperationTime(opCtx); rpc::readRequestMetadata(opCtx, request, command->requiresAuth()); - uassert(ErrorCodes::Unauthorized, - str::stream() << "Command " << command->getName() - << " is not supported in multitenancy mode", - command->allowedWithSecurityToken() || - auth::getSecurityToken(opCtx) == boost::none); - _tokenAuthorizationSessionGuard.emplace(opCtx); - _invocation = command->parse(opCtx, request); CommandInvocation::set(opCtx, _invocation); @@ -1413,6 +1406,14 @@ void ExecCommandDatabase::_initiateCommand() { Client* client = opCtx->getClient(); + if (auto scope = request.validatedTenancyScope; scope && scope->hasAuthenticatedUser()) { + uassert(ErrorCodes::Unauthorized, + str::stream() << "Command " << command->getName() + << " is not supported in multitenancy mode", + command->allowedWithSecurityToken()); + _tokenAuthorizationSessionGuard.emplace(opCtx, request.validatedTenancyScope.get()); + } + if (isHello()) { // Preload generic ClientMetadata ahead of our first hello request. After the first // request, metaElement should always be empty. @@ -1449,7 +1450,6 @@ void ExecCommandDatabase::_initiateCommand() { // Start authz contract tracking before we evaluate failpoints auto authzSession = AuthorizationSession::get(client); - authzSession->startContractTracking(); CommandHelpers::evaluateFailCommandFailPoint(opCtx, _invocation.get()); diff --git a/src/mongo/db/validated_tenant_id.cpp b/src/mongo/db/validated_tenant_id.cpp deleted file mode 100644 index 58f450529c7..00000000000 --- a/src/mongo/db/validated_tenant_id.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/** - * 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/db/validated_tenant_id.h" - -#include "mongo/db/auth/authorization_session.h" -#include "mongo/db/auth/security_token.h" -#include "mongo/db/multitenancy_gen.h" - -namespace mongo { - -ValidatedTenantId::ValidatedTenantId(const OpMsg& opMsg, Client& client) { - auto dollarTenantElem = opMsg.body["$tenant"]; - uassert(ErrorCodes::InvalidOptions, - "Multitenancy not enabled, cannot set $tenant in command body", - !dollarTenantElem || (dollarTenantElem && gMultitenancySupport)); - - if (!gMultitenancySupport) { - return; - } - - // TODO SERVER-66822: Re-enable this uassert. - // uassert(ErrorCodes::Unauthorized, - // "Multitenancy is enabled, $tenant id or securityToken is required.", - // dollarTenantElem || opMsg.securityToken.nFields() > 0); - - uassert(6545800, - str::stream() << "Cannot pass $tenant id if also passing securityToken, " - << opMsg.securityToken.toString() << ", " << dollarTenantElem.toString(), - !(dollarTenantElem && opMsg.securityToken.nFields() > 0)); - - if (dollarTenantElem) { - uassert(ErrorCodes::Unauthorized, - "'$tenant' may only be specified with the useTenant action type", - AuthorizationSession::get(client)->isAuthorizedForActionsOnResource( - ResourcePattern::forClusterResource(), ActionType::useTenant)); - _tenant = TenantId::parseFromBSON(dollarTenantElem); - return; - } - - if (opMsg.securityToken.nFields() > 0) { - auto verifiedToken = auth::verifySecurityToken(opMsg.securityToken); - _tenant = verifiedToken.getAuthenticatedUser().getTenant(); - } -} - -ValidatedTenantId::ValidatedTenantId(const DatabaseName& dbName) { - _tenant = dbName.tenantId(); -} - -} // namespace mongo diff --git a/src/mongo/executor/network_interface_tl.cpp b/src/mongo/executor/network_interface_tl.cpp index 9ee2ffac0b6..d919bca3c3b 100644 --- a/src/mongo/executor/network_interface_tl.cpp +++ b/src/mongo/executor/network_interface_tl.cpp @@ -33,7 +33,7 @@ #include <fmt/format.h> #include "mongo/config.h" -#include "mongo/db/auth/security_token.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/server_options.h" #include "mongo/db/wire_version.h" #include "mongo/executor/connection_pool_tl.h" @@ -71,9 +71,7 @@ Status appendMetadata(RemoteCommandRequestOnAny* request, if (!request->opCtx) return Status::OK(); - if (auto securityToken = auth::getSecurityToken(request->opCtx)) { - request->securityToken = securityToken->toBSON(); - } + request->validatedTenancyScope = auth::ValidatedTenancyScope::get(request->opCtx); return Status::OK(); } diff --git a/src/mongo/executor/remote_command_request.h b/src/mongo/executor/remote_command_request.h index 7d232a34756..cca27b03e71 100644 --- a/src/mongo/executor/remote_command_request.h +++ b/src/mongo/executor/remote_command_request.h @@ -33,6 +33,7 @@ #include <string> #include "mongo/base/error_codes.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/jsobj.h" #include "mongo/rpc/metadata.h" #include "mongo/transport/transport_layer.h" @@ -73,7 +74,7 @@ struct RemoteCommandRequestBase { std::string dbname; BSONObj metadata{rpc::makeEmptyMetadata()}; BSONObj cmdObj; - BSONObj securityToken; + boost::optional<auth::ValidatedTenancyScope> validatedTenancyScope; // OperationContext is added to each request to allow OP_Command metadata attachment access to // the Client object. The OperationContext is only accessed on the thread that calls diff --git a/src/mongo/logv2/logv2_test.cpp b/src/mongo/logv2/logv2_test.cpp index e9c2cbd76ee..0144f9a86b2 100644 --- a/src/mongo/logv2/logv2_test.cpp +++ b/src/mongo/logv2/logv2_test.cpp @@ -40,7 +40,7 @@ #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" #include "mongo/bson/oid.h" -#include "mongo/db/auth/security_token.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/tenant_id.h" #include "mongo/logv2/bson_formatter.h" #include "mongo/logv2/component_settings_filter.h" diff --git a/src/mongo/rpc/factory.cpp b/src/mongo/rpc/factory.cpp index 41384b0c678..7e001edc0af 100644 --- a/src/mongo/rpc/factory.cpp +++ b/src/mongo/rpc/factory.cpp @@ -62,11 +62,7 @@ OpMsgRequest opMsgRequestFromAnyProtocol(const Message& unownedMessage, Client* case mongo::dbMsg: return OpMsgRequest::parseOwned(unownedMessage, client); case mongo::dbQuery: { - auto request = opMsgRequestFromLegacyRequest(unownedMessage); - if (client) { - request.parseValidatedTenant(*client); - } - return request; + return opMsgRequestFromLegacyRequest(unownedMessage); } default: uasserted(ErrorCodes::UnsupportedFormat, diff --git a/src/mongo/rpc/metadata.cpp b/src/mongo/rpc/metadata.cpp index aa70c17a1af..b54a4474cc2 100644 --- a/src/mongo/rpc/metadata.cpp +++ b/src/mongo/rpc/metadata.cpp @@ -33,7 +33,7 @@ #include "mongo/client/read_preference.h" #include "mongo/db/auth/authorization_session.h" -#include "mongo/db/auth/security_token.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/dbmessage.h" #include "mongo/db/jsobj.h" #include "mongo/db/logical_time_validator.h" @@ -95,8 +95,7 @@ void readRequestMetadata(OperationContext* opCtx, const OpMsg& opMsg, bool cmdRe } readImpersonatedUserMetadata(impersonationElem, opCtx); - auth::setSecurityToken(opCtx, opMsg); - setDollarTenantOnOpCtx(opCtx, opMsg); + auth::ValidatedTenancyScope::set(opCtx, opMsg.validatedTenancyScope); // We check for "$client" but not "client" here, because currentOp can filter on "client" as // a top-level field. diff --git a/src/mongo/rpc/metadata/security_token_metadata_test.cpp b/src/mongo/rpc/metadata/security_token_metadata_test.cpp index 69fc73b89cd..98698572af1 100644 --- a/src/mongo/rpc/metadata/security_token_metadata_test.cpp +++ b/src/mongo/rpc/metadata/security_token_metadata_test.cpp @@ -31,8 +31,8 @@ #include "mongo/bson/oid.h" #include "mongo/crypto/sha256_block.h" -#include "mongo/db/auth/security_token.h" #include "mongo/db/auth/security_token_gen.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/client.h" #include "mongo/db/concurrency/locker_noop_service_context_test_fixture.h" #include "mongo/db/multitenancy_gen.h" @@ -51,7 +51,10 @@ BSONObj makeSecurityToken(const UserName& userName) { constexpr auto authUserFieldName = auth::SecurityToken::kAuthenticatedUserFieldName; auto authUser = userName.toBSON(true /* serialize token */); ASSERT_EQ(authUser["tenant"_sd].type(), jstOID); - return auth::signSecurityToken(BSON(authUserFieldName << authUser)); + using VTS = auth::ValidatedTenancyScope; + return VTS(BSON(authUserFieldName << authUser), VTS::TokenForTestingTag{}) + .getOriginalToken() + .getOwned(); } class SecurityTokenMetadataTest : public LockerNoopServiceContextTest { @@ -84,21 +87,19 @@ TEST_F(SecurityTokenMetadataTest, BasicSuccess) { auto msg = OpMsgBytes{0, kBodySection, kPingBody, kSecurityTokenSection, kTokenBody}.parse(); ASSERT_BSONOBJ_EQ(msg.body, kPingBody); ASSERT_EQ(msg.sequences.size(), 0u); - ASSERT_BSONOBJ_EQ(msg.securityToken, kTokenBody); + ASSERT_TRUE(msg.validatedTenancyScope != boost::none); + ASSERT_BSONOBJ_EQ(msg.validatedTenancyScope->getOriginalToken(), kTokenBody); + ASSERT_EQ(msg.validatedTenancyScope->tenantId(), kTenantId); auto opCtx = makeOperationContext(); - ASSERT(auth::getSecurityToken(opCtx.get()) == boost::none); + ASSERT(auth::ValidatedTenancyScope::get(opCtx.get()) == boost::none); - msg.parseValidatedTenant(*client.get()); - ASSERT(msg.validatedTenant); - ASSERT(msg.validatedTenant->tenantId()); - ASSERT_EQ(msg.validatedTenant->tenantId().get(), kTenantId); - - auth::setSecurityToken(opCtx.get(), msg); - auto token = auth::getSecurityToken(opCtx.get()); + auth::ValidatedTenancyScope::set(opCtx.get(), msg.validatedTenancyScope); + auto token = auth::ValidatedTenancyScope::get(opCtx.get()); ASSERT(token != boost::none); - auto authedUser = token->getAuthenticatedUser(); + ASSERT_TRUE(token->hasAuthenticatedUser()); + auto authedUser = token->authenticatedUser(); ASSERT_EQ(authedUser.getUser(), "user"); ASSERT_EQ(authedUser.getDB(), "admin"); ASSERT_TRUE(authedUser.getTenant() != boost::none); diff --git a/src/mongo/rpc/op_msg.cpp b/src/mongo/rpc/op_msg.cpp index 1f4a3ba51bf..be420d55e50 100644 --- a/src/mongo/rpc/op_msg.cpp +++ b/src/mongo/rpc/op_msg.cpp @@ -159,6 +159,7 @@ OpMsg OpMsg::parse(const Message& message, Client* client) try { // TODO some validation may make more sense in the IDL parser. I've tagged them with comments. bool haveBody = false; OpMsg msg; + BSONObj securityToken; while (!sectionsBuf.atEof()) { const auto sectionKind = sectionsBuf.read<Section>(); switch (sectionKind) { @@ -166,6 +167,10 @@ OpMsg OpMsg::parse(const Message& message, Client* client) try { uassert(40430, "Multiple body sections in message", !haveBody); haveBody = true; msg.body = sectionsBuf.read<Validated<BSONObj>>(); + + uassert(ErrorCodes::InvalidOptions, + "Multitenancy not enabled, cannot set $tenant in command body", + gMultitenancySupport || !msg.body["$tenant"_sd]); break; } @@ -197,7 +202,7 @@ OpMsg OpMsg::parse(const Message& message, Client* client) try { uassert(ErrorCodes::Unauthorized, "Unsupported Security Token provided", gMultitenancySupport); - msg.securityToken = sectionsBuf.read<Validated<BSONObj>>(); + securityToken = sectionsBuf.read<Validated<BSONObj>>(); break; } @@ -228,9 +233,11 @@ OpMsg OpMsg::parse(const Message& message, Client* client) try { *checksum == calculateChecksum(message)); } #endif - if (client != nullptr) { - msg.parseValidatedTenant(*client); + if (gMultitenancySupport) { + msg.validatedTenancyScope = + auth::ValidatedTenancyScope::create(client, msg.body, securityToken); } + return msg; } catch (const DBException& ex) { LOGV2_DEBUG( @@ -244,17 +251,16 @@ OpMsg OpMsg::parse(const Message& message, Client* client) try { throw; } -void OpMsg::parseValidatedTenant(Client& client) { - validatedTenant = ValidatedTenantId(*this, client); -} - namespace { void serializeHelper(const std::vector<OpMsg::DocumentSequence>& sequences, const BSONObj& body, - const BSONObj& securityToken, + const boost::optional<auth::ValidatedTenancyScope>& validatedTenancyScope, OpMsgBuilder* output) { - if (securityToken.nFields() > 0) { - output->setSecurityToken(securityToken); + if (validatedTenancyScope) { + auto securityToken = validatedTenancyScope->getOriginalToken(); + if (securityToken.nFields() > 0) { + output->setSecurityToken(securityToken); + } } for (auto&& seq : sequences) { auto docSeq = output->beginDocSequence(seq.name); @@ -268,13 +274,13 @@ void serializeHelper(const std::vector<OpMsg::DocumentSequence>& sequences, Message OpMsg::serialize() const { OpMsgBuilder builder; - serializeHelper(sequences, body, securityToken, &builder); + serializeHelper(sequences, body, validatedTenancyScope, &builder); return builder.finish(); } Message OpMsg::serializeWithoutSizeChecking() const { OpMsgBuilder builder; - serializeHelper(sequences, body, securityToken, &builder); + serializeHelper(sequences, body, validatedTenancyScope, &builder); return builder.finishWithoutSizeChecking(); } @@ -289,9 +295,6 @@ void OpMsg::shareOwnershipWith(const ConstSharedBuffer& buffer) { } } } - if (!securityToken.isOwned()) { - securityToken.shareOwnershipWith(buffer); - } } BSONObjBuilder OpMsgBuilder::beginSecurityToken() { diff --git a/src/mongo/rpc/op_msg.h b/src/mongo/rpc/op_msg.h index 4083d9fde13..243dbf9a344 100644 --- a/src/mongo/rpc/op_msg.h +++ b/src/mongo/rpc/op_msg.h @@ -36,8 +36,8 @@ #include "mongo/base/string_data.h" #include "mongo/bson/bsonobj.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/jsobj.h" -#include "mongo/db/validated_tenant_id.h" #include "mongo/rpc/message.h" namespace mongo { @@ -133,8 +133,6 @@ struct OpMsg { return msg; } - void parseValidatedTenant(Client& client); - Message serialize() const; /** @@ -161,10 +159,16 @@ struct OpMsg { } BSONObj body; - BSONObj securityToken; std::vector<DocumentSequence> sequences; - // The validated tenant id will not be serialized into the Message. - boost::optional<ValidatedTenantId> validatedTenant = boost::none; + + boost::optional<auth::ValidatedTenancyScope> validatedTenancyScope = boost::none; + + boost::optional<TenantId> getValidatedTenantId() const { + if (!validatedTenancyScope) { + return boost::none; + } + return validatedTenancyScope->tenantId(); + } }; /** diff --git a/src/mongo/rpc/op_msg_test.cpp b/src/mongo/rpc/op_msg_test.cpp index 4a7dfb17e43..ea07a42ea31 100644 --- a/src/mongo/rpc/op_msg_test.cpp +++ b/src/mongo/rpc/op_msg_test.cpp @@ -38,7 +38,8 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/authorization_session_impl.h" #include "mongo/db/auth/authz_manager_external_state_mock.h" -#include "mongo/db/auth/security_token.h" +#include "mongo/db/auth/security_token_gen.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/jsobj.h" #include "mongo/db/multitenancy_gen.h" #include "mongo/db/service_context_test_fixture.h" @@ -806,13 +807,15 @@ protected: constexpr auto authUserFieldName = auth::SecurityToken::kAuthenticatedUserFieldName; auto authUser = userName.toBSON(true /* serialize token */); ASSERT_EQ(authUser["tenant"_sd].type(), jstOID); - return auth::signSecurityToken(BSON(authUserFieldName << authUser)); + using VTS = auth::ValidatedTenancyScope; + return VTS(BSON(authUserFieldName << authUser), VTS::TokenForTestingTag{}) + .getOriginalToken(); } ServiceContext::UniqueClient client; }; -TEST_F(OpMsgWithAuth, ParseValidatedTenantIdFromSecurityToken) { +TEST_F(OpMsgWithAuth, ParseValidatedTenancyScopeFromSecurityToken) { gMultitenancySupport = true; const auto kTenantId = TenantId(OID::gen()); @@ -837,11 +840,11 @@ TEST_F(OpMsgWithAuth, ParseValidatedTenantIdFromSecurityToken) { auto body = BSON("ping" << 1); - ASSERT(msg.validatedTenant); - ASSERT_EQ(msg.validatedTenant->tenantId().get(), kTenantId); + ASSERT(msg.validatedTenancyScope); + ASSERT_EQ(msg.validatedTenancyScope->tenantId(), kTenantId); } -TEST_F(OpMsgWithAuth, ParseValidatedTenantIdFromDollarTenant) { +TEST_F(OpMsgWithAuth, ParseValidatedTenancyScopeFromDollarTenant) { gMultitenancySupport = true; AuthorizationSessionImplTestHelper::grantUseTenant(*(client.get())); @@ -862,11 +865,11 @@ TEST_F(OpMsgWithAuth, ParseValidatedTenantIdFromDollarTenant) { } .parse(client.get()); - ASSERT(msg.validatedTenant); - ASSERT_EQ(msg.validatedTenant->tenantId().get(), kTenantId); + ASSERT(msg.validatedTenancyScope); + ASSERT_EQ(msg.validatedTenancyScope->tenantId(), kTenantId); } -TEST_F(OpMsgWithAuth, ValidatedTenantIdShouldNotBeSerialized) { +TEST_F(OpMsgWithAuth, ValidatedTenancyScopeShouldNotBeSerialized) { gMultitenancySupport = true; AuthorizationSessionImplTestHelper::grantUseTenant(*(client.get())); @@ -885,7 +888,7 @@ TEST_F(OpMsgWithAuth, ValidatedTenantIdShouldNotBeSerialized) { }, }; auto msg = msgBytes.parse(client.get()); - ASSERT(msg.validatedTenant); + ASSERT(msg.validatedTenancyScope); auto serializedMsg = msg.serialize(); testSerializer(serializedMsg, diff --git a/src/mongo/s/commands/cluster_explain_cmd.cpp b/src/mongo/s/commands/cluster_explain_cmd.cpp index 6bd53032ff8..00ed9bfd521 100644 --- a/src/mongo/s/commands/cluster_explain_cmd.cpp +++ b/src/mongo/s/commands/cluster_explain_cmd.cpp @@ -190,7 +190,7 @@ std::unique_ptr<CommandInvocation> ClusterExplainCmd::parse(OperationContext* op str::stream() << "Explain failed due to unknown command: " << cmdName, explainedCommand); auto innerRequest = std::make_unique<OpMsgRequest>(OpMsg{explainedObj}); - innerRequest->validatedTenant = request.validatedTenant; + innerRequest->validatedTenancyScope = request.validatedTenancyScope; auto innerInvocation = explainedCommand->parseForExplain(opCtx, *innerRequest, verbosity); return std::make_unique<Invocation>( this, request, std::move(verbosity), std::move(innerRequest), std::move(innerInvocation)); diff --git a/src/mongo/scripting/mozjs/mongo.cpp b/src/mongo/scripting/mozjs/mongo.cpp index dd30e8e488e..dffc1163e47 100644 --- a/src/mongo/scripting/mozjs/mongo.cpp +++ b/src/mongo/scripting/mozjs/mongo.cpp @@ -257,13 +257,16 @@ void doRunCommand(JSContext* cx, JS::CallArgs args, MakeRequest makeRequest) { auto arg = ValueWriter(cx, args.get(1)).toBSON(); auto request = makeRequest(database, arg); - if (auto token = args.get(3); token.isObject()) { - request.securityToken = ValueWriter(cx, token).toBSON(); + if (auto tokenArg = args.get(3); tokenArg.isObject()) { + using VTS = auth::ValidatedTenancyScope; + if (auto token = ValueWriter(cx, tokenArg).toBSON(); token.nFields() > 0) { + request.validatedTenancyScope = VTS(token, VTS::InitTag::kInitForShell); + } } else { uassert(ErrorCodes::BadValue, str::stream() << "The token parameter to " << Params::kCommandName << " must be an object", - token.isUndefined()); + tokenArg.isUndefined()); } const auto& conn = getConnectionRef(args); diff --git a/src/mongo/shell/shell_utils.cpp b/src/mongo/shell/shell_utils.cpp index 3c1f4a843f5..748d5a1b80c 100644 --- a/src/mongo/shell/shell_utils.cpp +++ b/src/mongo/shell/shell_utils.cpp @@ -48,7 +48,8 @@ #include "mongo/base/shim.h" #include "mongo/client/dbclient_base.h" #include "mongo/client/replica_set_monitor.h" -#include "mongo/db/auth/security_token.h" +#include "mongo/db/auth/security_token_gen.h" +#include "mongo/db/auth/validated_tenancy_scope.h" #include "mongo/db/hasher.h" #include "mongo/logv2/log.h" #include "mongo/platform/decimal128.h" @@ -436,7 +437,9 @@ BSONObj _createSecurityToken(const BSONObj& args, void* data) { constexpr auto authUserFieldName = auth::SecurityToken::kAuthenticatedUserFieldName; auto authUser = args.firstElement().Obj(); - return BSON("" << auth::signSecurityToken(BSON(authUserFieldName << authUser))); + using VTS = auth::ValidatedTenancyScope; + auto token = VTS(BSON(authUserFieldName << authUser), VTS::TokenForTestingTag{}); + return BSON("" << token.getOriginalToken()); } BSONObj replMonitorStats(const BSONObj& a, void* data) { |