8000 Bug fix/user manager version race (#14382) by mchacki · Pull Request #14392 · arangodb/arangodb · GitHub
[go: up one dir, main page]

Skip to content

Bug fix/user manager version race (#14382) #14392

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
v3.7.13 (XXXX-XX-XX)
--------------------

* Fixed ES-863: reloading of users within the Cluster.
If a Coordinator is asked to reload its users (e.g. by the UserManager in
Foxx, it is also possible to do via API, but this is internal and on purpose
not documented, so unlikely that it is used), in concurrency with user
management updates there is a chance that the reload is not correctly
performed on this coordinator. It may have missed the last update locally,
causing one user to have an older state. It will be fixed on the next
modification of any other users/permissions. Unfortunately this bug can
cascade and when hit again, the coordinator can now be off by two updates.
In DC2DC this situation is more likely to happen on the target datacenter,
causing this datacenter to have other users/permissions than the source one.

* Fix BTS-446: When finding a not yet fully initialized agency, do not
immediately fatal exit. Keep trying for (very generous) 5 minutes.

Expand Down
47 changes: 29 additions & 18 deletions arangod/Auth/UserManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,7 @@ auth::UserManager::UserManager(application_features::ApplicationServer& server)
: _server(server), _globalVersion(1), _internalVersion(0) {}
#else
auth::UserManager::UserManager(application_features::ApplicationServer& server)
: _server(server),
_globalVersion(1),
_internalVersion(0),
_authHandler(nullptr) {}
: _server(server), _globalVersion(1), _internalVersion(0), _authHandler(nullptr) {}

auth::UserManager::UserManager(application_features::ApplicationServer& server,
std::unique_ptr<auth::Handler> handler)
Expand Down Expand Up @@ -129,7 +126,8 @@ static std::shared_ptr<VPackBuilder> QueryAllUsers(application_features::Applica
if (vocbase == nullptr) {
LOG_TOPIC("b8c47", DEBUG, arangodb::Logger::AUTHENTICATION)
<< "system database is unknown";
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL, "system database is unknown");
THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_INTERNAL,
"system database is unknown");
}

// we cannot set this execution context, otherwise the transaction
Expand All @@ -138,8 +136,8 @@ static std::shared_ptr<VPackBuilder> QueryAllUsers(application_features::Applica
std::string const queryStr("FOR user IN _users RETURN user");
auto emptyBuilder = std::make_shared<VPackBuilder>();
arangodb::aql::Query query(transaction::StandaloneContext::Create(*vocbase),
arangodb::aql::QueryString(queryStr),
emptyBuilder, emptyBuilder);
arangodb::aql::QueryString(queryStr), emptyBuilder,
emptyBuilder);

query.queryOptions().cache = false;
query.queryOptions().ttl = 30;
Expand All @@ -156,7 +154,8 @@ static std::shared_ptr<VPackBuilder> QueryAllUsers(application_features::Applica
THROW_ARANGO_EXCEPTION(TRI_ERROR_REQUEST_CANCELED);
}
THROW_ARANGO_EXCEPTION_MESSAGE(queryResult.result.errorNumber(),
"Error executing user query: " + queryResult.result.errorMessage());
"Error executing user query: " +
queryResult.result.errorMessage());
}

VPackSlice usersSlice = queryResult.data->slice();
Expand Down Expand Up @@ -200,6 +199,13 @@ void auth::UserManager::loadFromDB() {
return;
}

TRI_IF_FAILURE("UserManager::performDBLookup") {
// Used in GTest. It is used to identify
// if the UserManager would have updated it's
// cache in a specific situation.
THROW_ARANGO_EXCEPTION(TRI_ERROR_DEBUG);
}

