/** * Copyright (C) 2018 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * 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 GNU Affero General 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 #include #include "mongo/base/string_data.h" #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/commands.h" #include "mongo/db/commands/test_commands_enabled.h" #include "mongo/db/operation_context.h" #include "mongo/db/service_context.h" namespace mongo { class User; /** * The set of attributes SASL mechanisms may possess. * Different SASL mechanisms provide different types of assurances to clients and servers. * These SecurityProperties are attached to mechanism types to allow reasoning about them. * * Descriptions of individual properties assumed while using a mechanism with the property. */ enum class SecurityProperty : size_t { kMutualAuth, // Both clients and servers are assured of the other's identity. kNoPlainText, // Messages sent across the wire don't include the plaintext password. kLastSecurityProperty, // Do not use. An enum value for internal bookkeeping. }; /** Allows a set of SecurityProperties to be defined in an initializer list. */ using SecurityPropertyList = std::initializer_list; /** A set of SecurityProperties which may be exhibited by a SASL mechanism. */ class SecurityPropertySet { public: explicit SecurityPropertySet(SecurityPropertyList props) { for (auto prop : props) { _set.set(static_cast(prop)); } } /** Returns true if the set contains all of the requested properties. */ bool hasAllProperties(SecurityPropertySet propertySet) const { for (size_t i = 0; i < propertySet._set.size(); ++i) { if (propertySet._set[i] && !_set[i]) { return false; } } return true; } private: std::bitset(SecurityProperty::kLastSecurityProperty)> _set; }; /** * SASL server mechanisms are made by a corresponding factory. * Mechanisms have properties. We wish to be able to manipulate these properties both at runtime * and compile-time. These properties should apply to, and be accessible from, mechanisms and * factories. Client mechanisms/factories should be able to use the same property definitions. * This allows safe mechanism negotiation. * * The properties are set by creating a Policy class, and making mechanisms inherit from a helper * derived from the class. Factories are derived from a helper which uses the mechanism. */ /** Exposes properties of the SASL mechanism. */ class SaslServerCommonBase { public: virtual ~SaslServerCommonBase() = default; virtual StringData mechanismName() const = 0; virtual SecurityPropertySet properties() const = 0; }; /** * Base class shared by all server-side SASL mechanisms. */ class ServerMechanismBase : public SaslServerCommonBase { public: explicit ServerMechanismBase(std::string authenticationDatabase) : _authenticationDatabase(std::move(authenticationDatabase)) {} virtual ~ServerMechanismBase() = default; /** * Returns the principal name which this mechanism is performing authentication for. * This name is provided by the client, in the SASL messages they send the server. * This value may not be available until after some number of conversation steps have * occurred. * * This method is virtual so more complex implementations can obtain this value from a * non-member. */ virtual StringData getPrincipalName() const { return _principalName; } /** * Standard method in mongodb for determining if "authenticatedUser" may act as "requestedUser." * * The standard rule in MongoDB is simple. The authenticated user name must be the same as the * requested user name. */ virtual bool isAuthorizedToActAs(StringData requestedUser, StringData authenticatedUser) { return requestedUser == authenticatedUser; } /** * Performs a single step of a SASL exchange. Takes an input provided by a client, * and either returns an error, or a response to be sent back. */ StatusWith step(OperationContext* opCtx, StringData input) { auto result = stepImpl(opCtx, input); if (result.isOK()) { bool isDone; std::string responseMessage; std::tie(isDone, responseMessage) = result.getValue(); _done = isDone; return responseMessage; } return result.getStatus(); } /** * Returns true if the conversation has completed. * Note that this does not mean authentication succeeded! * An error may have occurred. */ bool isDone() const { return _done; } /** Returns which database contains the user which authentication is being performed against. */ StringData getAuthenticationDatabase() const { if (getTestCommandsEnabled() && _authenticationDatabase == "admin" && getPrincipalName() == internalSecurity.user->getName().getUser()) { // Allows authenticating as the internal user against the admin database. This is to // support the auth passthrough test framework on mongos (since you can't use the local // database on a mongos, so you can't auth as the internal user without this). return internalSecurity.user->getName().getDB(); } else { return _authenticationDatabase; } } protected: /** * Mechanism provided step implementation. * On failure, returns a non-OK status. On success, returns a tuple consisting of * a boolean indicating whether the mechanism has completed, and the string * containing the server's response to the client. */ virtual StatusWith> stepImpl(OperationContext* opCtx, StringData input) = 0; bool _done = false; std::string _principalName; std::string _authenticationDatabase; }; /** Base class for server mechanism factories. */ class ServerFactoryBase : public SaslServerCommonBase { public: /** * Returns if the factory is capable of producing a server mechanism object which could * authenticate the provided user. */ virtual bool canMakeMechanismForUser(const User* user) const = 0; /** Produces a unique_ptr containing a server SASL mechanism.*/ std::unique_ptr create(std::string authenticationDatabase) { std::unique_ptr rv( this->createImpl(std::move(authenticationDatabase))); invariant(rv->mechanismName() == this->mechanismName()); return rv; } private: virtual ServerMechanismBase* createImpl(std::string authenticationDatabase) = 0; }; /** Instantiates a class which provides runtime access to Policy properties. */ template class MakeServerMechanism : public ServerMechanismBase { public: explicit MakeServerMechanism(std::string authenticationDatabase) : ServerMechanismBase(std::move(authenticationDatabase)) {} virtual ~MakeServerMechanism() = default; using policy_type = Policy; StringData mechanismName() const final { return policy_type::getName(); } SecurityPropertySet properties() const final { return policy_type::getProperties(); } }; /** Instantiates a class which provides runtime access to Policy properties. */ template class MakeServerFactory : public ServerFactoryBase { public: static_assert(std::is_base_of, ServerMechanism>::value, "MakeServerFactory must be instantiated with a ServerMechanism derived from " "MakeServerMechanism"); using mechanism_type = ServerMechanism; using policy_type = typename ServerMechanism::policy_type; virtual ServerMechanism* createImpl(std::string authenticationDatabase) override { return new ServerMechanism(std::move(authenticationDatabase)); } StringData mechanismName() const final { return policy_type::getName(); } SecurityPropertySet properties() const final { return policy_type::getProperties(); } }; /** * Tracks server-side SASL mechanisms. Mechanisms' factories are registered with this class during * server initialization. During authentication, this class finds a factory, to obtains a * mechanism from. Also capable of producing a list of mechanisms which would be valid for a * particular user. */ class SASLServerMechanismRegistry { public: static SASLServerMechanismRegistry& get(ServiceContext* serviceContext); static void set(ServiceContext* service, std::unique_ptr registry); /** * Produces a list of SASL mechanisms which can be used to authenticate as a user. * If isMasterCmd contains a field with a username called 'saslSupportedMechs', * will populate 'builder' with an Array called saslSupportedMechs containing each mechanism the * user supports. */ void advertiseMechanismNamesForUser(OperationContext* opCtx, const BSONObj& isMasterCmd, BSONObjBuilder* builder); /** * Gets a mechanism object which corresponds to the provided name. * The mechanism will be able to authenticate users which exist on the * "authenticationDatabase". */ StatusWith> getServerMechanism( StringData mechanismName, std::string authenticationDatabase); /** * Registers a factory T to produce a type of SASL mechanism. * If 'internal' is false, the factory will be used to create mechanisms for authentication * attempts on $external. Otherwise, the mechanism may be used for any database but $external. * This allows distinct mechanisms with the same name for the servers' different authentication * domains. */ enum ValidateGlobalMechanisms : bool { kValidateGlobalMechanisms = true, kNoValidateGlobalMechanisms = false }; template bool registerFactory( ValidateGlobalMechanisms validateGlobalConfig = kValidateGlobalMechanisms) { using policy_type = typename T::policy_type; auto mechName = policy_type::getName(); // Always allow SCRAM-SHA-1 to pass to the first sasl step since we need to // handle internal user authentication, SERVER-16534 if (validateGlobalConfig && (mechName != "SCRAM-SHA-1" && !_mechanismSupportedByConfig(mechName))) { return false; } invariant(_getMapRef(T::mechanism_type::isInternal) .emplace(mechName.toString(), std::make_unique()) .second); return true; } private: stdx::unordered_map>& _getMapRef( StringData dbName) { return _getMapRef(dbName != "$external"_sd); } stdx::unordered_map>& _getMapRef( bool internal) { if (internal) { return _internalMap; } return _externalMap; } bool _mechanismSupportedByConfig(StringData mechName); // Stores factories which make mechanisms for all databases other than $external stdx::unordered_map> _internalMap; // Stores factories which make mechanisms exclusively for $external stdx::unordered_map> _externalMap; }; } // namespace mongo