summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorAmalia Hawkins <amalia.hawkins@10gen.com>2014-07-21 13:48:43 -0400
committerAmalia Hawkins <amalia.hawkins@10gen.com>2014-07-21 13:48:43 -0400
commitf2d47ee02a94f56b29e1874aebf8ae4dca222d2e (patch)
treeb00f66aa6b23fa728b08c7a5a23aa7e27fc43145 /src/mongo
parentfcab456c204a1c5eccfc3d700337cb5bff0621fc (diff)
downloadmongo-f2d47ee02a94f56b29e1874aebf8ae4dca222d2e.tar.gz
SERVER-12512: Add role-based, selective audit logging.
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/base/error_codes.err1
-rw-r--r--src/mongo/db/audit.cpp16
-rw-r--r--src/mongo/db/audit.h42
-rw-r--r--src/mongo/db/auth/authorization_manager.cpp4
-rw-r--r--src/mongo/db/auth/authorization_session.cpp55
-rw-r--r--src/mongo/db/auth/authorization_session.h37
-rw-r--r--src/mongo/db/auth/user.cpp11
-rw-r--r--src/mongo/db/auth/user.h14
-rw-r--r--src/mongo/db/auth/user_document_parser.cpp31
-rw-r--r--src/mongo/db/auth/user_document_parser.h1
-rw-r--r--src/mongo/db/auth/user_management_commands_parser.h1
-rw-r--r--src/mongo/db/dbcommands.cpp46
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()) {