diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2021-10-13 21:04:55 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-12-07 00:23:48 +0000 |
commit | d7845457fd30cd1798f70444c2a66d725ab361b4 (patch) | |
tree | 335bd948371be032d209a26c564a140b4ea215a4 /src | |
parent | 4a915072ab5279480305a6023db6671e3f32cfd0 (diff) | |
download | mongo-d7845457fd30cd1798f70444c2a66d725ab361b4.tar.gz |
SERVER-61615 Parse authenticated user from security token and add to authorization session
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/auth/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session.h | 11 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session_impl.cpp | 69 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session_impl.h | 8 | ||||
-rw-r--r-- | src/mongo/db/auth/security_token.cpp | 40 | ||||
-rw-r--r-- | src/mongo/db/auth/security_token.h | 11 | ||||
-rw-r--r-- | src/mongo/db/auth/security_token.idl | 12 | ||||
-rw-r--r-- | src/mongo/db/commands.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 25 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.idl | 6 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands_common.cpp | 13 | ||||
-rw-r--r-- | src/mongo/db/multitenancy.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/service_entry_point_common.cpp | 4 | ||||
-rw-r--r-- | src/mongo/embedded/embedded_auth_session.cpp | 8 | ||||
-rw-r--r-- | src/mongo/rpc/metadata/security_token_metadata_test.cpp | 28 | ||||
-rw-r--r-- | src/mongo/shell/shell_utils.cpp | 27 |
17 files changed, 247 insertions, 22 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 844b1b76db7..3ef6beaf34d 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -14,6 +14,7 @@ env.Library( '$BUILD_DIR/mongo/base', ], LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/db/auth/auth', '$BUILD_DIR/mongo/db/multitenancy_params', '$BUILD_DIR/mongo/db/server_feature_flags', '$BUILD_DIR/mongo/db/service_context', diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index 3631c8049b4..b23fbfcf171 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -169,6 +169,9 @@ public: // Gets an iterator over the roles of all authenticated users stored in this manager. virtual RoleNameIterator getAuthenticatedRoleNames() = 0; + // Removes all authenticated principals while in kSecurityToken authentication mode. + virtual void logoutSecurityTokenUser(Client* client) = 0; + // Removes any authenticated principals and revokes any privileges that were granted via those // principals. This function modifies state. Synchronizes with the Client lock. virtual void logoutAllDatabases(Client* client, StringData reason) = 0; @@ -178,6 +181,14 @@ public: // modifies state. Synchronizes with the Client lock. virtual void logoutDatabase(Client* client, StringData dbname, StringData reason) = 0; + // How the active session is authenticated. + enum class AuthenticationMode { + kNone, // Not authenticated. + kConnection, // For the duration of the connection, or until logged out. + kSecurityToken, // By operation scoped security token. + }; + virtual AuthenticationMode getAuthenticationMode() const = 0; + // Adds the internalSecurity user to the set of authenticated users. // Used to grant internal threads full access. Takes in the Client // as a parameter so it can take out a lock on the client. diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp index 01ec890270d..be2ad897b27 100644 --- a/src/mongo/db/auth/authorization_session_impl.cpp +++ b/src/mongo/db/auth/authorization_session_impl.cpp @@ -44,6 +44,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/bson/dotted_path_support.h" #include "mongo/db/client.h" #include "mongo/db/namespace_string.h" @@ -101,6 +102,20 @@ AuthorizationManager& AuthorizationSessionImpl::getAuthorizationManager() { void AuthorizationSessionImpl::startRequest(OperationContext* opCtx) { _externalState->startRequest(opCtx); _refreshUserInfoAsNeeded(opCtx); + if (_authenticationMode == AuthenticationMode::kSecurityToken) { + // Previously authenticated using SecurityToken, + // clear that user and reset to unauthenticated state. + invariant(_authenticatedUsers.count() <= 1); + if (auto users = std::exchange(_authenticatedUsers, {}); users.count()) { + LOGV2_DEBUG(6161507, + 3, + "security token based user still authenticated at start of request, " + "clearing from authentication state", + "user"_attr = users.getNames().get().toBSON(true /* encode tenant */)); + _buildAuthenticatedRolesVector(); + } + _authenticationMode = AuthenticationMode::kNone; + } } void AuthorizationSessionImpl::startContractTracking() { @@ -177,6 +192,19 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, } stdx::lock_guard<Client> lk(*opCtx->getClient()); + + if (auto token = auth::getSecurityToken(opCtx)) { + 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); + _authenticationMode = AuthenticationMode::kSecurityToken; + } else { + _authenticationMode = AuthenticationMode::kConnection; + } _authenticatedUsers.add(std::move(user)); // If there are any users and roles in the impersonation data, clear it out. @@ -214,9 +242,35 @@ User* AuthorizationSessionImpl::getSingleUser() { return lookupUser(userName); } +void AuthorizationSessionImpl::logoutSecurityTokenUser(Client* client) { + stdx::lock_guard<Client> lk(*client); + + uassert(6161503, + "Attempted to deauth a security token user while using standard login", + _authenticationMode != AuthenticationMode::kConnection); + + auto users = std::exchange(_authenticatedUsers, {}); + invariant(users.count() <= 1); + if (users.count() == 1) { + LOGV2_DEBUG(6161506, + 5, + "security token based user explicitly logged out", + "user"_attr = users.getNames().get().toBSON(true /* encode tenant */)); + } + + // Explicitly skip auditing the logout event, + // security tokens don't represent a permanent login. + clearImpersonatedUserData(); + _buildAuthenticatedRolesVector(); +} + void AuthorizationSessionImpl::logoutAllDatabases(Client* client, StringData reason) { stdx::lock_guard<Client> lk(*client); + uassert(6161504, + "May not log out while using a security token based authentication", + _authenticationMode != AuthenticationMode::kSecurityToken); + auto users = std::exchange(_authenticatedUsers, {}); if (users.count() == 0) { return; @@ -234,6 +288,10 @@ void AuthorizationSessionImpl::logoutDatabase(Client* client, StringData reason) { stdx::lock_guard<Client> lk(*client); + uassert(6161505, + "May not log out while using a security token based authentication", + _authenticationMode != AuthenticationMode::kSecurityToken); + // Emit logout audit event and then remove all users logged into dbname. UserSet updatedUsers(_authenticatedUsers); updatedUsers.removeByDBName(dbname); @@ -410,7 +468,7 @@ bool AuthorizationSessionImpl::isAuthorizedForActionsOnNamespace(const Namespace return isAuthorizedForPrivilege(Privilege(ResourcePattern::forExactNamespace(ns), actions)); } -static const int resourceSearchListCapacity = 7; +constexpr int resourceSearchListCapacity = 7; /** * Builds from "target" an exhaustive list of all ResourcePatterns that match "target". * @@ -664,14 +722,17 @@ void AuthorizationSessionImpl::_refreshUserInfoAsNeeded(OperationContext* opCtx) void AuthorizationSessionImpl::_buildAuthenticatedRolesVector() { _authenticatedRoleNames.clear(); - for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); - ++it) { - RoleNameIterator roles = (*it)->getIndirectRoles(); + for (const auto& userHandle : _authenticatedUsers) { + RoleNameIterator roles = userHandle->getIndirectRoles(); while (roles.more()) { RoleName roleName = roles.next(); _authenticatedRoleNames.push_back(RoleName(roleName.getRole(), roleName.getDB())); } } + + if (_authenticatedUsers.count() == 0) { + _authenticationMode = AuthenticationMode::kNone; + } } bool AuthorizationSessionImpl::isAuthorizedForAnyActionOnAnyResourceInDB(StringData db) { diff --git a/src/mongo/db/auth/authorization_session_impl.h b/src/mongo/db/auth/authorization_session_impl.h index 71c07ab949a..2ec581af5c2 100644 --- a/src/mongo/db/auth/authorization_session_impl.h +++ b/src/mongo/db/auth/authorization_session_impl.h @@ -91,9 +91,14 @@ public: RoleNameIterator getAuthenticatedRoleNames() override; + void logoutSecurityTokenUser(Client* client) override; void logoutAllDatabases(Client* client, StringData reason) override; void logoutDatabase(Client* client, StringData dbname, StringData reason) override; + AuthenticationMode getAuthenticationMode() const override { + return _authenticationMode; + } + void grantInternalAuthorization(Client* client) override; void grantInternalAuthorization(OperationContext* opCtx) override; @@ -161,6 +166,9 @@ protected: // All Users who have been authenticated on this connection. UserSet _authenticatedUsers; + // What authentication mode we're currently operating in. + AuthenticationMode _authenticationMode = AuthenticationMode::kNone; + // The roles of the authenticated users. This vector is generated when the authenticated // users set is changed. std::vector<RoleName> _authenticatedRoleNames; diff --git a/src/mongo/db/auth/security_token.cpp b/src/mongo/db/auth/security_token.cpp index 5ebdfeda60d..b38fe1e0b9b 100644 --- a/src/mongo/db/auth/security_token.cpp +++ b/src/mongo/db/auth/security_token.cpp @@ -35,6 +35,7 @@ #include "mongo/base/init.h" #include "mongo/bson/oid.h" +#include "mongo/db/auth/authorization_session.h" #include "mongo/db/multitenancy_gen.h" #include "mongo/db/server_feature_flags_gen.h" #include "mongo/logv2/log.h" @@ -54,15 +55,42 @@ MONGO_INITIALIZER(SecurityTokenOptionValidate)(InitializerContext*) { auto* opCtx = client ? client->getOperationContext() : nullptr; auto token = getSecurityToken(opCtx); if (token) { - return token->getTenant(); + return token->getAuthenticatedUser().getTenant(); } else { return boost::none; } }); } } + +// Placeholder algorithm. +void validateSecurityTokenSignature(BSONObj authUser, const SHA256Block& sig) { + auto computed = + SHA256Block::computeHash({ConstDataRange(authUser.objdata(), authUser.objsize())}); + uassert(ErrorCodes::Unauthorized, "Token signature invalid", computed == sig); +} } // 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); + } +} + void readSecurityTokenMetadata(OperationContext* opCtx, BSONObj securityToken) try { if (securityToken.nFields() == 0) { return; @@ -70,7 +98,15 @@ void readSecurityTokenMetadata(OperationContext* opCtx, BSONObj securityToken) t uassert(ErrorCodes::BadValue, "Multitenancy not enabled", gMultitenancySupport); - securityTokenDecoration(opCtx) = SecurityToken::parse({"Security Token"}, securityToken); + auto token = SecurityToken::parse({"Security Token"}, securityToken); + auto authenticatedUser = token.getAuthenticatedUser(); + uassert(ErrorCodes::BadValue, + "Security token authenticated user requires a valid Tenant ID", + authenticatedUser.getTenant()); + + validateSecurityTokenSignature(securityToken["authenticatedUser"].Obj(), token.getSig()); + + securityTokenDecoration(opCtx) = std::move(token); LOGV2_DEBUG(5838100, 4, "Accepted security token", "token"_attr = securityToken); } catch (const DBException& ex) { diff --git a/src/mongo/db/auth/security_token.h b/src/mongo/db/auth/security_token.h index 52e57e2625e..7a30424e78b 100644 --- a/src/mongo/db/auth/security_token.h +++ b/src/mongo/db/auth/security_token.h @@ -33,11 +33,22 @@ #include "mongo/bson/bsonobj.h" #include "mongo/db/auth/security_token_gen.h" +#include "mongo/db/client.h" #include "mongo/db/operation_context.h" namespace mongo { namespace auth { +class SecurityTokenAuthenticationGuard { +public: + SecurityTokenAuthenticationGuard() = delete; + SecurityTokenAuthenticationGuard(OperationContext* opCtx); + ~SecurityTokenAuthenticationGuard(); + +private: + Client* _client; +}; + /** * Parse any SecurityToken from the OpMsg and place it as a decoration * on OperationContext diff --git a/src/mongo/db/auth/security_token.idl b/src/mongo/db/auth/security_token.idl index 03d401ecf65..62076ac8b31 100644 --- a/src/mongo/db/auth/security_token.idl +++ b/src/mongo/db/auth/security_token.idl @@ -30,6 +30,8 @@ global: cpp_namespace: "mongo::auth" imports: + - "mongo/db/auth/auth_types.idl" + - "mongo/crypto/sha256_block.idl" - "mongo/idl/basic_types.idl" structs: @@ -37,7 +39,11 @@ structs: description: "Security Token as passed in OP_MSG" strict: true fields: - tenant: - description: Tenant identifier - type: objectid + authenticatedUser: + description: Authenticated user for which this token grants authorizations + type: UserName + sig: + # WIP This is temporarily a SHA256 hash of the authenticatedUser BSON object. + description: Validated signature on this security token + type: sha256Block diff --git a/src/mongo/db/commands.cpp b/src/mongo/db/commands.cpp index 851c0f98bef..23967c70d60 100644 --- a/src/mongo/db/commands.cpp +++ b/src/mongo/db/commands.cpp @@ -109,7 +109,8 @@ bool checkAuthorizationImplPreParse(OperationContext* opCtx, return false; // Still can't decide on auth because of the localhost bypass. uassert(ErrorCodes::Unauthorized, str::stream() << "command " << command->getName() << " requires authentication", - !command->requiresAuth() || authzSession->isAuthenticated()); + !command->requiresAuth() || authzSession->isAuthenticated() || + request.securityToken.nFields()); return false; } diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index b10e156fa71..c40622a2f36 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -161,6 +161,7 @@ env.Library( '$BUILD_DIR/mongo/db/logical_session_id', '$BUILD_DIR/mongo/db/logical_session_id_helpers', '$BUILD_DIR/mongo/db/multitenancy', + '$BUILD_DIR/mongo/db/multitenancy_params', '$BUILD_DIR/mongo/db/pipeline/change_stream_pipeline', '$BUILD_DIR/mongo/db/pipeline/pipeline', '$BUILD_DIR/mongo/db/repl/isself', diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 199b127126c..178bd5c27f5 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -206,6 +206,16 @@ Status checkOkayToGrantPrivilegesToRole(const RoleName& role, const PrivilegeVec return Status::OK(); } +// Temporary placeholder pending availability of NamespaceWithTenant. +NamespaceString getNamespaceWithTenant(const NamespaceString& nss, + const boost::optional<OID>& tenant) { + if (tenant) { + return NamespaceString(str::stream() << tenant.get() << '_' << nss.db(), nss.coll()); + } else { + return nss; + } +} + /** * Finds all documents matching "query" in "collectionName". For each document returned, * calls the function resultProcessor on it. @@ -439,9 +449,11 @@ Status removeRoleDocuments(OperationContext* opCtx, /** * Creates the given user object in the given database. */ -Status insertPrivilegeDocument(OperationContext* opCtx, const BSONObj& userObj) { - Status status = - insertAuthzDocument(opCtx, AuthorizationManager::usersCollectionNamespace, userObj); +Status insertPrivilegeDocument(OperationContext* opCtx, + const BSONObj& userObj, + const boost::optional<OID>& tenant = boost::none) { + auto nss = getNamespaceWithTenant(AuthorizationManager::usersCollectionNamespace, tenant); + Status status = insertAuthzDocument(opCtx, nss, userObj); if (status.isOK()) { return status; } @@ -1005,7 +1017,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); + UserName userName(cmd.getCommandParameter(), dbname, cmd.getTenantOverride()); uassert(ErrorCodes::BadValue, "Must provide a 'pwd' field for all user documents, except those" @@ -1042,8 +1054,7 @@ void CmdUMCTyped<CreateUserCommand>::Invocation::typedRun(OperationContext* opCt BSONObjBuilder userObjBuilder; userObjBuilder.append("_id", userName.getUnambiguousName()); UUID::gen().appendToBuilder(&userObjBuilder, AuthorizationManager::USERID_FIELD_NAME); - userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, userName.getUser()); - userObjBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, userName.getDB()); + userName.appendToBSON(&userObjBuilder); auto* serviceContext = opCtx->getClient()->getServiceContext(); auto* authzManager = AuthorizationManager::get(serviceContext); @@ -1089,7 +1100,7 @@ void CmdUMCTyped<CreateUserCommand>::Invocation::typedRun(OperationContext* opCt authRestrictionsArray); // Must invalidate even on bad status - auto status = insertPrivilegeDocument(opCtx, userObj); + auto status = insertPrivilegeDocument(opCtx, userObj, userName.getTenant()); authzManager->invalidateUserByName(opCtx, userName); uassertStatusOK(status); } diff --git a/src/mongo/db/commands/user_management_commands.idl b/src/mongo/db/commands/user_management_commands.idl index 86911ba5528..720e8663ab5 100644 --- a/src/mongo/db/commands/user_management_commands.idl +++ b/src/mongo/db/commands/user_management_commands.idl @@ -127,6 +127,12 @@ commands: description: "List of valid authentication mechanisms for the user" type: array<string> optional: true + "$tenant": + # Only available with enableTestCommands and multitenancySupport + description: "Associate this user with a specific tenant" + type: objectid + cpp_name: tenantOverride + optional: true updateUser: description: "Modify a user" diff --git a/src/mongo/db/commands/user_management_commands_common.cpp b/src/mongo/db/commands/user_management_commands_common.cpp index bc89b9efed2..e6c4539a724 100644 --- a/src/mongo/db/commands/user_management_commands_common.cpp +++ b/src/mongo/db/commands/user_management_commands_common.cpp @@ -41,11 +41,14 @@ #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/resource_pattern.h" +#include "mongo/db/auth/security_token_gen.h" #include "mongo/db/auth/user.h" #include "mongo/db/auth/user_management_commands_parser.h" +#include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/commands/user_management_commands_gen.h" #include "mongo/db/jsobj.h" #include "mongo/db/multitenancy.h" +#include "mongo/db/multitenancy_gen.h" #include "mongo/util/sequence_util.h" #include "mongo/util/str.h" @@ -188,6 +191,16 @@ 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/multitenancy.cpp b/src/mongo/db/multitenancy.cpp index 2db3089e091..fbe5d6e651a 100644 --- a/src/mongo/db/multitenancy.cpp +++ b/src/mongo/db/multitenancy.cpp @@ -44,7 +44,7 @@ boost::optional<OID> getActiveTenant(OperationContext* opCtx) { return boost::none; } - return token->getTenant(); + return token->getAuthenticatedUser().getTenant(); } } // namespace mongo diff --git a/src/mongo/db/service_entry_point_common.cpp b/src/mongo/db/service_entry_point_common.cpp index 5acaa1e2614..8fa9892aacf 100644 --- a/src/mongo/db/service_entry_point_common.cpp +++ b/src/mongo/db/service_entry_point_common.cpp @@ -44,6 +44,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/client.h" #include "mongo/db/command_can_run_here.h" #include "mongo/db/commands.h" @@ -634,6 +635,7 @@ private: OperationSessionInfoFromClient _sessionOptions; boost::optional<ResourceConsumption::ScopedMetricsCollector> _scopedMetrics; boost::optional<ImpersonationSessionGuard> _impersonationSessionGuard; + boost::optional<auth::SecurityTokenAuthenticationGuard> _tokenAuthorizationSessionGuard; std::unique_ptr<PolymorphicScoped> _scoped; bool _refreshedDatabase = false; bool _refreshedCollection = false; @@ -1288,6 +1290,8 @@ void ExecCommandDatabase::_initiateCommand() { }); rpc::readRequestMetadata(opCtx, request, command->requiresAuth()); + _tokenAuthorizationSessionGuard.emplace(opCtx); + rpc::TrackingMetadata::get(opCtx).initWithOperName(command->getName()); auto const replCoord = repl::ReplicationCoordinator::get(opCtx); diff --git a/src/mongo/embedded/embedded_auth_session.cpp b/src/mongo/embedded/embedded_auth_session.cpp index c26f16c3781..9b7f07c1658 100644 --- a/src/mongo/embedded/embedded_auth_session.cpp +++ b/src/mongo/embedded/embedded_auth_session.cpp @@ -215,6 +215,14 @@ public: // Do nothing } + AuthenticationMode getAuthenticationMode() const override { + return AuthenticationMode::kNone; + } + + void logoutSecurityTokenUser(Client* client) override { + UASSERT_NOT_IMPLEMENTED; + } + protected: std::tuple<std::vector<UserName>*, std::vector<RoleName>*> _getImpersonations() override { UASSERT_NOT_IMPLEMENTED; diff --git a/src/mongo/rpc/metadata/security_token_metadata_test.cpp b/src/mongo/rpc/metadata/security_token_metadata_test.cpp index 00c664c4fc7..1319dae55c7 100644 --- a/src/mongo/rpc/metadata/security_token_metadata_test.cpp +++ b/src/mongo/rpc/metadata/security_token_metadata_test.cpp @@ -30,6 +30,7 @@ #include "mongo/platform/basic.h" #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/client.h" @@ -43,14 +44,28 @@ namespace rpc { namespace test { namespace { +constexpr auto kAuthenticatedUserFieldName = "authenticatedUser"_sd; constexpr auto kPingFieldName = "ping"_sd; -constexpr auto kTenantFieldName = "tenant"_sd; +constexpr auto kSigFieldName = "sig"_sd; + +BSONObj makeSecurityToken(const UserName& userName) { + auto authUser = userName.toBSON(true /* serialize token */); + ASSERT_EQ(authUser["tenant"_sd].type(), jstOID); + + BSONObjBuilder token; + token.append(kAuthenticatedUserFieldName, authUser); + + auto block = SHA256Block::computeHash({ConstDataRange(authUser.objdata(), authUser.objsize())}); + token.appendBinData(kSigFieldName, block.size(), BinDataGeneral, block.data()); + + return token.obj(); +} class SecurityTokenMetadataTest : public LockerNoopServiceContextTest {}; TEST_F(SecurityTokenMetadataTest, SecurityTokenNotAccepted) { const auto kPingBody = BSON(kPingFieldName << 1); - const auto kTokenBody = BSON(kTenantFieldName << OID::gen()); + const auto kTokenBody = makeSecurityToken(UserName("user", "admin", OID::gen())); gMultitenancySupport = false; auto msgBytes = OpMsgBytes{0, kBodySection, kPingBody, kSecurityTokenSection, kTokenBody}; @@ -63,7 +78,7 @@ TEST_F(SecurityTokenMetadataTest, SecurityTokenNotAccepted) { TEST_F(SecurityTokenMetadataTest, BasicSuccess) { const auto kOid = OID::gen(); const auto kPingBody = BSON(kPingFieldName << 1); - const auto kTokenBody = BSON(kTenantFieldName << kOid); + const auto kTokenBody = makeSecurityToken(UserName("user", "admin", kOid)); gMultitenancySupport = true; auto msg = OpMsgBytes{0, kBodySection, kPingBody, kSecurityTokenSection, kTokenBody}.parse(); @@ -77,7 +92,12 @@ TEST_F(SecurityTokenMetadataTest, BasicSuccess) { auth::readSecurityTokenMetadata(opCtx.get(), msg.securityToken); auto token = auth::getSecurityToken(opCtx.get()); ASSERT(token != boost::none); - ASSERT_EQ(token->getTenant(), kOid); + + auto authedUser = token->getAuthenticatedUser(); + ASSERT_EQ(authedUser.getUser(), "user"); + ASSERT_EQ(authedUser.getDB(), "admin"); + ASSERT_TRUE(authedUser.getTenant() != boost::none); + ASSERT_EQ(authedUser.getTenant().get(), kOid); } } // namespace diff --git a/src/mongo/shell/shell_utils.cpp b/src/mongo/shell/shell_utils.cpp index aec44e0c723..79f89b28c57 100644 --- a/src/mongo/shell/shell_utils.cpp +++ b/src/mongo/shell/shell_utils.cpp @@ -415,6 +415,32 @@ BSONObj convertShardKeyToHashed(const BSONObj& a, void* data) { return BSON("" << key); } +/** + * Generate a security token suitable for passing in an OpMsg payload token field. + * + * @param user object - { user: 'name', db: 'dbname', tenant: OID } + * @return object - { authenticatedUser: {...user object...}, sig: BinDataGeneral(Signature) } + */ +BSONObj _createSecurityToken(const BSONObj& args, void* data) { + uassert(6161500, + "_createSecurityToken requires a single object argument", + (args.nFields() == 1) && (args.firstElement().type() == Object)); + auto authUser = args.firstElement().Obj(); + + // Temporary algorithm. + auto digest = + SHA256Block::computeHash({ConstDataRange(authUser.objdata(), authUser.objsize())}); + + BSONObjBuilder ret; + { + BSONObjBuilder token(ret.subobjStart(""_sd)); + token.append("authenticatedUser"_sd, authUser); + token.appendBinData("sig"_sd, digest.size(), BinDataGeneral, digest.data()); + token.doneFast(); + } + return ret.obj(); +} + BSONObj replMonitorStats(const BSONObj& a, void* data) { uassert(17134, "replMonitorStats requires a single string argument (the ReplSet name)", @@ -478,6 +504,7 @@ BSONObj numberDecimalsEqual(const BSONObj& input, void*) { void installShellUtils(Scope& scope) { scope.injectNative("getMemInfo", JSGetMemInfo); + scope.injectNative("_createSecurityToken", _createSecurityToken); scope.injectNative("_replMonitorStats", replMonitorStats); scope.injectNative("_srand", JSSrand); scope.injectNative("_rand", JSRand); |