summaryrefslogtreecommitdiff
path: root/src/acl.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/acl.c')
-rw-r--r--src/acl.c288
1 files changed, 173 insertions, 115 deletions
diff --git a/src/acl.c b/src/acl.c
index 6947fc204..199e050dd 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -101,9 +101,9 @@ struct ACLUserFlag {
{NULL,0} /* Terminator. */
};
-void ACLResetSubcommandsForCommand(user *u, unsigned long id);
-void ACLResetSubcommands(user *u);
-void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
+void ACLResetFirstArgsForCommand(user *u, unsigned long id);
+void ACLResetFirstArgs(user *u);
+void ACLAddAllowedFirstArg(user *u, unsigned long id, const char *sub);
void ACLFreeLogEntry(void *le);
/* The length of the string representation of a hashed password. */
@@ -254,7 +254,7 @@ user *ACLCreateUser(const char *name, size_t namelen) {
user *u = zmalloc(sizeof(*u));
u->name = sdsnewlen(name,namelen);
u->flags = USER_FLAG_DISABLED | server.acl_pubsub_default;
- u->allowed_subcommands = NULL;
+ u->allowed_firstargs = NULL;
u->passwords = listCreate();
u->patterns = listCreate();
u->channels = listCreate();
@@ -296,7 +296,7 @@ void ACLFreeUser(user *u) {
listRelease(u->passwords);
listRelease(u->patterns);
listRelease(u->channels);
- ACLResetSubcommands(u);
+ ACLResetFirstArgs(u);
zfree(u);
}
@@ -343,15 +343,15 @@ void ACLCopyUser(user *dst, user *src) {
memcpy(dst->allowed_commands,src->allowed_commands,
sizeof(dst->allowed_commands));
dst->flags = src->flags;
- ACLResetSubcommands(dst);
- /* Copy the allowed subcommands array of array of SDS strings. */
- if (src->allowed_subcommands) {
+ ACLResetFirstArgs(dst);
+ /* Copy the allowed first-args array of array of SDS strings. */
+ if (src->allowed_firstargs) {
for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) {
- if (src->allowed_subcommands[j]) {
- for (int i = 0; src->allowed_subcommands[j][i]; i++)
+ if (src->allowed_firstargs[j]) {
+ for (int i = 0; src->allowed_firstargs[j][i]; i++)
{
- ACLAddAllowedSubcommand(dst, j,
- src->allowed_subcommands[j][i]);
+ ACLAddAllowedFirstArg(dst, j,
+ src->allowed_firstargs[j][i]);
}
}
}
@@ -413,6 +413,38 @@ void ACLSetUserCommandBit(user *u, unsigned long id, int value) {
}
}
+/* This function is used to allow/block a specific command.
+ * Allowing/blocking a container command also applies for its subcommands */
+void ACLChangeCommandPerm(user *u, struct redisCommand *cmd, int allow) {
+ unsigned long id = cmd->id;
+ ACLSetUserCommandBit(u,id,allow);
+ ACLResetFirstArgsForCommand(u,id);
+ if (cmd->subcommands_dict) {
+ dictEntry *de;
+ dictIterator *di = dictGetSafeIterator(cmd->subcommands_dict);
+ while((de = dictNext(di)) != NULL) {
+ struct redisCommand *sub = (struct redisCommand *)dictGetVal(de);
+ ACLSetUserCommandBit(u,sub->id,allow);
+ }
+ }
+}
+
+void ACLSetUserCommandBitsForCategoryLogic(dict *commands, user *u, uint64_t cflag, int value) {
+ dictIterator *di = dictGetIterator(commands);
+ dictEntry *de;
+ while ((de = dictNext(di)) != NULL) {
+ struct redisCommand *cmd = dictGetVal(de);
+ if (cmd->flags & CMD_MODULE) continue; /* Ignore modules commands. */
+ if (cmd->flags & cflag) {
+ ACLChangeCommandPerm(u,cmd,value);
+ }
+ if (cmd->subcommands_dict) {
+ ACLSetUserCommandBitsForCategoryLogic(cmd->subcommands_dict, u, cflag, value);
+ }
+ }
+ dictReleaseIterator(di);
+}
+
/* This is like ACLSetUserCommandBit(), but instead of setting the specified
* ID, it will check all the commands in the category specified as argument,
* and will set all the bits corresponding to such commands to the specified
@@ -422,18 +454,26 @@ void ACLSetUserCommandBit(user *u, unsigned long id, int value) {
int ACLSetUserCommandBitsForCategory(user *u, const char *category, int value) {
uint64_t cflag = ACLGetCommandCategoryFlagByName(category);
if (!cflag) return C_ERR;
- dictIterator *di = dictGetIterator(server.orig_commands);
+ ACLSetUserCommandBitsForCategoryLogic(server.orig_commands, u, cflag, value);
+ return C_OK;
+}
+
+void ACLCountCategoryBitsForCommands(dict *commands, user *u, unsigned long *on, unsigned long *off, uint64_t cflag) {
+ dictIterator *di = dictGetIterator(commands);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
- if (cmd->flags & CMD_MODULE) continue; /* Ignore modules commands. */
if (cmd->flags & cflag) {
- ACLSetUserCommandBit(u,cmd->id,value);
- ACLResetSubcommandsForCommand(u,cmd->id);
+ if (ACLGetUserCommandBit(u,cmd->id))
+ (*on)++;
+ else
+ (*off)++;
+ }
+ if (cmd->subcommands_dict) {
+ ACLCountCategoryBitsForCommands(cmd->subcommands_dict, u, on, off, cflag);
}
}
dictReleaseIterator(di);
- return C_OK;
}
/* Return the number of commands allowed (on) and denied (off) for the user 'u'
@@ -447,19 +487,46 @@ int ACLCountCategoryBitsForUser(user *u, unsigned long *on, unsigned long *off,
if (!cflag) return C_ERR;
*on = *off = 0;
- dictIterator *di = dictGetIterator(server.orig_commands);
+ ACLCountCategoryBitsForCommands(server.orig_commands, u, on, off, cflag);
+ return C_OK;
+}
+
+sds ACLDescribeUserCommandRulesSingleCommands(user *u, user *fakeuser, sds rules, dict *commands) {
+ dictIterator *di = dictGetIterator(commands);
dictEntry *de;
while ((de = dictNext(di)) != NULL) {
struct redisCommand *cmd = dictGetVal(de);
- if (cmd->flags & cflag) {
- if (ACLGetUserCommandBit(u,cmd->id))
- (*on)++;
- else
- (*off)++;
+ int userbit = ACLGetUserCommandBit(u,cmd->id);
+ int fakebit = ACLGetUserCommandBit(fakeuser,cmd->id);
+ if (userbit != fakebit) {
+ rules = sdscatlen(rules, userbit ? "+" : "-", 1);
+ sds fullname = getFullCommandName(cmd);
+ rules = sdscat(rules,fullname);
+ sdsfree(fullname);
+ rules = sdscatlen(rules," ",1);
+ ACLChangeCommandPerm(fakeuser,cmd,userbit);
+ }
+
+ if (cmd->subcommands_dict)
+ rules = ACLDescribeUserCommandRulesSingleCommands(u,fakeuser,rules,cmd->subcommands_dict);
+
+ /* Emit the first-args if there are any. */
+ if (userbit == 0 && u->allowed_firstargs &&
+ u->allowed_firstargs[cmd->id])
+ {
+ for (int j = 0; u->allowed_firstargs[cmd->id][j]; j++) {
+ rules = sdscatlen(rules,"+",1);
+ sds fullname = getFullCommandName(cmd);
+ rules = sdscat(rules,fullname);
+ sdsfree(fullname);
+ rules = sdscatlen(rules,"|",1);
+ rules = sdscatsds(rules,u->allowed_firstargs[cmd->id][j]);
+ rules = sdscatlen(rules," ",1);
+ }
}
}
dictReleaseIterator(di);
- return C_OK;
+ return rules;
}
/* This function returns an SDS string representing the specified user ACL
@@ -563,33 +630,7 @@ sds ACLDescribeUserCommandRules(user *u) {
}
/* Fix the final ACLs with single commands differences. */
- dictIterator *di = dictGetIterator(server.orig_commands);
- dictEntry *de;
- while ((de = dictNext(di)) != NULL) {
- struct redisCommand *cmd = dictGetVal(de);
- int userbit = ACLGetUserCommandBit(u,cmd->id);
- int fakebit = ACLGetUserCommandBit(fakeuser,cmd->id);
- if (userbit != fakebit) {
- rules = sdscatlen(rules, userbit ? "+" : "-", 1);
- rules = sdscat(rules,cmd->name);
- rules = sdscatlen(rules," ",1);
- ACLSetUserCommandBit(fakeuser,cmd->id,userbit);
- }
-
- /* Emit the subcommands if there are any. */
- if (userbit == 0 && u->allowed_subcommands &&
- u->allowed_subcommands[cmd->id])
- {
- for (int j = 0; u->allowed_subcommands[cmd->id][j]; j++) {
- rules = sdscatlen(rules,"+",1);
- rules = sdscat(rules,cmd->name);
- rules = sdscatlen(rules,"|",1);
- rules = sdscatsds(rules,u->allowed_subcommands[cmd->id][j]);
- rules = sdscatlen(rules," ",1);
- }
- }
- }
- dictReleaseIterator(di);
+ rules = ACLDescribeUserCommandRulesSingleCommands(u,fakeuser,rules,server.orig_commands);
/* Trim the final useless space. */
sdsrange(rules,0,-2);
@@ -683,67 +724,66 @@ sds ACLDescribeUser(user *u) {
struct redisCommand *ACLLookupCommand(const char *name) {
struct redisCommand *cmd;
sds sdsname = sdsnew(name);
- cmd = dictFetchValue(server.orig_commands, sdsname);
+ cmd = lookupCommandBySdsLogic(server.orig_commands,sdsname);
sdsfree(sdsname);
return cmd;
}
-/* Flush the array of allowed subcommands for the specified user
+/* Flush the array of allowed first-args for the specified user
* and command ID. */
-void ACLResetSubcommandsForCommand(user *u, unsigned long id) {
- if (u->allowed_subcommands && u->allowed_subcommands[id]) {
- for (int i = 0; u->allowed_subcommands[id][i]; i++)
- sdsfree(u->allowed_subcommands[id][i]);
- zfree(u->allowed_subcommands[id]);
- u->allowed_subcommands[id] = NULL;
+void ACLResetFirstArgsForCommand(user *u, unsigned long id) {
+ if (u->allowed_firstargs && u->allowed_firstargs[id]) {
+ for (int i = 0; u->allowed_firstargs[id][i]; i++)
+ sdsfree(u->allowed_firstargs[id][i]);
+ zfree(u->allowed_firstargs[id]);
+ u->allowed_firstargs[id] = NULL;
}
}
-/* Flush the entire table of subcommands. This is useful on +@all, -@all
+/* 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 ACLResetSubcommands(user *u) {
- if (u->allowed_subcommands == NULL) return;
+void ACLResetFirstArgs(user *u) {
+ if (u->allowed_firstargs == NULL) return;
for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) {
- if (u->allowed_subcommands[j]) {
- for (int i = 0; u->allowed_subcommands[j][i]; i++)
- sdsfree(u->allowed_subcommands[j][i]);
- zfree(u->allowed_subcommands[j]);
+ if (u->allowed_firstargs[j]) {
+ for (int i = 0; u->allowed_firstargs[j][i]; i++)
+ sdsfree(u->allowed_firstargs[j][i]);
+ zfree(u->allowed_firstargs[j]);
}
}
- zfree(u->allowed_subcommands);
- u->allowed_subcommands = NULL;
+ zfree(u->allowed_firstargs);
+ u->allowed_firstargs = NULL;
}
-/* Add a subcommand to the list of subcommands for the user 'u' and
+/* Add a first-arh to the list of subcommands for the user 'u' and
* the command id specified. */
-void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
- /* If this is the first subcommand to be configured for
- * this user, we have to allocate the subcommands array. */
- if (u->allowed_subcommands == NULL) {
- u->allowed_subcommands = zcalloc(USER_COMMAND_BITS_COUNT *
- sizeof(sds*));
+void ACLAddAllowedFirstArg(user *u, 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 (u->allowed_firstargs == NULL) {
+ u->allowed_firstargs = zcalloc(USER_COMMAND_BITS_COUNT * sizeof(sds*));
}
/* We also need to enlarge the allocation pointing to the
* null terminated SDS array, to make space for this one.
* To start check the current size, and while we are here
- * make sure the subcommand is not already specified inside. */
+ * make sure the first-arg is not already specified inside. */
long items = 0;
- if (u->allowed_subcommands[id]) {
- while(u->allowed_subcommands[id][items]) {
+ if (u->allowed_firstargs[id]) {
+ while(u->allowed_firstargs[id][items]) {
/* If it's already here do not add it again. */
- if (!strcasecmp(u->allowed_subcommands[id][items],sub)) return;
+ if (!strcasecmp(u->allowed_firstargs[id][items],sub))
+ return;
items++;
}
}
/* Now we can make space for the new item (and the null term). */
items += 2;
- u->allowed_subcommands[id] = zrealloc(u->allowed_subcommands[id],
- sizeof(sds)*items);
- u->allowed_subcommands[id][items-2] = sdsnew(sub);
- u->allowed_subcommands[id][items-1] = NULL;
+ u->allowed_firstargs[id] = zrealloc(u->allowed_firstargs[id], sizeof(sds)*items);
+ u->allowed_firstargs[id][items-2] = sdsnew(sub);
+ u->allowed_firstargs[id][items-1] = NULL;
}
/* Set user properties according to the string "op". The following
@@ -753,8 +793,10 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
* off Disable the user: it's no longer possible to authenticate
* with this user, however the already authenticated connections
* will still work.
- * +<command> Allow the execution of that command
- * -<command> Disallow the execution of that command
+ * +<command> Allow the execution of that command.
+ * May be used with `|` for allowing subcommands (e.g "+config|get")
+ * -<command> Disallow the execution of that command.
+ * May be used with `|` for blocking subcommands (e.g "-config|set")
* +@<category> Allow the execution of all the commands in such category
* with valid categories are like @admin, @set, @sortedset, ...
* and so forth, see the full list in the server.c file where
@@ -762,10 +804,10 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
* The special category @all means all the commands, but currently
* present in the server, and that will be loaded in the future
* via modules.
- * +<command>|subcommand Allow a specific subcommand of an otherwise
- * disabled command. Note that this form is not
- * allowed as negative like -DEBUG|SEGFAULT, but
- * only additive starting with "+".
+ * +<command>|first-arg Allow a specific first argument of an otherwise
+ * disabled command. Note that this form is not
+ * allowed as negative like -SELECT|1, but
+ * only additive starting with "+".
* allcommands Alias for +@all. Note that it implies the ability to execute
* all the future commands loaded via the modules system.
* nocommands Alias for -@all.
@@ -866,13 +908,13 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
{
memset(u->allowed_commands,255,sizeof(u->allowed_commands));
u->flags |= USER_FLAG_ALLCOMMANDS;
- ACLResetSubcommands(u);
+ ACLResetFirstArgs(u);
} else if (!strcasecmp(op,"nocommands") ||
!strcasecmp(op,"-@all"))
{
memset(u->allowed_commands,0,sizeof(u->allowed_commands));
u->flags &= ~USER_FLAG_ALLCOMMANDS;
- ACLResetSubcommands(u);
+ ACLResetFirstArgs(u);
} else if (!strcasecmp(op,"nopass")) {
u->flags |= USER_FLAG_NOPASS;
listEmpty(u->passwords);
@@ -952,24 +994,25 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
sdsfree(newpat);
u->flags &= ~USER_FLAG_ALLCHANNELS;
} else if (op[0] == '+' && op[1] != '@') {
- if (strchr(op,'|') == NULL) {
- if (ACLLookupCommand(op+1) == NULL) {
+ if (strrchr(op,'|') == NULL) {
+ struct redisCommand *cmd = ACLLookupCommand(op+1);
+ if (cmd == NULL) {
errno = ENOENT;
return C_ERR;
}
- unsigned long id = ACLGetCommandID(op+1);
- ACLSetUserCommandBit(u,id,1);
- ACLResetSubcommandsForCommand(u,id);
+ ACLChangeCommandPerm(u,cmd,1);
} else {
/* Split the command and subcommand parts. */
char *copy = zstrdup(op+1);
- char *sub = strchr(copy,'|');
+ char *sub = strrchr(copy,'|');
sub[0] = '\0';
sub++;
+ struct redisCommand *cmd = ACLLookupCommand(copy);
+
/* Check if the command exists. We can't check the
* subcommand to see if it is valid. */
- if (ACLLookupCommand(copy) == NULL) {
+ if (cmd == NULL) {
zfree(copy);
errno = ENOENT;
return C_ERR;
@@ -983,22 +1026,38 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
return C_ERR;
}
- unsigned long id = ACLGetCommandID(copy);
- /* Add the subcommand to the list of valid ones, if the command is not set. */
- if (ACLGetUserCommandBit(u,id) == 0) {
- ACLAddAllowedSubcommand(u,id,sub);
+ if (cmd->subcommands_dict) {
+ /* If user is trying to allow a valid subcommand we can just add its unique ID */
+ struct redisCommand *cmd = ACLLookupCommand(op+1);
+ if (cmd == NULL) {
+ zfree(copy);
+ errno = ENOENT;
+ return C_ERR;
+ }
+ ACLChangeCommandPerm(u,cmd,1);
+ } else {
+ /* If user is trying to use the ACL mech to block SELECT except SELECT 0 or
+ * block DEBUG except DEBUG OBJECT (DEBUG subcommands are not considered
+ * subcommands for now) we use the allowed_firstargs mechanism. */
+ struct redisCommand *cmd = ACLLookupCommand(copy);
+ if (cmd == NULL) {
+ zfree(copy);
+ errno = ENOENT;
+ return C_ERR;
+ }
+ /* Add the first-arg to the list of valid ones. */
+ ACLAddAllowedFirstArg(u,cmd->id,sub);
}
zfree(copy);
}
} else if (op[0] == '-' && op[1] != '@') {
- if (ACLLookupCommand(op+1) == NULL) {
+ struct redisCommand *cmd = ACLLookupCommand(op+1);
+ if (cmd == NULL) {
errno = ENOENT;
return C_ERR;
}
- unsigned long id = ACLGetCommandID(op+1);
- ACLSetUserCommandBit(u,id,0);
- ACLResetSubcommandsForCommand(u,id);
+ ACLChangeCommandPerm(u,cmd,0);
} else if ((op[0] == '+' || op[0] == '-') && op[1] == '@') {
int bitval = op[0] == '+' ? 1 : 0;
if (ACLSetUserCommandBitsForCategory(u,op+2,bitval) == C_ERR) {
@@ -1138,7 +1197,6 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) {
* command name, so that a command retains the same ID in case of modules that
* are unloaded and later reloaded. */
unsigned long ACLGetCommandID(const char *cmdname) {
-
sds lowername = sdsnew(cmdname);
sdstolower(lowername);
if (commandId == NULL) commandId = raxNew();
@@ -1225,23 +1283,23 @@ int ACLCheckCommandPerm(const user *u, struct redisCommand *cmd, robj **argv, in
if (!(u->flags & USER_FLAG_ALLCOMMANDS) && !(cmd->flags & CMD_NO_AUTH))
{
/* If the bit is not set we have to check further, in case the
- * command is allowed just with that specific subcommand. */
+ * command is allowed just with that specific first argument. */
if (ACLGetUserCommandBit(u,id) == 0) {
- /* Check if the subcommand matches. */
+ /* Check if the first argument matches. */
if (argc < 2 ||
- u->allowed_subcommands == NULL ||
- u->allowed_subcommands[id] == NULL)
+ u->allowed_firstargs == NULL ||
+ u->allowed_firstargs[id] == NULL)
{
return ACL_DENIED_CMD;
}
long subid = 0;
while (1) {
- if (u->allowed_subcommands[id][subid] == NULL)
+ if (u->allowed_firstargs[id][subid] == NULL)
return ACL_DENIED_CMD;
- if (!strcasecmp(argv[1]->ptr,
- u->allowed_subcommands[id][subid]))
- break; /* Subcommand match found. Stop here. */
+ int idx = cmd->parent ? 2 : 1;
+ if (!strcasecmp(argv[idx]->ptr,u->allowed_firstargs[id][subid]))
+ break; /* First argument match found. Stop here. */
subid++;
}
}