feat(AclFamily): add acl dryrun command (#1894)

* add acl dryrun command
* add unit tests
This commit is contained in:
Kostas Kyrimis 2023-09-19 19:41:52 +03:00 committed by GitHub
parent b1c0d5c2a9
commit ea589db4db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 8 deletions

View file

@ -31,6 +31,7 @@
#include "server/acl/acl_commands_def.h"
#include "server/acl/acl_log.h"
#include "server/acl/helpers.h"
#include "server/acl/validator.h"
#include "server/command_registry.h"
#include "server/common.h"
#include "server/conn_context.h"
@ -455,6 +456,35 @@ void AclFamily::GetUser(CmdArgList args, ConnectionContext* cntx) {
(*cntx)->SendSimpleString(acl);
}
void AclFamily::DryRun(CmdArgList args, ConnectionContext* cntx) {
auto username = facade::ArgS(args, 0);
const auto registry_with_lock = registry_->GetRegistryWithLock();
const auto& registry = registry_with_lock.registry;
if (!registry.contains(username)) {
auto error = absl::StrCat("User: ", username, " does not exists!");
(*cntx)->SendError(error);
return;
}
ToUpper(&args[1]);
auto command = facade::ArgS(args, 1);
auto* cid = cmd_registry_->Find(command);
if (!cid) {
auto error = absl::StrCat("Command: ", command, " does not exists!");
(*cntx)->SendError(error);
return;
}
const auto& user = registry.find(username)->second;
if (IsUserAllowedToInvokeCommandGeneric(user.AclCategory(), user.AclCommandsRef(), *cid)) {
(*cntx)->SendOk();
return;
}
auto error = absl::StrCat("User: ", username, " is not allowed to execute command: ", command);
(*cntx)->SendError(error);
}
using MemberFunc = void (AclFamily::*)(CmdArgList args, ConnectionContext* cntx);
CommandId::Handler HandlerFunc(AclFamily* acl, MemberFunc f) {
@ -474,6 +504,7 @@ constexpr uint32_t kLog = acl::ADMIN | acl::SLOW | acl::DANGEROUS;
constexpr uint32_t kUsers = acl::ADMIN | acl::SLOW | acl::DANGEROUS;
constexpr uint32_t kCat = acl::SLOW;
constexpr uint32_t kGetUser = acl::ADMIN | acl::SLOW | acl::DANGEROUS;
constexpr uint32_t kDryRun = acl::ADMIN | acl::SLOW | acl::DANGEROUS;
// We can't implement the ACL commands and its respective subcommands LIST, CAT, etc
// the usual way, (that is, one command called ACL which then dispatches to the subcommand
@ -507,6 +538,8 @@ void AclFamily::Register(dfly::CommandRegistry* registry) {
Cat);
*registry << CI{"ACL GETUSER", CO::ADMIN | CO::NOSCRIPT | CO::LOADING, 2, 0, 0, 0, acl::kGetUser}
.HFUNC(GetUser);
*registry << CI{"ACL DRYRUN", CO::ADMIN | CO::NOSCRIPT | CO::LOADING, 3, 0, 0, 0, acl::kDryRun}
.HFUNC(DryRun);
cmd_registry_ = registry;
}

View file

@ -42,6 +42,7 @@ class AclFamily final {
void Users(CmdArgList args, ConnectionContext* cntx);
void Cat(CmdArgList args, ConnectionContext* cntx);
void GetUser(CmdArgList args, ConnectionContext* cntx);
void DryRun(CmdArgList args, ConnectionContext* cntx);
// Helper function that updates all open connections and their
// respective ACL fields on all the available proactor threads

View file

@ -242,4 +242,34 @@ TEST_F(AclFamilyTest, TestGetUser) {
EXPECT_THAT(kvec[5], "+@STRING +HSET");
}
TEST_F(AclFamilyTest, TestDryRun) {
TestInitAclFam();
auto resp = Run({"ACL", "DRYRUN"});
EXPECT_THAT(resp, ErrArg("ERR wrong number of arguments for 'acl dryrun' command"));
resp = Run({"ACL", "DRYRUN", "default"});
EXPECT_THAT(resp, ErrArg("ERR wrong number of arguments for 'acl dryrun' command"));
resp = Run({"ACL", "DRYRUN", "default", "get", "more"});
EXPECT_THAT(resp, ErrArg("ERR wrong number of arguments for 'acl dryrun' command"));
resp = Run({"ACL", "DRYRUN", "kostas", "more"});
EXPECT_THAT(resp, ErrArg("ERR User: kostas does not exists!"));
resp = Run({"ACL", "DRYRUN", "default", "nope"});
EXPECT_THAT(resp, ErrArg("ERR Command: NOPE does not exists!"));
resp = Run({"ACL", "DRYRUN", "default", "SET"});
EXPECT_THAT(resp, "OK");
resp = Run({"ACL", "SETUSER", "kostas", "+GET"});
EXPECT_THAT(resp, "OK");
resp = Run({"ACL", "DRYRUN", "kostas", "GET"});
EXPECT_THAT(resp, "OK");
resp = Run({"ACL", "DRYRUN", "kostas", "SET"});
EXPECT_THAT(resp, ErrArg("ERR User: kostas is not allowed to execute command: SET"));
}
} // namespace dfly

View file

@ -13,13 +13,8 @@ namespace dfly::acl {
[[nodiscard]] bool IsUserAllowedToInvokeCommand(const ConnectionContext& cntx,
const facade::CommandId& id) {
const auto cat_credentials = id.acl_categories();
const size_t index = id.GetFamily();
const uint64_t command_mask = id.GetBitIndex();
DCHECK_LT(index, cntx.acl_commands.size());
const bool is_authed = (cntx.acl_categories & cat_credentials) != 0 ||
(cntx.acl_commands[index] & command_mask) != 0;
const bool is_authed =
IsUserAllowedToInvokeCommandGeneric(cntx.acl_categories, cntx.acl_commands, id);
if (!is_authed) {
auto& log = ServerState::tlocal()->acl_log;
@ -30,4 +25,14 @@ namespace dfly::acl {
return is_authed;
}
[[nodiscard]] bool IsUserAllowedToInvokeCommandGeneric(uint32_t acl_cat,
const std::vector<uint64_t>& acl_commands,
const facade::CommandId& id) {
const auto cat_credentials = id.acl_categories();
const size_t index = id.GetFamily();
const uint64_t command_mask = id.GetBitIndex();
DCHECK_LT(index, acl_commands.size());
return (acl_cat & cat_credentials) != 0 || (acl_commands[index] & command_mask) != 0;
}
} // namespace dfly::acl

View file

@ -9,6 +9,10 @@
namespace dfly::acl {
bool IsUserAllowedToInvokeCommandGeneric(uint32_t acl_cat,
const std::vector<uint64_t>& acl_commands,
const facade::CommandId& id);
bool IsUserAllowedToInvokeCommand(const ConnectionContext& cntx, const facade::CommandId& id);
}
} // namespace dfly::acl