try {
std::shared_ptr<VPackBuilder> builder = QueryAllUsers(_server);
if (builder) {
Expand Down Expand Up @@ -325,7 +331,7 @@ Result auth::UserManager::storeUserInternal(auth::User const& entry, bool replac
// user was outdated, we should trigger a reload
triggerLocalReload();
LOG_TOPIC("cf922", DEBUG, Logger::AUTHENTICATION)
<< "Cannot update user : '" << res.errorMessage() << "'";
<< "Cannot update user : '" << res.errorMessage() << "'";
}
}
return res;
Expand All @@ -343,7 +349,8 @@ void auth::UserManager::createRootUser() {
WRITE_LOCKER(writeGuard, _userCacheLock); // must be second
UserMap::iterator const& it = _userCache.find("root");
if (it != _userCache.end()) {
LOG_TOPIC("bbc97", TRACE, Logger::AUTHENTICATION) << "\"root\" already exists";
LOG_TOPIC("bbc97", TRACE, Logger::AUTHENTICATION)
<< "\"root\" already exists";
return;
}
TRI_ASSERT(_userCache.empty());
Expand All @@ -368,7 +375,8 @@ void auth::UserManager::createRootUser() {
<< "unable to create user \"root\": " << ex.what();
} catch (...) {
// No action
LOG_TOPIC("268eb", ERR, Logger::AUTHENTICATION) << "unable to create user \"root\"";
LOG_TOPIC("268eb", ERR, Logger::AUTHENTICATION)
<< "unable to create user \"root\"";
}

triggerGlobalReload();
Expand Down Expand Up @@ -490,20 +498,20 @@ Result auth::UserManager::enumerateUsers(std::function<bool(auth::User&)>&& func
}
}
}

bool triggerUpdate = !toUpdate.empty();

Result res;
do {
auto it = toUpdate.begin();
while(it != toUpdate.end()) {
while (it != toUpdate.end()) {
WRITE_LOCKER(writeGuard, _userCacheLock);
res = storeUserInternal(*it, /*replace*/true);
res = storeUserInternal(*it, /*replace*/ true);

if (res.is(TRI_ERROR_ARANGO_CONFLICT) && retryOnConflict) {
res.reset();
writeGuard.unlock();
loadFromDB(); // should be noop iff nothing changed
loadFromDB(); // should be noop iff nothing changed
writeGuard.lock();
UserMap::iterator it2 = _userCache.find(it->username());
if (it2 != _userCache.end()) {
Expand Down Expand Up @@ -715,7 +723,8 @@ bool auth::UserManager::checkPassword(std::string const& username, std::string c
AuthenticationFeature* af = AuthenticationFeature::instance();
if (it != _userCache.end() && (it->second.source() == auth::Source::Local)) {
if (af != nullptr && !af->localAuthentication()) {
LOG_TOPIC("d3220", DEBUG, Logger::AUTHENTICATION) << "Local users are forbidden";
LOG_TOPIC("d3220", DEBUG, Logger::AUTHENTICATION)
<< "Local users are forbidden";
return false;
}
auth::User const& user = it->second;
Expand Down Expand Up @@ -799,10 +808,12 @@ auth::Level auth::UserManager::collectionAuthLevel(std::string const& user,
return level;
}

#ifdef ARANGODB_USE_GOOGLE_TESTS
/// Only used for testing
void auth::UserManager::setAuthInfo(auth::UserMap const& newMap) {
MUTEX_LOCKER(guard, _loadFromDBLock); // must be first
WRITE_LOCKER(writeGuard, _userCacheLock); // must be second
_userCache = newMap;
_internalVersion.store(_globalVersion.load());
}
#endif
7 changes: 4 additions & 3 deletions arangod/Auth/UserManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class UserManager {

/// @brief reload user cache and token caches
void triggerLocalReload() {
_globalVersion.fetch_add(1, std::memory_order_release);
_internalVersion.store(0, std::memory_order_release);
}

/// @brief used for caching
Expand All @@ -104,8 +104,7 @@ class UserManager {
bool active, velocypack::Slice extras);

/// Enumerate list of all users
Result enumerateUsers(std::function<bool(auth::User&)>&&,
bool retryOnConflict);
Result enumerateUsers(std::function<bool(auth::User&)>&&, bool retryOnConflict);
/// Update specific user
Result updateUser(std::string const& user, UserCallback&&);
/// Access user without modifying it
Expand Down Expand Up @@ -135,9 +134,11 @@ class UserManager {
auth::Level collectionAuthLevel(std::string const& username, std::string const& dbname,
std::string const& coll, bool configured = false);

#ifdef ARANGODB_USE_GOOGLE_TESTS
/// Overwrite internally cached permissions, only use
/// for testing purposes
void setAuthInfo(auth::UserMap const& userEntryMap);
#endif

#ifdef USE_ENTERPRISE

Expand Down
Loading
0