diff options
author | Amalia Hawkins <amalia.hawkins@10gen.com> | 2014-07-21 13:48:43 -0400 |
---|---|---|
committer | Amalia Hawkins <amalia.hawkins@10gen.com> | 2014-07-21 13:48:43 -0400 |
commit | f2d47ee02a94f56b29e1874aebf8ae4dca222d2e (patch) | |
tree | b00f66aa6b23fa728b08c7a5a23aa7e27fc43145 /src/mongo | |
parent | fcab456c204a1c5eccfc3d700337cb5bff0621fc (diff) | |
download | mongo-f2d47ee02a94f56b29e1874aebf8ae4dca222d2e.tar.gz |
SERVER-12512: Add role-based, selective audit logging.
Diffstat (limited to 'src/mongo')
-rw-r--r-- | src/mongo/base/error_codes.err | 1 | ||||
-rw-r--r-- | src/mongo/db/audit.cpp | 16 | ||||
-rw-r--r-- | src/mongo/db/audit.h | 42 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session.cpp | 55 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session.h | 37 | ||||
-rw-r--r-- | src/mongo/db/auth/user.cpp | 11 | ||||
-rw-r--r-- | src/mongo/db/auth/user.h | 14 | ||||
-rw-r--r-- | src/mongo/db/auth/user_document_parser.cpp | 31 | ||||
-rw-r--r-- | src/mongo/db/auth/user_document_parser.h | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/user_management_commands_parser.h | 1 | ||||
-rw-r--r-- | src/mongo/db/dbcommands.cpp | 46 |
12 files changed, 206 insertions, 53 deletions
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err index 49cd774b730..ef47be4e5db 100644 --- a/src/mongo/base/error_codes.err +++ b/src/mongo/base/error_codes.err @@ -101,6 +101,7 @@ error_code("DBPathInUse", 98) error_code("WriteConcernNotDefined", 99) error_code("CannotSatisfyWriteConcern", 100) error_code("OutdatedClient", 101) +error_code("IncompatibleAuditMetadata", 102) # Non-sequential error codes (for compatibility only) error_code("NotMaster", 10107) #this comes from assert_util.h diff --git a/src/mongo/db/audit.cpp b/src/mongo/db/audit.cpp index 9b3dd2aa02d..a06d0058a8f 100644 --- a/src/mongo/db/audit.cpp +++ b/src/mongo/db/audit.cpp @@ -211,11 +211,17 @@ namespace audit { void appendImpersonatedUsers(BSONObjBuilder* cmd) MONGO_AUDIT_STUB - void parseAndRemoveImpersonatedUserField(BSONObj cmdObj, - AuthorizationSession* authSession, - std::vector<UserName>* parsedUserNames, - bool* fieldIsPresent) - MONGO_AUDIT_STUB + void parseAndRemoveImpersonatedUsersField( + BSONObj cmdObj, + AuthorizationSession* authSession, + std::vector<UserName>* parsedUserNames, + bool* fieldIsPresent) MONGO_AUDIT_STUB + + void parseAndRemoveImpersonatedRolesField( + BSONObj cmdObj, + AuthorizationSession* authSession, + std::vector<RoleName>* parsedRoleNames, + bool* fieldIsPresent) MONGO_AUDIT_STUB } // namespace audit } // namespace mongo diff --git a/src/mongo/db/audit.h b/src/mongo/db/audit.h index b939966f168..0e6d07cf92a 100644 --- a/src/mongo/db/audit.h +++ b/src/mongo/db/audit.h @@ -351,15 +351,16 @@ namespace audit { 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. + /* + * Appends an array of user/db pairs and an array of role/db pairs + * to the provided Document. The users and roles are extracted from the current client. + * They are to be the impersonated users and roles for a Command run by an internal user. */ void appendImpersonatedUsers(BSONObjBuilder* cmd); const char cmdOptionImpersonatedUsers[] = "impersonatedUsers"; + const char cmdOptionImpersonatedRoles[] = "impersonatedRoles"; - /* + /* * 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 @@ -372,11 +373,32 @@ namespace audit { * 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); + */ + void parseAndRemoveImpersonatedUsersField( + BSONObj cmdObj, + AuthorizationSession* authSession, + std::vector<UserName>* parsedUserNames, + bool* fieldIsPresent); + + /* + * Looks for an 'impersonatedRoles' field. This field is used by mongos to + * transmit the roles of the currently authenticated user when it runs commands + * on a shard using internal user authentication. Auditing uses this information + * to properly ascribe user roles 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 roles field exists, it will be parsed and removed. + * authSession [in]: current authorization session + * parsedRoleNames [out]: populated with parsed user rolenames + * fieldIsPresent [out]: true if impersonatedRoles field was present in the object + */ + void parseAndRemoveImpersonatedRolesField( + BSONObj cmdObj, + AuthorizationSession* authSession, + std::vector<RoleName>* parsedRoleNames, + bool* fieldIsPresent); } // namespace audit } // namespace mongo diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp index ccdb85a35eb..09326ade31a 100644 --- a/src/mongo/db/auth/authorization_manager.cpp +++ b/src/mongo/db/auth/authorization_manager.cpp @@ -489,6 +489,10 @@ namespace mongo { if (!status.isOK()) { return status; } + status = parser.initializeUserIndirectRolesFromUserDocument(privDoc, user); + if (!status.isOK()) { + return status; + } status = parser.initializeUserPrivilegesFromUserDocument(privDoc, user); return Status::OK(); } diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp index 45071becf46..c6da7ce8935 100644 --- a/src/mongo/db/auth/authorization_session.cpp +++ b/src/mongo/db/auth/authorization_session.cpp @@ -84,9 +84,6 @@ 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); @@ -94,6 +91,10 @@ namespace { getAuthorizationManager().releaseUser(replacedUser); } + // If there are any users and roles in the impersonation data, clear it out. + clearImpersonatedUserData(); + + _buildAuthenticatedRolesVector(); return Status::OK(); } @@ -102,17 +103,23 @@ namespace { } void AuthorizationSession::logoutDatabase(const std::string& dbname) { - clearImpersonatedUserNames(); User* removedUser = _authenticatedUsers.removeByDBName(dbname); if (removedUser) { getAuthorizationManager().releaseUser(removedUser); } + clearImpersonatedUserData(); + _buildAuthenticatedRolesVector(); } UserNameIterator AuthorizationSession::getAuthenticatedUserNames() { return _authenticatedUsers.getNames(); } + RoleNameIterator AuthorizationSession::getAuthenticatedRoleNames() { + return makeRoleNameIterator(_authenticatedRoleNames.begin(), + _authenticatedRoleNames.end()); + } + std::string AuthorizationSession::getAuthenticatedUserNamesToken() { std::string ret; for (UserNameIterator nameIter = getAuthenticatedUserNames(); @@ -127,6 +134,7 @@ namespace { void AuthorizationSession::grantInternalAuthorization() { _authenticatedUsers.add(internalSecurity.user); + _buildAuthenticatedRolesVector(); } PrivilegeVector AuthorizationSession::getDefaultPrivileges() { @@ -470,6 +478,21 @@ namespace { } ++it; } + _buildAuthenticatedRolesVector(); + } + + void AuthorizationSession::_buildAuthenticatedRolesVector() { + _authenticatedRoleNames.clear(); + for (UserSet::iterator it = _authenticatedUsers.begin(); + it != _authenticatedUsers.end(); + ++it) { + RoleNameIterator roles = (*it)->getIndirectRoles(); + while (roles.more()) { + RoleName roleName = roles.next(); + _authenticatedRoleNames.push_back(RoleName(roleName.getRole(), + roleName.getDB())); + } + } } bool AuthorizationSession::_isAuthorizedForPrivilege(const Privilege& privilege) { @@ -511,20 +534,30 @@ namespace { return false; } - void AuthorizationSession::setImpersonatedUserNames(const std::vector<UserName>& names) { - _impersonatedUserNames = names; + void AuthorizationSession::setImpersonatedUserData(std::vector<UserName> usernames, + std::vector<RoleName> roles) { + _impersonatedUserNames = usernames; + _impersonatedRoleNames = roles; _impersonationFlag = true; } - // Clear the vector of impersonated UserNames. - void AuthorizationSession::clearImpersonatedUserNames() { + UserNameIterator AuthorizationSession::getImpersonatedUserNames() { + return makeUserNameIterator(_impersonatedUserNames.begin(), + _impersonatedUserNames.end()); + } + + RoleNameIterator AuthorizationSession::getImpersonatedRoleNames() { + return makeRoleNameIterator(_impersonatedRoleNames.begin(), + _impersonatedRoleNames.end()); + } + + // Clear the vectors of impersonated usernames and roles. + void AuthorizationSession::clearImpersonatedUserData() { _impersonatedUserNames.clear(); + _impersonatedRoleNames.clear(); _impersonationFlag = false; } - UserNameIterator AuthorizationSession::getImpersonatedUserNames() const { - return makeUserNameIteratorForContainer(_impersonatedUserNames); - } bool AuthorizationSession::isImpersonating() const { return _impersonationFlag; diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index f0484e26f1b..a2fc0e9e05b 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -88,6 +88,9 @@ namespace mongo { // Gets an iterator over the names of all authenticated users stored in this manager. UserNameIterator getAuthenticatedUserNames(); + // Gets an iterator over the roles of all authenticated users stored in this manager. + RoleNameIterator getAuthenticatedRoleNames(); + // Returns a std::string representing all logged-in users on the current session. // WARNING: this std::string will contain NUL bytes so don't call c_str()! std::string getAuthenticatedUserNamesToken(); @@ -185,18 +188,21 @@ namespace mongo { 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); + // Replaces the data for users that a system user is impersonating with new data. + // The auditing system adds these users and their roles to each audit record in the log. + void setImpersonatedUserData(std::vector<UserName> usernames, std::vector<RoleName> roles); + + // Gets an iterator over the names of all users that the system user is impersonating. + UserNameIterator getImpersonatedUserNames(); - // Returns an iterator to a vector of impersonated usernames. - UserNameIterator getImpersonatedUserNames() const; + // Gets an iterator over the roles of all users that the system user is impersonating. + RoleNameIterator getImpersonatedRoleNames(); - // Clears the vector of impersonated UserNames. - void clearImpersonatedUserNames(); + // Clears the data for impersonated users. + void clearImpersonatedUserData(); // Tells whether impersonation is active or not. This state is set when - // setImpersonatedUserNames is called and cleared when clearImpersonatedUserNames is + // setImpersonatedUserData is called and cleared when clearImpersonatedUserData is // called. bool isImpersonating() const; @@ -206,6 +212,11 @@ namespace mongo { // up-to-date information. May require a read lock on the "admin" db to read the user data. void _refreshUserInfoAsNeeded(OperationContext* txn); + // Builds a vector of all roles held by users who are authenticated on this connection. The + // vector is stored in _authenticatedRoleNames. This function is called when users are + // logged in or logged out, as well as when the user cache is determined to be out of date. + void _buildAuthenticatedRolesVector(); + // Checks if this connection is authorized for the given Privilege, ignoring whether or not // we should even be doing authorization checks in general. Note: this may acquire a read // lock on the admin database (to update out-of-date user privilege information). @@ -213,12 +224,16 @@ namespace mongo { scoped_ptr<AuthzSessionExternalState> _externalState; - // All Users who have been authenticated on this connection + // All Users who have been authenticated on this connection. UserSet _authenticatedUsers; + // The roles of the authenticated users. This vector is generated when the authenticated + // users set is changed. + std::vector<RoleName> _authenticatedRoleNames; - // A vector of impersonated UserNames. These are used in the auditing system. - // They are not used for authz checks. + // A vector of impersonated UserNames and a vector of those users' RoleNames. + // These are used in the auditing system. They are not used for authz checks. std::vector<UserName> _impersonatedUserNames; + std::vector<RoleName> _impersonatedRoleNames; bool _impersonationFlag; }; diff --git a/src/mongo/db/auth/user.cpp b/src/mongo/db/auth/user.cpp index cb856f2adad..4fcfaf883cb 100644 --- a/src/mongo/db/auth/user.cpp +++ b/src/mongo/db/auth/user.cpp @@ -58,6 +58,10 @@ namespace mongo { return makeRoleNameIteratorForContainer(_roles); } + RoleNameIterator User::getIndirectRoles() const { + return makeRoleNameIteratorForContainer(_indirectRoles); + } + bool User::hasRole(const RoleName& roleName) const { return _roles.count(roleName); } @@ -101,6 +105,13 @@ namespace mongo { } } + void User::setIndirectRoles(RoleNameIterator indirectRoles) { + _indirectRoles.clear(); + while (indirectRoles.more()) { + _indirectRoles.push_back(indirectRoles.next()); + } + } + void User::setPrivileges(const PrivilegeVector& privileges) { _privileges.clear(); for (size_t i = 0; i < privileges.size(); ++i) { diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h index 0fca7461d93..3950e42090a 100644 --- a/src/mongo/db/auth/user.h +++ b/src/mongo/db/auth/user.h @@ -86,6 +86,11 @@ namespace mongo { RoleNameIterator getRoles() const; /** + * Returns an iterator over the names of the user's indirect roles + */ + RoleNameIterator getIndirectRoles() const; + + /** * Returns true if this user is a member of the given role. */ bool hasRole(const RoleName& roleName) const; @@ -136,6 +141,12 @@ namespace mongo { void setRoles(RoleNameIterator roles); /** + * Replaces any existing indirect user role membership information with the roles from + * "indirectRoles". + */ + void setIndirectRoles(RoleNameIterator indirectRoles); + + /** * Replaces any existing user privilege information with "privileges". */ void setPrivileges(const PrivilegeVector& privileges); @@ -195,6 +206,9 @@ namespace mongo { // Roles the user has privileges from unordered_set<RoleName> _roles; + // Roles that the user indirectly has privileges from, due to role inheritance. + std::vector<RoleName> _indirectRoles; + // Credential information. CredentialData _credentials; diff --git a/src/mongo/db/auth/user_document_parser.cpp b/src/mongo/db/auth/user_document_parser.cpp index d9b72faa1e8..4e78a712710 100644 --- a/src/mongo/db/auth/user_document_parser.cpp +++ b/src/mongo/db/auth/user_document_parser.cpp @@ -45,6 +45,7 @@ namespace { const std::string ROLES_FIELD_NAME = "roles"; const std::string PRIVILEGES_FIELD_NAME = "inheritedPrivileges"; + const std::string INHERITED_ROLES_FIELD_NAME = "inheritedRoles"; const std::string OTHER_DB_ROLES_FIELD_NAME = "otherDBRoles"; const std::string READONLY_FIELD_NAME = "readOnly"; const std::string CREDENTIALS_FIELD_NAME = "credentials"; @@ -477,6 +478,36 @@ namespace { return Status::OK(); } + Status V2UserDocumentParser::initializeUserIndirectRolesFromUserDocument( + const BSONObj& privDoc, User* user) const { + + BSONElement indirectRolesElement = privDoc[INHERITED_ROLES_FIELD_NAME]; + + if (indirectRolesElement.type() != Array) { + return Status(ErrorCodes::UnsupportedFormat, + "User document needs 'inheritedRoles' field to be an array"); + } + + std::vector<RoleName> indirectRoles; + for (BSONObjIterator it(indirectRolesElement.Obj()); it.more(); it.next()) { + if ((*it).type() != Object) { + return Status(ErrorCodes::UnsupportedFormat, + "User document needs values in 'inheritedRoles'" + " array to be a sub-documents"); + } + BSONObj indirectRoleObject = (*it).Obj(); + + RoleName indirectRole; + Status status = parseRoleName(indirectRoleObject, &indirectRole); + if (!status.isOK()) { + return status; + } + indirectRoles.push_back(indirectRole); + } + user->setIndirectRoles(makeRoleNameIteratorForContainer(indirectRoles)); + return Status::OK(); + } + Status V2UserDocumentParser::initializeUserPrivilegesFromUserDocument(const BSONObj& doc, User* user) const { BSONElement privilegesElement = doc[PRIVILEGES_FIELD_NAME]; diff --git a/src/mongo/db/auth/user_document_parser.h b/src/mongo/db/auth/user_document_parser.h index be68330239e..3be1ae98812 100644 --- a/src/mongo/db/auth/user_document_parser.h +++ b/src/mongo/db/auth/user_document_parser.h @@ -69,6 +69,7 @@ namespace mongo { Status initializeUserCredentialsFromUserDocument(User* user, const BSONObj& privDoc) const; Status initializeUserRolesFromUserDocument(const BSONObj& doc, User* user) const; + Status initializeUserIndirectRolesFromUserDocument(const BSONObj& doc, User* user) const; Status initializeUserPrivilegesFromUserDocument(const BSONObj& doc, User* user) const; }; diff --git a/src/mongo/db/auth/user_management_commands_parser.h b/src/mongo/db/auth/user_management_commands_parser.h index 91cb92200ee..606d1667259 100644 --- a/src/mongo/db/auth/user_management_commands_parser.h +++ b/src/mongo/db/auth/user_management_commands_parser.h @@ -218,7 +218,6 @@ namespace auth { const StringData& dbname, std::vector<UserName>* parsedUserNames); - struct MergeAuthzCollectionsArgs { std::string usersCollName; std::string rolesCollName; diff --git a/src/mongo/db/dbcommands.cpp b/src/mongo/db/dbcommands.cpp index d02291b9ba5..e6c6d36ce41 100644 --- a/src/mongo/db/dbcommands.cpp +++ b/src/mongo/db/dbcommands.cpp @@ -1192,19 +1192,20 @@ namespace mongo { MONGO_DISALLOW_COPYING(ImpersonationSessionGuard); public: ImpersonationSessionGuard(AuthorizationSession* authSession, - bool fieldIsPresent, - const std::vector<UserName> &parsedUserNames) : + bool fieldIsPresent, + const std::vector<UserName> &parsedUserNames, + const std::vector<RoleName> &parsedRoleNames): _authSession(authSession), _impersonation(false) { if (fieldIsPresent) { - massert(17317, "impersonation unexpectedly active", + massert(17317, "impersonation unexpectedly active", !authSession->isImpersonating()); - authSession->setImpersonatedUserNames(parsedUserNames); + authSession->setImpersonatedUserData(parsedUserNames, parsedRoleNames); _impersonation = true; } } ~ImpersonationSessionGuard() { if (_impersonation) { - _authSession->clearImpersonatedUserNames(); + _authSession->clearImpersonatedUserData(); } } private: @@ -1252,19 +1253,34 @@ namespace mongo { return; } - // Handle command option impersonatedUsers. + // Handle command option impersonatedUsers and impersonatedRoles. // 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. + // in that code path that must not see the impersonated user and roles array elements. std::vector<UserName> parsedUserNames; + std::vector<RoleName> parsedRoleNames; AuthorizationSession* authSession = client.getAuthorizationSession(); - bool fieldIsPresent = false; - audit::parseAndRemoveImpersonatedUserField(cmdObj, - authSession, - &parsedUserNames, - &fieldIsPresent); - ImpersonationSessionGuard impersonationSession(authSession, - fieldIsPresent, - parsedUserNames); + bool rolesFieldIsPresent = false; + bool usersFieldIsPresent = false; + audit::parseAndRemoveImpersonatedRolesField(cmdObj, + authSession, + &parsedRoleNames, + &rolesFieldIsPresent); + audit::parseAndRemoveImpersonatedUsersField(cmdObj, + authSession, + &parsedUserNames, + &usersFieldIsPresent); + if (rolesFieldIsPresent != usersFieldIsPresent) { + // If there is a version mismatch between the mongos and the mongod, + // the mongos may fail to pass the role information, causing an error. + Status s(ErrorCodes::IncompatibleAuditMetadata, + "Audit metadata does not include both user and role information."); + appendCommandStatus(result, s); + return; + } + ImpersonationSessionGuard impersonationSession(authSession, + usersFieldIsPresent, + parsedUserNames, + parsedRoleNames); Status status = _checkAuthorization(c, &client, dbname, cmdObj, fromRepl); if (!status.isOK()) { |