mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-11 18:35:46 +02:00
chore: remove locks from acl validation in the hot path (#1765)
* remove locking the registry when Validating users * deep copies the acl categories in the connection context * streamline updates to the acl categories via the proactor pool
This commit is contained in:
parent
7492550f12
commit
866b9afe58
11 changed files with 106 additions and 23 deletions
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "server/acl/acl_family.h"
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
@ -12,6 +14,7 @@
|
|||
#include "absl/strings/match.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "core/overloaded.h"
|
||||
#include "facade/dragonfly_connection.h"
|
||||
#include "facade/facade_types.h"
|
||||
#include "server/acl/acl_commands_def.h"
|
||||
#include "server/command_registry.h"
|
||||
|
@ -157,12 +160,31 @@ std::variant<User::UpdateRequest, ErrorReply> ParseAclSetUser(CmdArgList args) {
|
|||
|
||||
} // namespace
|
||||
|
||||
void AclFamily::StreamUpdatesToAllProactorConnections(std::string_view user, uint32_t update_cat) {
|
||||
auto update_cb = [user, update_cat]([[maybe_unused]] size_t id, util::Connection* conn) {
|
||||
DCHECK(conn);
|
||||
auto connection = static_cast<facade::Connection*>(conn);
|
||||
auto ctx = static_cast<ConnectionContext*>(connection->cntx());
|
||||
if (ctx && user == ctx->authed_username) {
|
||||
ctx->acl_categories = update_cat;
|
||||
}
|
||||
};
|
||||
|
||||
if (main_listener_) {
|
||||
main_listener_->TraverseConnections(update_cb);
|
||||
}
|
||||
}
|
||||
|
||||
void AclFamily::SetUser(CmdArgList args, ConnectionContext* cntx) {
|
||||
std::string_view username = facade::ToSV(args[0]);
|
||||
auto req = ParseAclSetUser(args.subspan(1));
|
||||
auto error_case = [cntx](ErrorReply&& error) { (*cntx)->SendError(error); };
|
||||
auto update_case = [username, cntx](User::UpdateRequest&& req) {
|
||||
ServerState::tlocal()->user_registry->MaybeAddAndUpdate(username, std::move(req));
|
||||
auto update_case = [username, cntx, this](User::UpdateRequest&& req) {
|
||||
auto& registry = ServerState::tlocal()->user_registry;
|
||||
auto user_with_lock = registry->MaybeAddAndUpdateWithLock(username, std::move(req));
|
||||
if (user_with_lock.exists) {
|
||||
StreamUpdatesToAllProactorConnections(username, user_with_lock.user.AclCategory());
|
||||
}
|
||||
(*cntx)->SendOk();
|
||||
};
|
||||
|
||||
|
@ -171,8 +193,15 @@ void AclFamily::SetUser(CmdArgList args, ConnectionContext* cntx) {
|
|||
|
||||
using CI = dfly::CommandId;
|
||||
|
||||
#define HFUNC(x) SetHandler(&AclFamily::x)
|
||||
using MemberFunc = void (AclFamily::*)(CmdArgList args, ConnectionContext* cntx);
|
||||
|
||||
inline CommandId::Handler HandlerFunc(AclFamily* acl, MemberFunc f) {
|
||||
return [=](CmdArgList args, ConnectionContext* cntx) { return (acl->*f)(args, cntx); };
|
||||
}
|
||||
|
||||
#define HFUNC(x) SetHandler(HandlerFunc(this, &AclFamily::x))
|
||||
|
||||
constexpr uint32_t kAcl = acl::CONNECTION;
|
||||
constexpr uint32_t kList = acl::ADMIN | acl::SLOW | acl::DANGEROUS;
|
||||
constexpr uint32_t kSetUser = acl::ADMIN | acl::SLOW | acl::DANGEROUS;
|
||||
|
||||
|
@ -184,7 +213,7 @@ constexpr uint32_t kSetUser = acl::ADMIN | acl::SLOW | acl::DANGEROUS;
|
|||
// easy to handle that case explicitly in `DispatchCommand`.
|
||||
|
||||
void AclFamily::Register(dfly::CommandRegistry* registry) {
|
||||
*registry << CI{"ACL", CO::NOSCRIPT | CO::LOADING, 0, 0, 0, 0, acl::kList}.HFUNC(Acl);
|
||||
*registry << CI{"ACL", CO::NOSCRIPT | CO::LOADING, 0, 0, 0, 0, acl::kAcl}.HFUNC(Acl);
|
||||
*registry << CI{"ACL LIST", CO::ADMIN | CO::NOSCRIPT | CO::LOADING, 1, 0, 0, 0, acl::kList}.HFUNC(
|
||||
List);
|
||||
*registry << CI{"ACL SETUSER", CO::ADMIN | CO::NOSCRIPT | CO::LOADING, -2, 0, 0, 0, acl::kSetUser}
|
||||
|
@ -193,4 +222,8 @@ void AclFamily::Register(dfly::CommandRegistry* registry) {
|
|||
|
||||
#undef HFUNC
|
||||
|
||||
void AclFamily::Init(facade::Listener* main_listener) {
|
||||
main_listener_ = main_listener;
|
||||
}
|
||||
|
||||
} // namespace dfly::acl
|
||||
|
|
|
@ -4,22 +4,38 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "facade/dragonfly_listener.h"
|
||||
#include "helio/util/proactor_pool.h"
|
||||
#include "server/common.h"
|
||||
|
||||
namespace dfly {
|
||||
|
||||
class ConnectionContext;
|
||||
class CommandRegistry;
|
||||
|
||||
namespace acl {
|
||||
|
||||
class AclFamily {
|
||||
class AclFamily final {
|
||||
public:
|
||||
static void Register(CommandRegistry* registry);
|
||||
AclFamily() = default;
|
||||
|
||||
void Register(CommandRegistry* registry);
|
||||
void Init(facade::Listener* listener);
|
||||
|
||||
private:
|
||||
static void Acl(CmdArgList args, ConnectionContext* cntx);
|
||||
static void List(CmdArgList args, ConnectionContext* cntx);
|
||||
static void SetUser(CmdArgList args, ConnectionContext* cntx);
|
||||
void Acl(CmdArgList args, ConnectionContext* cntx);
|
||||
void List(CmdArgList args, ConnectionContext* cntx);
|
||||
void SetUser(CmdArgList args, ConnectionContext* cntx);
|
||||
|
||||
// Helper function that updates all open connections and their
|
||||
// respective ACL fields on all the available proactor threads
|
||||
void StreamUpdatesToAllProactorConnections(std::string_view user, uint32_t update_cat);
|
||||
|
||||
facade::Listener* main_listener_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace acl
|
||||
|
|
|
@ -55,9 +55,9 @@ bool UserRegistry::AuthUser(std::string_view username, std::string_view password
|
|||
return user->second.IsActive() && user->second.HasPassword(password);
|
||||
}
|
||||
|
||||
UserRegistry::RegistryViewWithLock::RegistryViewWithLock(std::shared_lock<util::SharedMutex> mu,
|
||||
UserRegistry::RegistryViewWithLock::RegistryViewWithLock(std::shared_lock<util::SharedMutex> lk,
|
||||
const RegistryType& registry)
|
||||
: registry(registry), registry_mu_(std::move(mu)) {
|
||||
: registry(registry), registry_lk_(std::move(lk)) {
|
||||
}
|
||||
|
||||
UserRegistry::RegistryViewWithLock UserRegistry::GetRegistryWithLock() const {
|
||||
|
@ -65,4 +65,18 @@ UserRegistry::RegistryViewWithLock UserRegistry::GetRegistryWithLock() const {
|
|||
return {std::move(lock), registry_};
|
||||
}
|
||||
|
||||
UserRegistry::UserViewWithLock::UserViewWithLock(std::shared_lock<util::SharedMutex> lk,
|
||||
const User& user, bool exists)
|
||||
: user(user), exists(exists), registry_lk_(std::move(lk)) {
|
||||
}
|
||||
|
||||
UserRegistry::UserViewWithLock UserRegistry::MaybeAddAndUpdateWithLock(std::string_view username,
|
||||
User::UpdateRequest req) {
|
||||
std::shared_lock<util::SharedMutex> lock(mu_);
|
||||
const bool exists = registry_.contains(username);
|
||||
auto& user = registry_[username];
|
||||
user.Update(std::move(req));
|
||||
return {std::move(lock), user, exists};
|
||||
}
|
||||
|
||||
} // namespace dfly::acl
|
||||
|
|
|
@ -30,7 +30,6 @@ class UserRegistry {
|
|||
// If the user with name `username` does not exist, it's added in the store with
|
||||
// the exact fields found in req
|
||||
// If the user exists, the bitfields are updated with a `logical and` operation
|
||||
// TODO change return time to communicate back results to acl commands
|
||||
void MaybeAddAndUpdate(std::string_view username, User::UpdateRequest req);
|
||||
|
||||
// Acquires a write lock on mu_
|
||||
|
@ -55,16 +54,29 @@ class UserRegistry {
|
|||
// Helper class for accessing the registry with a ReadLock outside the scope of UserRegistry
|
||||
class RegistryViewWithLock {
|
||||
public:
|
||||
RegistryViewWithLock(std::shared_lock<util::SharedMutex> mu, const RegistryType& registry);
|
||||
RegistryViewWithLock(std::shared_lock<util::SharedMutex> lk, const RegistryType& registry);
|
||||
const RegistryType& registry;
|
||||
|
||||
private:
|
||||
std::shared_lock<util::SharedMutex> registry_mu_;
|
||||
std::shared_lock<util::SharedMutex> registry_lk_;
|
||||
};
|
||||
|
||||
// Helper function used for printing users via ACL LIST
|
||||
RegistryViewWithLock GetRegistryWithLock() const;
|
||||
|
||||
// Helper class for accessing a user with a ReadLock outside the scope of UserRegistry
|
||||
class UserViewWithLock {
|
||||
public:
|
||||
UserViewWithLock(std::shared_lock<util::SharedMutex> lk, const User& user, bool exists);
|
||||
const User& user;
|
||||
const bool exists;
|
||||
|
||||
private:
|
||||
std::shared_lock<util::SharedMutex> registry_lk_;
|
||||
};
|
||||
|
||||
UserViewWithLock MaybeAddAndUpdateWithLock(std::string_view username, User::UpdateRequest req);
|
||||
|
||||
private:
|
||||
RegistryType registry_;
|
||||
// TODO add abseil mutex attributes
|
||||
|
|
|
@ -4,16 +4,12 @@
|
|||
|
||||
#include "server/acl/validator.h"
|
||||
|
||||
#include "server/server_state.h"
|
||||
|
||||
namespace dfly::acl {
|
||||
|
||||
[[nodiscard]] bool IsUserAllowedToInvokeCommand(const ConnectionContext& cntx,
|
||||
const facade::CommandId& id) {
|
||||
auto& registry = *ServerState::tlocal()->user_registry;
|
||||
auto credentials = registry.GetCredentials(cntx.authed_username);
|
||||
auto command_credentials = id.acl_categories();
|
||||
return (credentials.acl_categories & command_credentials) != 0;
|
||||
return (cntx.acl_categories & command_credentials) != 0;
|
||||
}
|
||||
|
||||
} // namespace dfly::acl
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "facade/command_id.h"
|
||||
#include "server/conn_context.h"
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <absl/container/fixed_array.h>
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
|
||||
#include "acl/acl_commands_def.h"
|
||||
#include "core/fibers.h"
|
||||
#include "facade/conn_context.h"
|
||||
#include "facade/reply_capture.h"
|
||||
|
@ -197,6 +198,7 @@ class ConnectionContext : public facade::ConnectionContext {
|
|||
FlowInfo* replication_flow;
|
||||
|
||||
std::string authed_username{"default"};
|
||||
uint32_t acl_categories{acl::ALL};
|
||||
|
||||
private:
|
||||
void EnableMonitoring(bool enable) {
|
||||
|
|
|
@ -330,6 +330,9 @@ bool RunEngine(ProactorPool* pool, AcceptServer* acceptor) {
|
|||
Listener* main_listener = nullptr;
|
||||
|
||||
std::vector<facade::Listener*> listeners;
|
||||
// If we ever add a new listener, plz don't change this,
|
||||
// we depend on tcp listener to be at the front since we later
|
||||
// need to pass it to the AclFamily::Init
|
||||
if (!tcp_disabled) {
|
||||
main_listener = new Listener{Protocol::REDIS, &service};
|
||||
listeners.push_back(main_listener);
|
||||
|
|
|
@ -664,7 +664,12 @@ void Service::Init(util::AcceptServer* acceptor, std::vector<facade::Listener*>
|
|||
}
|
||||
|
||||
shard_set->Init(shard_num, !opts.disable_time_update);
|
||||
|
||||
const auto tcp_disabled = GetFlag(FLAGS_port) == 0u;
|
||||
// We assume that listeners.front() is the main_listener
|
||||
// see dfly_main RunEngine
|
||||
if (!tcp_disabled && !listeners.empty()) {
|
||||
acl_family_.Init(listeners.front());
|
||||
}
|
||||
request_latency_usec.Init(&pp_);
|
||||
StringFamily::Init(&pp_);
|
||||
GenericFamily::Init(&pp_);
|
||||
|
@ -2132,7 +2137,7 @@ void Service::RegisterCommands() {
|
|||
BitOpsFamily::Register(®istry_);
|
||||
HllFamily::Register(®istry_);
|
||||
SearchFamily::Register(®istry_);
|
||||
acl::AclFamily::Register(®istry_);
|
||||
acl_family_.Register(®istry_);
|
||||
|
||||
server_family_.Register(®istry_);
|
||||
cluster_family_.Register(®istry_);
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "base/varz_value.h"
|
||||
#include "core/interpreter.h"
|
||||
#include "facade/service_interface.h"
|
||||
#include "server/acl/acl_family.h"
|
||||
#include "server/acl/user_registry.h"
|
||||
#include "server/cluster/cluster_family.h"
|
||||
#include "server/command_registry.h"
|
||||
|
@ -165,6 +166,7 @@ class Service : public facade::ServiceInterface {
|
|||
util::ProactorPool& pp_;
|
||||
|
||||
acl::UserRegistry user_registry_;
|
||||
acl::AclFamily acl_family_;
|
||||
ServerFamily server_family_;
|
||||
ClusterFamily cluster_family_;
|
||||
CommandRegistry registry_;
|
||||
|
|
|
@ -1023,6 +1023,8 @@ void ServerFamily::Auth(CmdArgList args, ConnectionContext* cntx) {
|
|||
auto is_authorized = registry->AuthUser(username, password);
|
||||
if (is_authorized) {
|
||||
cntx->authed_username = username;
|
||||
auto cred = registry->GetCredentials(username);
|
||||
cntx->acl_categories = cred.acl_categories;
|
||||
return (*cntx)->SendOk();
|
||||
}
|
||||
return (*cntx)->SendError(absl::StrCat("Could not authorize user: ", username));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue