Merge 7cd487c6eba9b8d10664fcc20d21b5d21e6271f3 into 26c6f1af9b29d525831c7fa9840ab3e47ed7b700

This commit is contained in:
Rain Valentine 2025-02-01 10:49:46 -08:00 committed by GitHub
commit b297783750
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 152 additions and 129 deletions

175
src/acl.c
View File

@ -152,17 +152,6 @@ struct ACLUserFlag {
{NULL, 0} /* Terminator. */
};
struct ACLSelectorFlags {
const char *name;
uint64_t flag;
} ACLSelectorFlags[] = {
/* Note: the order here dictates the emitted order at ACLDescribeUser */
{"allkeys", SELECTOR_FLAG_ALLKEYS},
{"allchannels", SELECTOR_FLAG_ALLCHANNELS},
{"allcommands", SELECTOR_FLAG_ALLCOMMANDS},
{NULL, 0} /* Terminator. */
};
/* ACL selectors are private and not exposed outside of acl.c. */
typedef struct {
uint32_t flags; /* See SELECTOR_FLAG_* */
@ -193,11 +182,11 @@ typedef struct {
* is used to regenerate the original ACL string for display. */
} aclSelector;
void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id);
void ACLResetFirstArgs(aclSelector *selector);
void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub);
void ACLFreeLogEntry(void *le);
int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen);
static void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id);
static void ACLResetFirstArgs(aclSelector *selector);
static void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub);
static void ACLFreeLogEntry(void *le);
static int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen);
/* The length of the string representation of a hashed password. */
#define HASH_PASSWORD_LEN (SHA256_BLOCK_SIZE * 2)
@ -211,7 +200,7 @@ int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen);
* information about the nature of the strings just monitoring the execution
* time of the function. Note: The two strings must be the same length.
*/
int time_independent_strcmp(char *a, char *b, int len) {
static int time_independent_strcmp(char *a, char *b, int len) {
int diff = 0;
for (int j = 0; j < len; j++) {
diff |= (a[j] ^ b[j]);
@ -221,7 +210,7 @@ int time_independent_strcmp(char *a, char *b, int len) {
/* Given an SDS string, returns the SHA256 hex representation as a
* new SDS string. */
sds ACLHashPassword(unsigned char *cleartext, size_t len) {
static sds ACLHashPassword(unsigned char *cleartext, size_t len) {
SHA256_CTX ctx;
unsigned char hash[SHA256_BLOCK_SIZE];
char hex[HASH_PASSWORD_LEN];
@ -240,7 +229,7 @@ sds ACLHashPassword(unsigned char *cleartext, size_t len) {
/* Given a hash and the hash length, returns C_OK if it is a valid password
* hash, or C_ERR otherwise. */
int ACLCheckPasswordHash(unsigned char *hash, int hashlen) {
static int ACLCheckPasswordHash(unsigned char *hash, int hashlen) {
if (hashlen != HASH_PASSWORD_LEN) {
return C_ERR;
}
@ -266,7 +255,7 @@ int ACLCheckPasswordHash(unsigned char *hash, int hashlen) {
* ACL rules, presentation on ACL list, and to avoid subtle security bugs
* that may arise from parsing the rules in presence of escapes.
* The function returns 0 if the string has no spaces. */
int ACLStringHasSpaces(const char *s, size_t len) {
static int ACLStringHasSpaces(const char *s, size_t len) {
for (size_t i = 0; i < len; i++) {
if (isspace(s[i]) || s[i] == 0) return 1;
}
@ -287,19 +276,19 @@ uint64_t ACLGetCommandCategoryFlagByName(const char *name) {
/* Method for searching for a user within a list of user definitions. The
* list contains an array of user arguments, and we are only
* searching the first argument, the username, for a match. */
int ACLListMatchLoadedUser(void *definition, void *user) {
static int ACLListMatchLoadedUser(void *definition, void *user) {
sds *user_definition = definition;
return sdscmp(user_definition[0], user) == 0;
}
/* Method for passwords/pattern comparison used for the user->passwords list
* so that we can search for items with listSearchKey(). */
int ACLListMatchSds(void *a, void *b) {
static int ACLListMatchSds(void *a, void *b) {
return sdscmp(a, b) == 0;
}
/* Method to duplicate list elements from ACL users password/patterns lists. */
void *ACLListDupSds(void *item) {
static void *ACLListDupSds(void *item) {
return sdsdup(item);
}
@ -311,7 +300,7 @@ typedef struct {
} keyPattern;
/* Create a new key pattern. */
keyPattern *ACLKeyPatternCreate(sds pattern, int flags) {
static keyPattern *ACLKeyPatternCreate(sds pattern, int flags) {
keyPattern *new = (keyPattern *)zmalloc(sizeof(keyPattern));
new->pattern = pattern;
new->flags = flags;
@ -319,31 +308,31 @@ keyPattern *ACLKeyPatternCreate(sds pattern, int flags) {
}
/* Free a key pattern and internal structures. */
void ACLKeyPatternFree(keyPattern *pattern) {
static void ACLKeyPatternFree(keyPattern *pattern) {
sdsfree(pattern->pattern);
zfree(pattern);
}
/* Method for passwords/pattern comparison used for the user->passwords list
* so that we can search for items with listSearchKey(). */
int ACLListMatchKeyPattern(void *a, void *b) {
static int ACLListMatchKeyPattern(void *a, void *b) {
return sdscmp(((keyPattern *)a)->pattern, ((keyPattern *)b)->pattern) == 0;
}
/* Method to free list elements from ACL users password/patterns lists. */
void ACLListFreeKeyPattern(void *item) {
static void ACLListFreeKeyPattern(void *item) {
ACLKeyPatternFree(item);
}
/* Method to duplicate list elements from ACL users password/patterns lists. */
void *ACLListDupKeyPattern(void *item) {
static void *ACLListDupKeyPattern(void *item) {
keyPattern *old = (keyPattern *)item;
return ACLKeyPatternCreate(sdsdup(old->pattern), old->flags);
}
/* Append the string representation of a key pattern onto the
* provided base string. */
sds sdsCatPatternString(sds base, keyPattern *pat) {
static sds sdsCatPatternString(sds base, keyPattern *pat) {
if (pat->flags == ACL_ALL_PERMISSION) {
base = sdscatlen(base, "~", 1);
} else if (pat->flags == ACL_READ_PERMISSION) {
@ -358,7 +347,7 @@ sds sdsCatPatternString(sds base, keyPattern *pat) {
/* Create an empty selector with the provided set of initial
* flags. The selector will be default have no permissions. */
aclSelector *ACLCreateSelector(int flags) {
static aclSelector *ACLCreateSelector(int flags) {
aclSelector *selector = zmalloc(sizeof(aclSelector));
selector->flags = flags | server.acl_pubsub_default;
selector->patterns = listCreate();
@ -378,7 +367,7 @@ aclSelector *ACLCreateSelector(int flags) {
}
/* Cleanup the provided selector, including all interior structures. */
void ACLFreeSelector(aclSelector *selector) {
static void ACLFreeSelector(aclSelector *selector) {
listRelease(selector->patterns);
listRelease(selector->channels);
sdsfree(selector->command_rules);
@ -387,7 +376,7 @@ void ACLFreeSelector(aclSelector *selector) {
}
/* Create an exact copy of the provided selector. */
aclSelector *ACLCopySelector(aclSelector *src) {
static aclSelector *ACLCopySelector(aclSelector *src) {
aclSelector *dst = zmalloc(sizeof(aclSelector));
dst->flags = src->flags;
dst->patterns = listDup(src->patterns);
@ -408,19 +397,19 @@ aclSelector *ACLCopySelector(aclSelector *src) {
}
/* List method for freeing a selector */
void ACLListFreeSelector(void *a) {
static void ACLListFreeSelector(void *a) {
ACLFreeSelector((aclSelector *)a);
}
/* List method for duplicating a selector */
void *ACLListDuplicateSelector(void *src) {
static void *ACLListDuplicateSelector(void *src) {
return ACLCopySelector((aclSelector *)src);
}
/* All users have an implicit root selector which
* provides backwards compatibility to the old ACLs-
* permissions. */
aclSelector *ACLUserGetRootSelector(user *u) {
static aclSelector *ACLUserGetRootSelector(user *u) {
serverAssert(listLength(u->selectors));
aclSelector *s = (aclSelector *)listNodeValue(listFirst(u->selectors));
serverAssert(s->flags & SELECTOR_FLAG_ROOT);
@ -432,7 +421,7 @@ aclSelector *ACLUserGetRootSelector(user *u) {
* the structure representing the user.
*
* If the user with such name already exists NULL is returned. */
user *ACLCreateUser(const char *name, size_t namelen) {
static user *ACLCreateUser(const char *name, size_t namelen) {
if (raxFind(Users, (unsigned char *)name, namelen, NULL)) return NULL;
user *u = zmalloc(sizeof(*u));
u->name = sdsnewlen(name, namelen);
@ -523,7 +512,7 @@ void ACLFreeUserAndKillClients(user *u) {
/* Copy the user ACL rules from the source user 'src' to the destination
* user 'dst' so that at the end of the process they'll have exactly the
* same rules (but the names will continue to be the original ones). */
void ACLCopyUser(user *dst, user *src) {
static void ACLCopyUser(user *dst, user *src) {
listRelease(dst->passwords);
listRelease(dst->selectors);
dst->passwords = listDup(src->passwords);
@ -545,7 +534,7 @@ void ACLCopyUser(user *dst, user *src) {
* so that user->allowed_commands[word]&bit will identify that specific
* bit. The function returns C_ERR in case the specified ID overflows
* the bitmap in the user representation. */
int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
static int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
if (id >= USER_COMMAND_BITS_COUNT) return C_ERR;
*word = id / sizeof(uint64_t) / 8;
*bit = 1ULL << (id % (sizeof(uint64_t) * 8));
@ -559,7 +548,7 @@ int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
*
* If the bit overflows the user internal representation, zero is returned
* in order to disallow the execution of the command in such edge case. */
int ACLGetSelectorCommandBit(const aclSelector *selector, unsigned long id) {
static int ACLGetSelectorCommandBit(const aclSelector *selector, unsigned long id) {
uint64_t word, bit;
if (ACLGetCommandBitCoordinates(id, &word, &bit) == C_ERR) return 0;
return (selector->allowed_commands[word] & bit) != 0;
@ -568,7 +557,7 @@ int ACLGetSelectorCommandBit(const aclSelector *selector, unsigned long id) {
/* When +@all or allcommands is given, we set a reserved bit as well that we
* can later test, to see if the user has the right to execute "future commands",
* that is, commands loaded later via modules. */
int ACLSelectorCanExecuteFutureCommands(aclSelector *selector) {
static int ACLSelectorCanExecuteFutureCommands(aclSelector *selector) {
return ACLGetSelectorCommandBit(selector, USER_COMMAND_BITS_COUNT - 1);
}
@ -577,7 +566,7 @@ int ACLSelectorCanExecuteFutureCommands(aclSelector *selector) {
* is performed. As a side effect of calling this function with a value of
* zero, the user flag ALLCOMMANDS is cleared since it is no longer possible
* to skip the command bit explicit test. */
void ACLSetSelectorCommandBit(aclSelector *selector, unsigned long id, int value) {
static void ACLSetSelectorCommandBit(aclSelector *selector, unsigned long id, int value) {
uint64_t word, bit;
if (ACLGetCommandBitCoordinates(id, &word, &bit) == C_ERR) return;
if (value) {
@ -591,7 +580,7 @@ void ACLSetSelectorCommandBit(aclSelector *selector, unsigned long id, int value
/* Remove a rule from the retained command rules. Always match rules
* verbatim, but also remove subcommand rules if we are adding or removing the
* entire command. */
void ACLSelectorRemoveCommandRule(aclSelector *selector, sds new_rule) {
static void ACLSelectorRemoveCommandRule(aclSelector *selector, sds new_rule) {
size_t new_len = sdslen(new_rule);
char *existing_rule = selector->command_rules;
@ -637,7 +626,7 @@ void ACLSelectorRemoveCommandRule(aclSelector *selector, sds new_rule) {
/* This function is responsible for updating the command_rules struct so that relative ordering of
* commands and categories is maintained and can be reproduced without loss. */
void ACLUpdateCommandRules(aclSelector *selector, const char *rule, int allow) {
static void ACLUpdateCommandRules(aclSelector *selector, const char *rule, int allow) {
sds new_rule = sdsnew(rule);
sdstolower(new_rule);
@ -649,7 +638,7 @@ void ACLUpdateCommandRules(aclSelector *selector, const char *rule, int allow) {
/* This function is used to allow/block a specific command.
* Allowing/blocking a container command also applies for its subcommands */
void ACLChangeSelectorPerm(aclSelector *selector, struct serverCommand *cmd, int allow) {
static void ACLChangeSelectorPerm(aclSelector *selector, struct serverCommand *cmd, int allow) {
unsigned long id = cmd->id;
ACLSetSelectorCommandBit(selector, id, allow);
ACLResetFirstArgsForCommand(selector, id);
@ -671,7 +660,7 @@ void ACLChangeSelectorPerm(aclSelector *selector, struct serverCommand *cmd, int
* value. Since the category passed by the user may be non existing, the
* function returns C_ERR if the category was not found, or C_OK if it was
* found and the operation was performed. */
void ACLSetSelectorCommandBitsForCategory(hashtable *commands, aclSelector *selector, uint64_t cflag, int value) {
static void ACLSetSelectorCommandBitsForCategory(hashtable *commands, aclSelector *selector, uint64_t cflag, int value) {
hashtableIterator iter;
hashtableInitIterator(&iter, commands, 0);
void *next;
@ -724,7 +713,7 @@ void ACLRecomputeCommandBitsFromCommandRulesAllUsers(void) {
raxStop(&ri);
}
int ACLSetSelectorCategory(aclSelector *selector, const char *category, int allow) {
static int ACLSetSelectorCategory(aclSelector *selector, const char *category, int allow) {
uint64_t cflag = ACLGetCommandCategoryFlagByName(category + 1);
if (!cflag) return C_ERR;
@ -735,42 +724,6 @@ int ACLSetSelectorCategory(aclSelector *selector, const char *category, int allo
return C_OK;
}
void ACLCountCategoryBitsForCommands(hashtable *commands,
aclSelector *selector,
unsigned long *on,
unsigned long *off,
uint64_t cflag) {
hashtableIterator iter;
hashtableInitIterator(&iter, commands, 0);
void *next;
while (hashtableNext(&iter, &next)) {
struct serverCommand *cmd = next;
if (cmd->acl_categories & cflag) {
if (ACLGetSelectorCommandBit(selector, cmd->id))
(*on)++;
else
(*off)++;
}
if (cmd->subcommands_ht) {
ACLCountCategoryBitsForCommands(cmd->subcommands_ht, selector, on, off, cflag);
}
}
hashtableResetIterator(&iter);
}
/* Return the number of commands allowed (on) and denied (off) for the user 'u'
* in the subset of commands flagged with the specified category name.
* If the category name is not valid, C_ERR is returned, otherwise C_OK is
* returned and on and off are populated by reference. */
int ACLCountCategoryBitsForSelector(aclSelector *selector, unsigned long *on, unsigned long *off, const char *category) {
uint64_t cflag = ACLGetCommandCategoryFlagByName(category);
if (!cflag) return C_ERR;
*on = *off = 0;
ACLCountCategoryBitsForCommands(server.orig_commands, selector, on, off, cflag);
return C_OK;
}
/* This function returns an SDS string representing the specified selector ACL
* rules related to command execution, in the same format you could set them
* back using ACL SETUSER. The function will return just the set of rules needed
@ -778,7 +731,7 @@ int ACLCountCategoryBitsForSelector(aclSelector *selector, unsigned long *on, un
* as on/off, passwords and so forth. The returned string always starts with
* the +@all or -@all rule, depending on the user bitmap, and is followed, if
* needed, by the other rules needed to narrow or extend what the user can do. */
sds ACLDescribeSelectorCommandRules(aclSelector *selector) {
static sds ACLDescribeSelectorCommandRules(aclSelector *selector) {
sds rules = sdsempty();
/* We use this fake selector as a "sanity" check to make sure the rules
@ -828,7 +781,7 @@ sds ACLDescribeSelectorCommandRules(aclSelector *selector) {
return rules;
}
sds ACLDescribeSelector(aclSelector *selector) {
static sds ACLDescribeSelector(aclSelector *selector) {
listIter li;
listNode *ln;
sds res = sdsempty();
@ -921,7 +874,7 @@ robj *ACLDescribeUser(user *u) {
/* Get a command from the original command table, that is not affected
* by the command renaming operations: we base all the ACL work from that
* table, so that ACLs are valid regardless of command renaming. */
struct serverCommand *ACLLookupCommand(const char *name) {
static struct serverCommand *ACLLookupCommand(const char *name) {
struct serverCommand *cmd;
sds sdsname = sdsnew(name);
cmd = lookupCommandBySdsLogic(server.orig_commands, sdsname);
@ -931,7 +884,7 @@ struct serverCommand *ACLLookupCommand(const char *name) {
/* Flush the array of allowed first-args for the specified user
* and command ID. */
void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id) {
static void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id) {
if (selector->allowed_firstargs && selector->allowed_firstargs[id]) {
for (int i = 0; selector->allowed_firstargs[id][i]; i++) sdsfree(selector->allowed_firstargs[id][i]);
zfree(selector->allowed_firstargs[id]);
@ -942,7 +895,7 @@ void ACLResetFirstArgsForCommand(aclSelector *selector, unsigned long id) {
/* Flush the entire table of first-args. This is useful on +@all, -@all
* or similar to return back to the minimal memory usage (and checks to do)
* for the user. */
void ACLResetFirstArgs(aclSelector *selector) {
static void ACLResetFirstArgs(aclSelector *selector) {
if (selector->allowed_firstargs == NULL) return;
for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) {
if (selector->allowed_firstargs[j]) {
@ -956,7 +909,7 @@ void ACLResetFirstArgs(aclSelector *selector) {
/* Add a first-arg to the list of subcommands for the user 'u' and
* the command id specified. */
void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub) {
static void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *sub) {
/* If this is the first first-arg to be configured for
* this user, we have to allocate the first-args array. */
if (selector->allowed_firstargs == NULL) {
@ -989,7 +942,7 @@ void ACLAddAllowedFirstArg(aclSelector *selector, unsigned long id, const char *
*
* If any of the operations are invalid, NULL will be returned instead
* and errno will be set corresponding to the interior error. */
aclSelector *aclCreateSelectorFromOpSet(const char *opset, size_t opsetlen) {
static aclSelector *aclCreateSelectorFromOpSet(const char *opset, size_t opsetlen) {
serverAssert(opset[0] == '(' && opset[opsetlen - 1] == ')');
aclSelector *s = ACLCreateSelector(0);
@ -1000,11 +953,10 @@ aclSelector *aclCreateSelectorFromOpSet(const char *opset, size_t opsetlen) {
if (ACLSetSelector(s, argv[i], sdslen(argv[i])) == C_ERR) {
ACLFreeSelector(s);
s = NULL;
goto cleanup;
break;
}
}
cleanup:
sdsfreesplitres(argv, argc);
sdsfree(trimmed);
return s;
@ -1047,7 +999,7 @@ cleanup:
* allchannels Alias for &*
* resetchannels Flush the list of allowed channel patterns.
*/
int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen) {
static int ACLSetSelector(aclSelector *selector, const char *op, size_t oplen) {
if (!strcasecmp(op, "allkeys") || !strcasecmp(op, "~*")) {
selector->flags |= SELECTOR_FLAG_ALLKEYS;
listEmpty(selector->patterns);
@ -1423,7 +1375,7 @@ const char *ACLSetUserStringError(void) {
}
/* Create the default user, this has special permissions. */
user *ACLCreateDefaultUser(void) {
static user *ACLCreateDefaultUser(void) {
user *new = ACLCreateUser("default", 7);
ACLSetUser(new, "+@all", -1);
ACLSetUser(new, "~*", -1);
@ -1501,7 +1453,7 @@ void addAuthErrReply(client *c, robj *err) {
* connection user reference is populated.
*
* The return value is AUTH_OK on success (valid username / password pair) & AUTH_ERR otherwise. */
int checkPasswordBasedAuth(client *c, robj *username, robj *password) {
static int checkPasswordBasedAuth(client *c, robj *username, robj *password) {
if (ACLCheckUserCredentials(username, password) == C_OK) {
c->flag.authenticated = 1;
c->user = ACLGetUserByName(username->ptr, sdslen(username->ptr));
@ -1565,13 +1517,6 @@ unsigned long ACLGetCommandID(sds cmdname) {
return thisid;
}
/* Clear command id table and reset nextid to 0. */
void ACLClearCommandID(void) {
if (commandId) raxFree(commandId);
commandId = NULL;
nextid = 0;
}
/* Return an username by its name, or NULL if the user does not exist. */
user *ACLGetUserByName(const char *name, size_t namelen) {
void *myuser = NULL;
@ -1674,11 +1619,11 @@ typedef struct {
getKeysResult keys;
} aclKeyResultCache;
void initACLKeyResultCache(aclKeyResultCache *cache) {
static void initACLKeyResultCache(aclKeyResultCache *cache) {
cache->keys_init = 0;
}
void cleanupACLKeyResultCache(aclKeyResultCache *cache) {
static void cleanupACLKeyResultCache(aclKeyResultCache *cache) {
if (cache->keys_init) getKeysFreeResult(&(cache->keys));
}
@ -1897,7 +1842,7 @@ int ACLCheckAllPerm(client *c, int *idxptr) {
/* If 'new' can access all channels 'original' could then return NULL;
Otherwise return a list of channels that the new user can access */
list *getUpcomingChannelList(user *new, user *original) {
static list *getUpcomingChannelList(user *new, user *original) {
listIter li, lpi;
listNode *ln, *lpn;
@ -1954,7 +1899,7 @@ list *getUpcomingChannelList(user *new, user *original) {
/* Check if the client should be killed because it is subscribed to channels that were
* permitted in the past, are not in the `upcoming` channel list. */
int ACLShouldKillPubsubClient(client *c, list *upcoming) {
static int ACLShouldKillPubsubClient(client *c, list *upcoming) {
robj *o;
int kill = 0;
@ -2001,7 +1946,7 @@ int ACLShouldKillPubsubClient(client *c, list *upcoming) {
/* Check if the user's existing pub/sub clients violate the ACL pub/sub
* permissions specified via the upcoming argument, and kill them if so. */
void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
static void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
/* Do nothing if there are no subscribers. */
if (pubsubTotalSubscriptions() == 0) return;
@ -2041,7 +1986,7 @@ void ACLKillPubsubClientsIfNeeded(user *new, user *original) {
* that are untouched are still duplicated. If there is an unmatched parenthesis, NULL
* is returned and invalid_idx is set to the argument with the start of the opening
* parenthesis. */
sds *ACLMergeSelectorArguments(sds *argv, int argc, int *merged_argc, int *invalid_idx) {
static sds *ACLMergeSelectorArguments(sds *argv, int argc, int *merged_argc, int *invalid_idx) {
*merged_argc = 0;
int open_bracket_start = -1;
@ -2212,7 +2157,7 @@ int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) {
/* This function will load the configured users appended to the server
* configuration via ACLAppendUserForLoading(). On loading errors it will
* log an error and return C_ERR, otherwise C_OK will be returned. */
int ACLLoadConfiguredUsers(void) {
static int ACLLoadConfiguredUsers(void) {
listIter li;
listNode *ln;
listRewind(UsersToLoad, &li);
@ -2281,7 +2226,7 @@ int ACLLoadConfiguredUsers(void) {
* At the end of the process, if no errors were found in the whole file then
* NULL is returned. Otherwise an SDS string describing in a single line
* a description of all the issues found is returned. */
sds ACLLoadFromFile(const char *filename) {
static sds ACLLoadFromFile(const char *filename) {
FILE *fp;
char buf[1024];
@ -2467,7 +2412,7 @@ sds ACLLoadFromFile(const char *filename) {
/* Generate a copy of the ACLs currently in memory in the specified filename.
* Returns C_OK on success or C_ERR if there was an error during the I/O.
* When C_ERR is returned a log is produced with hints about the issue. */
int ACLSaveToFile(const char *filename) {
static int ACLSaveToFile(const char *filename) {
sds acl = sdsempty();
int fd = -1;
sds tmpfilename = NULL;
@ -2595,7 +2540,7 @@ typedef struct ACLLogEntry {
/* This function will check if ACL entries 'a' and 'b' are similar enough
* that we should actually update the existing entry in our ACL log instead
* of creating a new one. */
int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
static int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
if (a->reason != b->reason) return 0;
if (a->context != b->context) return 0;
mstime_t delta = a->ctime - b->ctime;
@ -2607,7 +2552,7 @@ int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
}
/* Release an ACL log entry. */
void ACLFreeLogEntry(void *leptr) {
static void ACLFreeLogEntry(void *leptr) {
ACLLogEntry *le = leptr;
sdsfree(le->object);
sdsfree(le->username);
@ -2616,7 +2561,7 @@ void ACLFreeLogEntry(void *leptr) {
}
/* Update the relevant counter by the reason */
void ACLUpdateInfoMetrics(int reason) {
static void ACLUpdateInfoMetrics(int reason) {
if (reason == ACL_DENIED_AUTH) {
server.acl_info.user_auth_failures++;
} else if (reason == ACL_DENIED_CMD) {
@ -2763,7 +2708,7 @@ sds getAclErrorMessage(int acl_res, user *user, struct serverCommand *cmd, sds e
* ==========================================================================*/
/* ACL CAT category */
void aclCatWithFlags(client *c, hashtable *commands, uint64_t cflag, int *arraylen) {
static void aclCatWithFlags(client *c, hashtable *commands, uint64_t cflag, int *arraylen) {
hashtableIterator iter;
hashtableInitIterator(&iter, commands, 0);
void *next;
@ -2787,7 +2732,7 @@ void aclCatWithFlags(client *c, hashtable *commands, uint64_t cflag, int *arrayl
* Setting verbose to 1 means that the full qualifier for key and channel
* permissions are shown.
*/
int aclAddReplySelectorDescription(client *c, aclSelector *s) {
static int aclAddReplySelectorDescription(client *c, aclSelector *s) {
listIter li;
listNode *ln;

View File

@ -542,8 +542,10 @@ void loadServerConfigFromString(char *config) {
/* Otherwise we re-add the command under a different name. */
if (sdslen(argv[2]) != 0) {
sdsfree(cmd->fullname);
cmd->fullname = sdsdup(argv[2]);
if (cmd->current_name != cmd->fullname) {
sdsfree(cmd->current_name);
}
cmd->current_name = sdsdup(argv[2]);
if (!hashtableAdd(server.commands, cmd)) {
err = "Target command name already exists";
goto loaderr;

View File

@ -1403,6 +1403,7 @@ ValkeyModuleCommand *moduleCreateCommandProxy(struct ValkeyModule *module,
cp->serverCmd = zcalloc(sizeof(*serverCmd));
cp->serverCmd->declared_name = declared_name; /* SDS for module commands */
cp->serverCmd->fullname = fullname;
cp->serverCmd->current_name = fullname;
cp->serverCmd->group = COMMAND_GROUP_MODULE;
cp->serverCmd->proc = ValkeyModuleCommandDispatcher;
cp->serverCmd->flags = flags | CMD_MODULE;

View File

@ -519,7 +519,12 @@ int hashtableResizeAllowed(size_t moreMem, double usedRatio) {
return !overMaxmemoryAfterAlloc(moreMem);
}
const void *hashtableCommandGetKey(const void *element) {
const void *hashtableCommandGetCurrentName(const void *element) {
struct serverCommand *command = (struct serverCommand *)element;
return command->current_name;
}
const void *hashtableCommandGetOriginalName(const void *element) {
struct serverCommand *command = (struct serverCommand *)element;
return command->fullname;
}
@ -624,12 +629,18 @@ hashtableType kvstoreExpiresHashtableType = {
.getMetadataSize = kvstoreHashtableMetadataSize,
};
/* Command set, hashed by sds string, stores serverCommand structs. */
hashtableType commandSetType = {.entryGetKey = hashtableCommandGetKey,
/* Command set, hashed by current command name, stores serverCommand structs. */
hashtableType commandSetType = {.entryGetKey = hashtableCommandGetCurrentName,
.hashFunction = dictSdsCaseHash,
.keyCompare = hashtableStringKeyCaseCompare,
.instant_rehashing = 1};
/* Command set, hashed by original command name, stores serverCommand structs. */
hashtableType originalCommandSetType = {.entryGetKey = hashtableCommandGetOriginalName,
.hashFunction = dictSdsCaseHash,
.keyCompare = hashtableStringKeyCaseCompare,
.instant_rehashing = 1};
/* Sub-command set, hashed by char* string, stores serverCommand structs. */
hashtableType subcommandSetType = {.entryGetKey = hashtableSubcommandGetKey,
.hashFunction = dictCStrCaseHash,
@ -2280,7 +2291,7 @@ void initServerConfig(void) {
* initial configuration, since command names may be changed via
* valkey.conf using the rename-command directive. */
server.commands = hashtableCreate(&commandSetType);
server.orig_commands = hashtableCreate(&commandSetType);
server.orig_commands = hashtableCreate(&originalCommandSetType);
populateCommandTable();
/* Debugging */
@ -3203,6 +3214,7 @@ void populateCommandTable(void) {
int retval1, retval2;
c->fullname = sdsnew(c->declared_name);
c->current_name = c->fullname;
if (populateCommandStructure(c) == C_ERR) continue;
retval1 = hashtableAdd(server.commands, c);

View File

@ -2459,12 +2459,13 @@ struct serverCommand {
/* Runtime populated data */
long long microseconds, calls, rejected_calls, failed_calls;
int id; /* Command ID. This is a progressive ID starting from 0 that
is assigned at runtime, and is used in order to check
ACLs. A connection is able to execute a given command if
the user associated to the connection has this command
bit set in the bitmap of allowed commands. */
sds fullname; /* A SDS string representing the command fullname. */
int id; /* Command ID. This is a progressive ID starting from 0 that
is assigned at runtime, and is used in order to check
ACLs. A connection is able to execute a given command if
the user associated to the connection has this command
bit set in the bitmap of allowed commands. */
sds fullname; /* Includes parent name if any: "parentcmd|childcmd". Unchanged if command is renamed. */
sds current_name; /* Same as fullname, becomes a separate string if command is renamed. */
struct hdr_histogram
*latency_histogram; /* Points to the command latency command histogram (unit of time nanosecond). */
keySpec legacy_range_key_spec; /* The legacy (first,last,step) key spec is
@ -3041,7 +3042,6 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password, robj **err);
int checkModuleAuthentication(client *c, robj *username, robj *password, robj **err);
void addAuthErrReply(client *c, robj *err);
unsigned long ACLGetCommandID(sds cmdname);
void ACLClearCommandID(void);
user *ACLGetUserByName(const char *name, size_t namelen);
int ACLUserCheckKeyPerm(user *u, const char *key, int keylen, int flags);
int ACLUserCheckChannelPerm(user *u, sds channel, int literal);
@ -3055,7 +3055,6 @@ int ACLAddCommandCategory(const char *name, uint64_t flag);
void ACLCleanupCategoriesOnFailure(size_t num_acl_categories_added);
int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err);
const char *ACLSetUserStringError(void);
int ACLLoadConfiguredUsers(void);
robj *ACLDescribeUser(user *u);
void ACLLoadUsersAtStartup(void);
void addReplyCommandCategories(client *c, struct serverCommand *cmd);

View File

@ -1244,3 +1244,67 @@ start_server {overrides {user "default on nopass ~* +@all -flushdb"} tags {acl e
}
}
tags {acl external:skip} {
set renames [list rename-command "PING RainWasHere" \
rename-command "RainWasHere VPING" \
rename-command "INCR VINCR" \
rename-command "SET VSET" \
rename-command "SADD VSADD" \
rename-command "HGET VHGET" \
rename-command "HSET VHSET" \
rename-command "CONFIG VCONFIG" ]
start_server [list config_lines $renames ] {
test {ACL still denies individual renamed commands} {
r ACL setuser newuser on nopass ~* +acl -ping +incr
r AUTH newuser anypass
r VINCR mycounter ; # Should not raise an error
catch {r vping} e
set e
} {*NOPERM*ping*}
test {ACL command classes aren't affected by command renaming} {
r ACL setuser newuser -@all +@set +acl
r VSADD myset a b c; # Should not raise an error
r ACL setuser newuser +@all -@string
r VSADD myset a b c; # Again should not raise an error
# String commands instead should raise an error
catch {r vset foo bar} e
r ACL setuser newuser allcommands; # Undo commands ACL
set e
} {*NOPERM*set*}
test {ACL GETUSER provides correct results when commands renamed} {
r ACL SETUSER adv-test
r ACL SETUSER adv-test +@all -@hash -@slow +hget
assert_equal "+@all -@hash -@slow +hget" [dict get [r ACL getuser adv-test] commands]
# Categories are re-ordered if re-added
r ACL SETUSER adv-test -@hash
assert_equal "+@all -@slow +hget -@hash" [dict get [r ACL getuser adv-test] commands]
# Inverting categories removes existing categories
r ACL SETUSER adv-test +@hash
assert_equal "+@all -@slow +hget +@hash" [dict get [r ACL getuser adv-test] commands]
# Make sure categories are case insensitive
r ACL SETUSER adv-test -@all +@HASH +@hash +@HaSh
assert_equal "-@all +@hash" [dict get [r ACL getuser adv-test] commands]
# Make sure commands are case insensitive
r ACL SETUSER adv-test -@all +HGET +hget +hGeT
assert_equal "-@all +hget" [dict get [r ACL getuser adv-test] commands]
# Arbitrary category additions and removals are handled
r ACL SETUSER adv-test -@all +@hash +@slow +@set +@set +@slow +@hash
assert_equal "-@all +@set +@slow +@hash" [dict get [r ACL getuser adv-test] commands]
# Arbitrary command additions and removals are handled
r ACL SETUSER adv-test -@all +hget -hset +hset -hget
assert_equal "-@all +hset -hget" [dict get [r ACL getuser adv-test] commands]
# Arbitrary subcommands are compacted
r ACL SETUSER adv-test -@all +client|list +client|list +config|get +config +acl|list -acl
assert_equal "-@all +client|list +config -acl" [dict get [r ACL getuser adv-test] commands]
}
}
}