diff options
author | Eric Milkie <milkie@10gen.com> | 2013-12-19 17:12:12 -0500 |
---|---|---|
committer | Eric Milkie <milkie@10gen.com> | 2013-12-24 14:04:22 -0500 |
commit | f148caa11e97727949b5e8594b7439856faff499 (patch) | |
tree | bf19ff059f11538c2a87c4e3e685f70e5a026937 | |
parent | ee0365ac659fa89758263d19327d69c547eb2d50 (diff) | |
download | mongo-f148caa11e97727949b5e8594b7439856faff499.tar.gz |
SERVER-11192 hook c++ driver to transmit mongos authenticated users to mongod, for auditing
Also, add UserNameIterator as a parallel to RoleIterator.
24 files changed, 330 insertions, 51 deletions
diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp index e815bd464c9..261247362e6 100644 --- a/src/mongo/client/dbclient.cpp +++ b/src/mongo/client/dbclient.cpp @@ -412,12 +412,25 @@ namespace mongo { return QueryOptions(0); } + void DBClientWithCommands::setRunCommandHook(RunCommandHookFunc func) { + _runCommandHook = func; + } + bool DBClientWithCommands::runCommand(const string &dbname, const BSONObj& cmd, BSONObj &info, int options) { string ns = dbname + ".$cmd"; - info = findOne(ns, cmd, 0 , options); + if (_runCommandHook) { + BSONObjBuilder cmdObj; + cmdObj.appendElements(cmd); + _runCommandHook(&cmdObj); + + info = findOne(ns, cmdObj.done(), 0 , options); + } + else { + info = findOne(ns, cmd, 0 , options); + } return isOk(info); } diff --git a/src/mongo/client/dbclient_rs.cpp b/src/mongo/client/dbclient_rs.cpp index e70d192a21b..d381f4adb8f 100644 --- a/src/mongo/client/dbclient_rs.cpp +++ b/src/mongo/client/dbclient_rs.cpp @@ -1393,6 +1393,15 @@ namespace mongo { return rsm->getServerAddress(); } + void DBClientReplicaSet::setRunCommandHook(DBClientWithCommands::RunCommandHookFunc func) { + if (_master) { + _master->setRunCommandHook(func); + } + if (_lastSlaveOkConn) { + _lastSlaveOkConn->setRunCommandHook(func); + } + } + // A replica set connection is never disconnected, since it controls its own reconnection // logic. // @@ -1504,6 +1513,7 @@ namespace mongo { _master.reset(newConn); _master->setReplSetClientCallback(this); + _master->setRunCommandHook(_runCommandHook); _auth( _master.get() ); return _master.get(); @@ -1888,6 +1898,7 @@ namespace mongo { _lastSlaveOkConn.reset(newConn); _lastSlaveOkConn->setReplSetClientCallback(this); + _lastSlaveOkConn->setRunCommandHook(_runCommandHook); _auth(_lastSlaveOkConn.get()); diff --git a/src/mongo/client/dbclient_rs.h b/src/mongo/client/dbclient_rs.h index 282479b00f5..7bcd2cc2eeb 100644 --- a/src/mongo/client/dbclient_rs.h +++ b/src/mongo/client/dbclient_rs.h @@ -531,6 +531,8 @@ namespace mongo { const BSONObj& queryObj, int queryOptions ); + virtual void setRunCommandHook(DBClientWithCommands::RunCommandHookFunc func); + protected: /** Authorize. Authorizes all nodes as needed */ diff --git a/src/mongo/client/dbclientcursor.cpp b/src/mongo/client/dbclientcursor.cpp index f6dc5758579..8ed7b8f5773 100644 --- a/src/mongo/client/dbclientcursor.cpp +++ b/src/mongo/client/dbclientcursor.cpp @@ -78,6 +78,15 @@ namespace mongo { void DBClientCursor::initLazy( bool isRetry ) { massert( 15875 , "DBClientCursor::initLazy called on a client that doesn't support lazy" , _client->lazySupported() ); + if (DBClientWithCommands::RunCommandHookFunc hook = _client->getRunCommandHook()) { + if (NamespaceString(ns).isCommand()) { + BSONObjBuilder bob; + bob.appendElements(query); + hook(&bob); + query = bob.obj(); + } + } + Message toSend; _assembleInit( toSend ); _client->say( toSend, isRetry, &_originalHost ); diff --git a/src/mongo/client/dbclientinterface.h b/src/mongo/client/dbclientinterface.h index 49226fb03c5..13d2939fba8 100644 --- a/src/mongo/client/dbclientinterface.h +++ b/src/mongo/client/dbclientinterface.h @@ -957,6 +957,19 @@ namespace mongo { virtual string toString() = 0; + /** + * A function type for runCommand hooking; the function takes a pointer + * to a BSONObjBuilder and returns nothing. The builder contains a + * runCommand BSON object. + * Once such a function is set as the runCommand hook, every time the DBClient + * processes a runCommand, the hook will be called just prior to sending it to the server. + */ + typedef boost::function<void(BSONObjBuilder*)> RunCommandHookFunc; + virtual void setRunCommandHook(RunCommandHookFunc func); + RunCommandHookFunc getRunCommandHook() const { + return _runCommandHook; + } + protected: /** if the result of a command is ok*/ bool isOk(const BSONObj&); @@ -996,6 +1009,11 @@ namespace mongo { const string &username, BSONObj *info); + /** + * This function will be executed by the driver on runCommand calls. + */ + RunCommandHookFunc _runCommandHook; + private: enum QueryOptions _cachedAvailableOptions; bool _haveCachedAvailableOptions; diff --git a/src/mongo/client/syncclusterconnection.cpp b/src/mongo/client/syncclusterconnection.cpp index 6e63a7d688d..361b0c83cc7 100644 --- a/src/mongo/client/syncclusterconnection.cpp +++ b/src/mongo/client/syncclusterconnection.cpp @@ -161,6 +161,7 @@ namespace mongo { void SyncClusterConnection::_connect( const std::string& host ) { log() << "SyncClusterConnection connecting to [" << host << "]" << endl; DBClientConnection * c = new DBClientConnection( true ); + c->setRunCommandHook(_runCommandHook); c->setSoTimeout( _socketTimeout ); string errmsg; if ( ! c->connect( host , errmsg ) ) @@ -513,4 +514,11 @@ namespace mongo { if( _conns[i] ) _conns[i]->setSoTimeout( socketTimeout ); } + void SyncClusterConnection::setRunCommandHook(DBClientWithCommands::RunCommandHookFunc func) { + for (size_t i = 0; i < _conns.size(); ++i) { + if (_conns[i]) { + _conns[i]->setRunCommandHook(func); + } + } + } } diff --git a/src/mongo/client/syncclusterconnection.h b/src/mongo/client/syncclusterconnection.h index cd81be44c6e..5994f8966cb 100644 --- a/src/mongo/client/syncclusterconnection.h +++ b/src/mongo/client/syncclusterconnection.h @@ -112,6 +112,8 @@ namespace mongo { virtual bool lazySupported() const { return false; } + virtual void setRunCommandHook(DBClientWithCommands::RunCommandHookFunc func); + protected: virtual void _auth(const BSONObj& params); diff --git a/src/mongo/db/audit.cpp b/src/mongo/db/audit.cpp index 226d76c331d..9b3dd2aa02d 100644 --- a/src/mongo/db/audit.cpp +++ b/src/mongo/db/audit.cpp @@ -208,6 +208,15 @@ namespace audit { const StringData& ns, const BSONObj& keyPattern, bool unique) MONGO_AUDIT_STUB + + void appendImpersonatedUsers(BSONObjBuilder* cmd) MONGO_AUDIT_STUB + + void parseAndRemoveImpersonatedUserField(BSONObj cmdObj, + AuthorizationSession* authSession, + std::vector<UserName>* parsedUserNames, + bool* fieldIsPresent) + MONGO_AUDIT_STUB + } // namespace audit } // namespace mongo diff --git a/src/mongo/db/audit.h b/src/mongo/db/audit.h index 2948c9a4b0e..b939966f168 100644 --- a/src/mongo/db/audit.h +++ b/src/mongo/db/audit.h @@ -39,6 +39,7 @@ namespace mongo { + class AuthorizationSession; class BSONObj; class ClientBasic; class NamespaceString; @@ -349,5 +350,33 @@ namespace audit { const BSONObj& keyPattern, bool unique); + + /* + * Appends an array of user/db pairs to the provided Document. + * The users are extracted from the current client. They are to be the + * impersonated users for a Command run by an internal user. + */ + void appendImpersonatedUsers(BSONObjBuilder* cmd); + const char cmdOptionImpersonatedUsers[] = "impersonatedUsers"; + + /* + * Looks for an 'impersonatedUsers' field. This field is used by mongos to + * transmit the usernames of the currently authenticated user when it runs commands + * on a shard using internal user authentication. Auditing uses this information + * to properly ascribe users to actions. This is necessary only for implicit actions that + * mongos cannot properly audit itself; examples are implicit collection and database creation. + * This function requires that the field is the last field in the bson object; it edits the + * command BSON to efficiently remove the field before returning. + * + * cmdObj [in, out]: If any impersonated users field exists, it will be parsed and removed. + * authSession [in]: current authorization session + * parsedUserNames [out]: populated with parsed usernames + * fieldIsPresent [out]: true if impersonatedUsers field was present in the object + */ + void parseAndRemoveImpersonatedUserField(BSONObj cmdObj, + AuthorizationSession* authSession, + std::vector<UserName>* parsedUserNames, + bool* fieldIsPresent); + } // namespace audit } // namespace mongo diff --git a/src/mongo/db/auth/action_types.txt b/src/mongo/db/auth/action_types.txt index 9c06abbc2fb..ecf7bf9ad87 100644 --- a/src/mongo/db/auth/action_types.txt +++ b/src/mongo/db/auth/action_types.txt @@ -59,6 +59,7 @@ "grantRolesToUser", # Not used for permissions checks, but to id the event in logs. "handshake", "hostInfo", +"impersonate", "indexStats", "inprog", "insert", diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp index bbab218f0b1..a9f0887253e 100644 --- a/src/mongo/db/auth/authorization_session.cpp +++ b/src/mongo/db/auth/authorization_session.cpp @@ -51,7 +51,8 @@ namespace { const std::string ADMIN_DBNAME = "admin"; } // namespace - AuthorizationSession::AuthorizationSession(AuthzSessionExternalState* externalState) { + AuthorizationSession::AuthorizationSession(AuthzSessionExternalState* externalState) + : _impersonationFlag(false) { _externalState.reset(externalState); } @@ -78,6 +79,9 @@ namespace { return status; } + // If there are any users in the impersonate list, clear them. + clearImpersonatedUserNames(); + // Calling add() on the UserSet may return a user that was replaced because it was from the // same database. User* replacedUser = _authenticatedUsers.add(user); @@ -97,19 +101,20 @@ namespace { } void AuthorizationSession::logoutDatabase(const std::string& dbname) { + clearImpersonatedUserNames(); User* removedUser = _authenticatedUsers.removeByDBName(dbname); if (removedUser) { getAuthorizationManager().releaseUser(removedUser); } } - UserSet::NameIterator AuthorizationSession::getAuthenticatedUserNames() { + UserNameIterator AuthorizationSession::getAuthenticatedUserNames() { return _authenticatedUsers.getNames(); } std::string AuthorizationSession::getAuthenticatedUserNamesToken() { std::string ret; - for (UserSet::NameIterator nameIter = getAuthenticatedUserNames(); + for (UserNameIterator nameIter = getAuthenticatedUserNames(); nameIter.more(); nameIter.next()) { ret += '\0'; // Using a NUL byte which isn't valid in usernames to separate them. @@ -475,4 +480,23 @@ namespace { return false; } + void AuthorizationSession::setImpersonatedUserNames(const std::vector<UserName>& names) { + _impersonatedUserNames = names; + _impersonationFlag = true; + } + + // Clear the vector of impersonated UserNames. + void AuthorizationSession::clearImpersonatedUserNames() { + _impersonatedUserNames.clear(); + _impersonationFlag = false; + } + + UserNameIterator AuthorizationSession::getImpersonatedUserNames() const { + return makeUserNameIteratorForContainer(_impersonatedUserNames); + } + + bool AuthorizationSession::isImpersonating() const { + return _impersonationFlag; + } + } // namespace mongo diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index c9c9178791a..3a64792b611 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -89,7 +89,7 @@ namespace mongo { size_t getNumAuthenticatedUsers(); // Gets an iterator over the names of all authenticated users stored in this manager. - UserSet::NameIterator getAuthenticatedUserNames(); + UserNameIterator getAuthenticatedUserNames(); // Returns a string representing all logged-in users on the current session. // WARNING: this string will contain NUL bytes so don't call c_str()! @@ -180,6 +180,21 @@ namespace mongo { // isAuthorizedForActionsOnResource(ResourcePattern::forExactNamespace(ns), actions). bool isAuthorizedForActionsOnNamespace(const NamespaceString& ns, const ActionSet& actions); + // Replaces the vector of UserNames that a system user is impersonating with a new vector. + // The auditing system adds these to each audit record in the log. + void setImpersonatedUserNames(const std::vector<UserName>& names); + + // Returns an iterator to a vector of impersonated usernames. + UserNameIterator getImpersonatedUserNames() const; + + // Clears the vector of impersonated UserNames. + void clearImpersonatedUserNames(); + + // Tells whether impersonation is active or not. This state is set when + // setImpersonatedUserNames is called and cleared when clearImpersonatedUserNames is + // called. + bool isImpersonating() const; + private: // If any users authenticated on this session are marked as invalid this updates them with @@ -195,6 +210,11 @@ namespace mongo { // All Users who have been authenticated on this connection UserSet _authenticatedUsers; + + // A vector of impersonated UserNames. These are used in the auditing system. + // They are not used for authz checks. + std::vector<UserName> _impersonatedUserNames; + bool _impersonationFlag; }; } // namespace mongo diff --git a/src/mongo/db/auth/user_management_commands_parser.cpp b/src/mongo/db/auth/user_management_commands_parser.cpp index 002880c2050..61023fe6a25 100644 --- a/src/mongo/db/auth/user_management_commands_parser.cpp +++ b/src/mongo/db/auth/user_management_commands_parser.cpp @@ -137,7 +137,7 @@ namespace auth { return Status::OK(); } - Status _parseUserNamesFromBSONArray(const BSONArray& usersArray, + Status parseUserNamesFromBSONArray(const BSONArray& usersArray, const StringData& dbname, std::vector<UserName>* parsedUserNames) { return _parseNamesFromBSONArray(usersArray, @@ -359,9 +359,9 @@ namespace auth { if (cmdObj["usersInfo"].numberInt() == 1) { parsedArgs->allForDB = true; } else if (cmdObj["usersInfo"].type() == Array) { - status = _parseUserNamesFromBSONArray(BSONArray(cmdObj["usersInfo"].Obj()), - dbname, - &parsedArgs->userNames); + status = parseUserNamesFromBSONArray(BSONArray(cmdObj["usersInfo"].Obj()), + dbname, + &parsedArgs->userNames); if (!status.isOK()) { return status; } diff --git a/src/mongo/db/auth/user_management_commands_parser.h b/src/mongo/db/auth/user_management_commands_parser.h index d5e10f7574d..befeb2731ba 100644 --- a/src/mongo/db/auth/user_management_commands_parser.h +++ b/src/mongo/db/auth/user_management_commands_parser.h @@ -194,7 +194,7 @@ namespace auth { PrivilegeVector* parsedPrivileges); /** - * Takes a BSONArray of name,source pair documents, parses that array and returns (via the + * Takes a BSONArray of name,db pair documents, parses that array and returns (via the * output param parsedRoleNames) a list of the role names in the input array. * Performs syntactic validation of "rolesArray", only. */ @@ -202,5 +202,14 @@ namespace auth { const StringData& dbname, std::vector<RoleName>* parsedRoleNames); + /** + * Takes a BSONArray of name,db pair documents, parses that array and returns (via the + * output param parsedUserNames) a list of the usernames in the input array. + * Performs syntactic validation of "usersArray", only. + */ + Status parseUserNamesFromBSONArray(const BSONArray& usersArray, + const StringData& dbname, + std::vector<UserName>* parsedUserNames); + } // namespace auth } // namespace mongo diff --git a/src/mongo/db/auth/user_name.h b/src/mongo/db/auth/user_name.h index acee87f8ada..9104fded846 100644 --- a/src/mongo/db/auth/user_name.h +++ b/src/mongo/db/auth/user_name.h @@ -18,6 +18,9 @@ #include <iosfwd> #include <string> +#include <boost/scoped_ptr.hpp> + +#include "mongo/base/disallow_copying.h" #include "mongo/base/string_data.h" namespace mongo { @@ -71,4 +74,77 @@ namespace mongo { std::ostream& operator<<(std::ostream& os, const UserName& name); + /** + * Iterator over an unspecified container of UserName objects. + */ + class UserNameIterator { + public: + class Impl { + MONGO_DISALLOW_COPYING(Impl); + public: + Impl() {}; + virtual ~Impl() {}; + static Impl* clone(Impl* orig) { return orig ? orig->doClone(): NULL; } + virtual bool more() const = 0; + virtual const UserName& get() const = 0; + + virtual const UserName& next() = 0; + + private: + virtual Impl* doClone() const = 0; + }; + + UserNameIterator() : _impl(NULL) {} + UserNameIterator(const UserNameIterator& other) : _impl(Impl::clone(other._impl.get())) {} + explicit UserNameIterator(Impl* impl) : _impl(impl) {} + + UserNameIterator& operator=(const UserNameIterator& other) { + _impl.reset(Impl::clone(other._impl.get())); + return *this; + } + + bool more() const { return _impl.get() && _impl->more(); } + const UserName& get() const { return _impl->get(); } + + const UserName& next() { return _impl->next(); } + + const UserName& operator*() const { return get(); } + const UserName* operator->() const { return &get(); } + + private: + boost::scoped_ptr<Impl> _impl; + }; + + + template <typename ContainerIterator> + class UserNameContainerIteratorImpl : public UserNameIterator::Impl { + MONGO_DISALLOW_COPYING(UserNameContainerIteratorImpl); + public: + UserNameContainerIteratorImpl(const ContainerIterator& begin, + const ContainerIterator& end) : + _curr(begin), _end(end) {} + virtual ~UserNameContainerIteratorImpl() {} + virtual bool more() const { return _curr != _end; } + virtual const UserName& next() { return *(_curr++); } + virtual const UserName& get() const { return *_curr; } + virtual UserNameIterator::Impl* doClone() const { + return new UserNameContainerIteratorImpl(_curr, _end); + } + + private: + ContainerIterator _curr; + ContainerIterator _end; + }; + + template <typename ContainerIterator> + UserNameIterator makeUserNameIterator(const ContainerIterator& begin, + const ContainerIterator& end) { + return UserNameIterator( new UserNameContainerIteratorImpl<ContainerIterator>(begin, end)); + } + + template <typename Container> + UserNameIterator makeUserNameIteratorForContainer(const Container& container) { + return makeUserNameIterator(container.begin(), container.end()); + } + } // namespace mongo diff --git a/src/mongo/db/auth/user_set.cpp b/src/mongo/db/auth/user_set.cpp index c36a1c093f7..34c1318ab9d 100644 --- a/src/mongo/db/auth/user_set.cpp +++ b/src/mongo/db/auth/user_set.cpp @@ -23,6 +23,27 @@ namespace mongo { +namespace { + class UserSetNameIteratorImpl : public UserNameIterator::Impl { + MONGO_DISALLOW_COPYING(UserSetNameIteratorImpl); + public: + UserSetNameIteratorImpl(const UserSet::iterator& begin, + const UserSet::iterator& end) : + _curr(begin), _end(end) {} + virtual ~UserSetNameIteratorImpl() {} + virtual bool more() const { return _curr != _end; } + virtual const UserName& next() { return (*(_curr++))->getName(); } + virtual const UserName& get() const { return (*_curr)->getName(); } + virtual UserNameIterator::Impl* doClone() const { + return new UserSetNameIteratorImpl(_curr, _end); + } + + private: + UserSet::iterator _curr; + UserSet::iterator _end; + }; +} // namespace + UserSet::UserSet() : _users(), _usersEnd(_users.end()) {} UserSet::~UserSet() {} @@ -90,4 +111,7 @@ namespace mongo { return NULL; } + UserNameIterator UserSet::getNames() const { + return UserNameIterator(new UserSetNameIteratorImpl(begin(), end())); + } } // namespace mongo diff --git a/src/mongo/db/auth/user_set.h b/src/mongo/db/auth/user_set.h index ad193654e52..591a9bdb9fa 100644 --- a/src/mongo/db/auth/user_set.h +++ b/src/mongo/db/auth/user_set.h @@ -37,37 +37,6 @@ namespace mongo { public: typedef std::vector<User*>::const_iterator iterator; - /** - * Forward iterator over the names of the users stored in a UserSet. - * - * Instances are valid until the underlying vector<User*> is modified. - * - * more() must be the first method called after construction, and must be checked - * after each call to next() before calling any other methods. - */ - class NameIterator { - public: - explicit NameIterator(iterator begin, iterator end) : _curr(begin), _end(end) {} - - NameIterator() {} - - bool more() { return _curr != _end; } - const UserName& next() { - const UserName& ret = get(); - ++_curr; - return ret; - } - - const UserName& get() const { return (*_curr)->getName(); } - - const UserName& operator*() const { return get(); } - const UserName* operator->() const { return &get(); } - - private: - std::vector<User*>::const_iterator _curr; - std::vector<User*>::const_iterator _end; - }; - UserSet(); ~UserSet(); @@ -121,7 +90,7 @@ namespace mongo { // Gets an iterator over the names of the users stored in the set. The iterator is // valid until the next non-const method is called on the UserSet. - NameIterator getNames() const { return NameIterator(begin(), end()); } + UserNameIterator getNames() const; iterator begin() const { return _users.begin(); } iterator end() const { return _usersEnd; } diff --git a/src/mongo/db/auth/user_set_test.cpp b/src/mongo/db/auth/user_set_test.cpp index fb30d00bee7..5063f727412 100644 --- a/src/mongo/db/auth/user_set_test.cpp +++ b/src/mongo/db/auth/user_set_test.cpp @@ -75,7 +75,7 @@ namespace { ASSERT_EQUALS(p3, set.lookup(UserName("Bob", "test2"))); ASSERT_EQUALS(p3, set.lookupByDBName("test2")); - UserSet::NameIterator iter = set.getNames(); + UserNameIterator iter = set.getNames(); ASSERT_TRUE(iter.more()); ASSERT_EQUALS(iter.next(), UserName("Bob", "test2")); ASSERT_FALSE(iter.more()); @@ -83,7 +83,7 @@ namespace { TEST(UserSetTest, IterateNames) { UserSet pset; - UserSet::NameIterator iter = pset.getNames(); + UserNameIterator iter = pset.getNames(); ASSERT(!iter.more()); ASSERT_NULL(pset.add(new User(UserName("bob", "test")))); diff --git a/src/mongo/db/commands/connection_status.cpp b/src/mongo/db/commands/connection_status.cpp index 680ae1730d0..a4d173c0143 100644 --- a/src/mongo/db/commands/connection_status.cpp +++ b/src/mongo/db/commands/connection_status.cpp @@ -56,7 +56,7 @@ namespace mongo { { BSONArrayBuilder authenticatedUsers(authInfo.subarrayStart("authenticatedUsers")); - UserSet::NameIterator nameIter = authSession->getAuthenticatedUserNames(); + UserNameIterator nameIter = authSession->getAuthenticatedUserNames(); for ( ; nameIter.more(); nameIter.next()) { BSONObjBuilder userInfoBuilder(authenticatedUsers.subobjStart()); userInfoBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, diff --git a/src/mongo/db/dbcommands.cpp b/src/mongo/db/dbcommands.cpp index bc277328bbc..7a39783a3aa 100644 --- a/src/mongo/db/dbcommands.cpp +++ b/src/mongo/db/dbcommands.cpp @@ -32,6 +32,7 @@ #include <time.h> +#include "mongo/base/disallow_copying.h" #include "mongo/base/init.h" #include "mongo/base/status.h" #include "mongo/bson/util/builder.h" @@ -41,6 +42,8 @@ #include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/privilege.h" +#include "mongo/db/auth/user_management_commands_parser.h" +#include "mongo/db/auth/user_name.h" #include "mongo/db/background.h" #include "mongo/db/clientcursor.h" #include "mongo/db/commands.h" @@ -1312,6 +1315,35 @@ namespace mongo { bool maintenanceModeSet; }; + + /** + * RAII class to optionally set an impersonated username list into the authorization session + * for the duration of the life of this object + */ + class ImpersonationSessionGuard { + MONGO_DISALLOW_COPYING(ImpersonationSessionGuard); + public: + ImpersonationSessionGuard(AuthorizationSession* authSession, + bool fieldIsPresent, + const std::vector<UserName> &parsedUserNames) : + _authSession(authSession), _impersonation(false) { + if (fieldIsPresent) { + massert(17317, "impersonation unexpectedly active", + !authSession->isImpersonating()); + authSession->setImpersonatedUserNames(parsedUserNames); + _impersonation = true; + } + } + ~ImpersonationSessionGuard() { + if (_impersonation) { + _authSession->clearImpersonatedUserNames(); + } + } + private: + AuthorizationSession* _authSession; + bool _impersonation; + }; + /** * this handles - auth @@ -1328,7 +1360,6 @@ namespace mongo { BSONObj& cmdObj, BSONObjBuilder& result, bool fromRepl ) { - std::string dbname = nsToDatabase( cmdns ); scoped_ptr<MaintenanceModeSetter> mmSetter; @@ -1343,6 +1374,18 @@ namespace mongo { return; } + // Handle command option impersonatedUsers. + // This must come before _checkAuthorization(), as there is some command parsing logic + // in that code path that must not see the impersonated user array element. + std::vector<UserName> parsedUserNames; + AuthorizationSession* authSession = client.getAuthorizationSession(); + bool fieldIsPresent = false; + audit::parseAndRemoveImpersonatedUserField(cmdObj, authSession, + &parsedUserNames, &fieldIsPresent); + ImpersonationSessionGuard impersonationSession(authSession, + fieldIsPresent, + parsedUserNames); + Status status = _checkAuthorization(c, &client, dbname, cmdObj, fromRepl); if (!status.isOK()) { appendCommandStatus(result, status); diff --git a/src/mongo/db/introspect.cpp b/src/mongo/db/introspect.cpp index 09d1ca58a80..d1dff7b2039 100644 --- a/src/mongo/db/introspect.cpp +++ b/src/mongo/db/introspect.cpp @@ -52,7 +52,7 @@ namespace { void _appendUserInfo(const Client& c, BSONObjBuilder& builder, AuthorizationSession* authSession) { - UserSet::NameIterator nameIter = authSession->getAuthenticatedUserNames(); + UserNameIterator nameIter = authSession->getAuthenticatedUserNames(); UserName bestUser; if (nameIter.more()) diff --git a/src/mongo/db/server_extra_log_context.cpp b/src/mongo/db/server_extra_log_context.cpp index 1552da28d54..824402de5fc 100644 --- a/src/mongo/db/server_extra_log_context.cpp +++ b/src/mongo/db/server_extra_log_context.cpp @@ -53,8 +53,8 @@ namespace { if (!clientBasic->hasAuthorizationSession()) return; - UserSet::NameIterator users = - clientBasic->getAuthorizationSession()->getAuthenticatedUserNames(); + UserNameIterator users = + clientBasic->getAuthorizationSession()->getAuthenticatedUserNames(); if (!users.more()) return; diff --git a/src/mongo/s/dbclient_multi_command.cpp b/src/mongo/s/dbclient_multi_command.cpp index 3a400f2692d..d6a401c3d6e 100644 --- a/src/mongo/s/dbclient_multi_command.cpp +++ b/src/mongo/s/dbclient_multi_command.cpp @@ -28,6 +28,9 @@ #include "mongo/s/dbclient_multi_command.h" +#include "mongo/bson/mutable/document.h" +#include "mongo/db/audit.h" +#include "mongo/db/client_basic.h" #include "mongo/db/dbmessage.h" #include "mongo/db/wire_version.h" #include "mongo/s/shard.h" @@ -111,14 +114,17 @@ namespace mongo { // THROWS static void sayAsCmd( DBClientBase* conn, const StringData& dbName, const BSONObj& cmdObj ) { Message toSend; - + BSONObjBuilder usersBuilder; + usersBuilder.appendElements(cmdObj); + audit::appendImpersonatedUsers(&usersBuilder); + // see query.h for the protocol we are using here. BufBuilder bufB; bufB.appendNum( 0 ); // command/query options bufB.appendStr( dbName.toString() + ".$cmd" ); // write command ns bufB.appendNum( 0 ); // ntoskip (0 for command) bufB.appendNum( 1 ); // ntoreturn (1 for command) - cmdObj.appendSelfToBufBuilder( bufB ); + usersBuilder.obj().appendSelfToBufBuilder( bufB ); toSend.setData( dbQuery, bufB.buf(), bufB.len() ); // Send our command diff --git a/src/mongo/s/shard.cpp b/src/mongo/s/shard.cpp index 693cdd7b51f..3c8e629ac0b 100644 --- a/src/mongo/s/shard.cpp +++ b/src/mongo/s/shard.cpp @@ -38,6 +38,7 @@ #include "mongo/client/dbclient_rs.h" #include "mongo/client/dbclientcursor.h" +#include "mongo/db/audit.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authorization_manager_global.h" @@ -451,6 +452,11 @@ namespace mongo { causedBy( (string)"unknown failure : " + result.toString() ) ), ok ); } + + // For every DBClient created by mongos, add a hook that will append impersonated users + // to the end of every runCommand. mongod uses this information to produce auditing + // records attributed to the proper authenticated user(s). + conn->setRunCommandHook(boost::bind(&audit::appendImpersonatedUsers, _1)); } void ShardingConnectionHook::onDestroy( DBClientBase * conn ) { |