2018-12-21 11:16:22 -05:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions are met:
|
|
|
|
*
|
|
|
|
* * Redistributions of source code must retain the above copyright notice,
|
|
|
|
* this list of conditions and the following disclaimer.
|
|
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* * Neither the name of Redis nor the names of its contributors may be used
|
|
|
|
* to endorse or promote products derived from this software without
|
|
|
|
* specific prior written permission.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "server.h"
|
|
|
|
|
2019-01-10 10:39:32 -05:00
|
|
|
/* =============================================================================
|
|
|
|
* Global state for ACLs
|
|
|
|
* ==========================================================================*/
|
|
|
|
|
|
|
|
rax *Users; /* Table mapping usernames to user structures. */
|
2019-01-11 05:02:55 -05:00
|
|
|
user *DefaultUser; /* Global reference to the default user.
|
|
|
|
Every new connection is associated to it, if no
|
|
|
|
AUTH or HELLO is used to authenticate with a
|
|
|
|
different user. */
|
2019-01-10 10:39:32 -05:00
|
|
|
|
2019-01-10 10:35:55 -05:00
|
|
|
/* =============================================================================
|
|
|
|
* Helper functions for the rest of the ACL implementation
|
|
|
|
* ==========================================================================*/
|
|
|
|
|
2018-12-21 11:16:22 -05:00
|
|
|
/* Return zero if strings are the same, non-zero if they are not.
|
|
|
|
* The comparison is performed in a way that prevents an attacker to obtain
|
|
|
|
* information about the nature of the strings just monitoring the execution
|
|
|
|
* time of the function.
|
|
|
|
*
|
|
|
|
* Note that limiting the comparison length to strings up to 512 bytes we
|
|
|
|
* can avoid leaking any information about the password length and any
|
|
|
|
* possible branch misprediction related leak.
|
|
|
|
*/
|
|
|
|
int time_independent_strcmp(char *a, char *b) {
|
|
|
|
char bufa[CONFIG_AUTHPASS_MAX_LEN], bufb[CONFIG_AUTHPASS_MAX_LEN];
|
|
|
|
/* The above two strlen perform len(a) + len(b) operations where either
|
|
|
|
* a or b are fixed (our password) length, and the difference is only
|
|
|
|
* relative to the length of the user provided string, so no information
|
|
|
|
* leak is possible in the following two lines of code. */
|
|
|
|
unsigned int alen = strlen(a);
|
|
|
|
unsigned int blen = strlen(b);
|
|
|
|
unsigned int j;
|
|
|
|
int diff = 0;
|
|
|
|
|
|
|
|
/* We can't compare strings longer than our static buffers.
|
|
|
|
* Note that this will never pass the first test in practical circumstances
|
|
|
|
* so there is no info leak. */
|
|
|
|
if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1;
|
|
|
|
|
|
|
|
memset(bufa,0,sizeof(bufa)); /* Constant time. */
|
|
|
|
memset(bufb,0,sizeof(bufb)); /* Constant time. */
|
|
|
|
/* Again the time of the following two copies is proportional to
|
|
|
|
* len(a) + len(b) so no info is leaked. */
|
|
|
|
memcpy(bufa,a,alen);
|
|
|
|
memcpy(bufb,b,blen);
|
|
|
|
|
|
|
|
/* Always compare all the chars in the two buffers without
|
|
|
|
* conditional expressions. */
|
|
|
|
for (j = 0; j < sizeof(bufa); j++) {
|
|
|
|
diff |= (bufa[j] ^ bufb[j]);
|
|
|
|
}
|
|
|
|
/* Length must be equal as well. */
|
|
|
|
diff |= alen ^ blen;
|
|
|
|
return diff; /* If zero strings are the same. */
|
|
|
|
}
|
|
|
|
|
2019-01-10 10:35:55 -05:00
|
|
|
/* =============================================================================
|
|
|
|
* Low level ACL API
|
|
|
|
* ==========================================================================*/
|
|
|
|
|
2019-01-15 06:58:54 -05:00
|
|
|
/* 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) {
|
|
|
|
return sdscmp(a,b) == 0;
|
|
|
|
}
|
|
|
|
|
2019-01-10 11:01:12 -05:00
|
|
|
/* Create a new user with the specified name, store it in the list
|
|
|
|
* of users (the Users global radix tree), and returns a reference to
|
|
|
|
* the structure representing the user.
|
|
|
|
*
|
|
|
|
* If the user with such name already exists NULL is returned. */
|
2019-01-11 05:02:55 -05:00
|
|
|
user *ACLCreateUser(const char *name, size_t namelen) {
|
2019-01-10 11:01:12 -05:00
|
|
|
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
|
|
|
|
user *u = zmalloc(sizeof(*u));
|
|
|
|
u->flags = 0;
|
|
|
|
u->allowed_subcommands = NULL;
|
|
|
|
u->passwords = listCreate();
|
2019-01-15 06:58:54 -05:00
|
|
|
listSetMatchMethod(u->passwords,ACLListMatchSds);
|
2019-01-10 11:01:12 -05:00
|
|
|
u->patterns = NULL; /* Just created users cannot access to any key, however
|
|
|
|
if the "~*" directive was enabled to match all the
|
|
|
|
keys, the user will be flagged with the ALLKEYS
|
|
|
|
flag. */
|
|
|
|
memset(u->allowed_commands,0,sizeof(u->allowed_commands));
|
|
|
|
raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
|
|
|
|
return u;
|
|
|
|
}
|
|
|
|
|
2019-01-11 05:25:55 -05:00
|
|
|
/* Set user properties according to the string "op". The following
|
|
|
|
* is a description of what different strings will do:
|
|
|
|
*
|
|
|
|
* on Enable the user
|
|
|
|
* off Disable the user
|
|
|
|
* +<command> Allow the execution of that command
|
|
|
|
* -<command> Disallow the execution of that command
|
|
|
|
* +@<category> Allow the execution of all the commands in such category
|
|
|
|
* with valid categories being @set, @sortedset, @list, @hash,
|
|
|
|
* @string, @bitmap, @hyperloglog,
|
|
|
|
* @stream, @admin, @readonly,
|
|
|
|
* @readwrite, @fast, @slow,
|
|
|
|
* @pubsub.
|
|
|
|
* The special category @all means all the commands.
|
|
|
|
* +<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 "+".
|
2019-01-11 07:03:50 -05:00
|
|
|
* ~<pattern> Add a pattern of keys that can be mentioned as part of
|
2019-01-11 05:25:55 -05:00
|
|
|
* commands. For instance ~* allows all the keys. The pattern
|
|
|
|
* is a glob-style pattern like the one of KEYS.
|
2019-01-11 07:03:50 -05:00
|
|
|
* It is possible to specify multiple patterns.
|
2019-01-11 05:25:55 -05:00
|
|
|
* ><password> Add this passowrd to the list of valid password for the user.
|
|
|
|
* For example >mypass will add "mypass" to the list.
|
|
|
|
* <<password> Remove this password from the list of valid passwords.
|
2019-01-14 07:19:42 -05:00
|
|
|
* allcommands Alias for +@all
|
2019-01-11 07:03:50 -05:00
|
|
|
* allkeys Alias for ~*
|
2019-01-11 05:25:55 -05:00
|
|
|
* resetpass Flush the list of allowed passwords.
|
2019-01-11 07:03:50 -05:00
|
|
|
* resetkeys Flush the list of allowed keys patterns.
|
|
|
|
* reset Performs the following actions: resetpass, resetkeys, off,
|
|
|
|
* -@all. The user returns to the same state it has immediately
|
|
|
|
* after its creation.
|
|
|
|
*
|
2019-01-15 06:58:54 -05:00
|
|
|
* The 'op' string must be null terminated. The 'oplen' argument should
|
|
|
|
* specify the length of the 'op' string in case the caller requires to pass
|
|
|
|
* binary data (for instance the >password form may use a binary password).
|
|
|
|
* Otherwise the field can be set to -1 and the function will use strlen()
|
|
|
|
* to determine the length.
|
|
|
|
*
|
2019-01-11 07:03:50 -05:00
|
|
|
* The function returns C_OK if the action to perform was understood because
|
|
|
|
* the 'op' string made sense. Otherwise C_ERR is returned if the operation
|
|
|
|
* is unknown or has some syntax error.
|
2019-01-11 05:25:55 -05:00
|
|
|
*/
|
2019-01-15 06:58:54 -05:00
|
|
|
int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|
|
|
if (oplen == -1) oplen = strlen(op);
|
2019-01-11 07:03:50 -05:00
|
|
|
if (!strcasecmp(op,"on")) {
|
|
|
|
u->flags |= USER_FLAG_ENABLED;
|
|
|
|
} else if (!strcasecmp(op,"off")) {
|
|
|
|
u->flags &= ~USER_FLAG_ENABLED;
|
|
|
|
} else if (!strcasecmp(op,"allkeys") ||
|
|
|
|
!strcasecmp(op,"~*"))
|
|
|
|
{
|
|
|
|
u->flags |= USER_FLAG_ALLKEYS;
|
2019-01-14 07:18:12 -05:00
|
|
|
if (u->patterns) listEmpty(u->patterns);
|
|
|
|
} else if (!strcasecmp(op,"allcommands") ||
|
|
|
|
!strcasecmp(op,"+@all"))
|
|
|
|
{
|
2019-01-14 10:09:29 -05:00
|
|
|
memset(u->allowed_commands,255,sizeof(u->allowed_commands));
|
2019-01-14 07:18:12 -05:00
|
|
|
u->flags |= USER_FLAG_ALLCOMMANDS;
|
2019-01-15 06:58:54 -05:00
|
|
|
} else if (op[0] == '>') {
|
|
|
|
sds newpass = sdsnewlen(op+1,oplen-1);
|
|
|
|
listNode *ln = listSearchKey(u->passwords,newpass);
|
|
|
|
/* Avoid re-adding the same password multiple times. */
|
|
|
|
if (ln == NULL) listAddNodeTail(u->passwords,newpass);
|
|
|
|
} else if (op[0] == '<') {
|
|
|
|
sds delpass = sdsnewlen(op+1,oplen-1);
|
|
|
|
listNode *ln = listSearchKey(u->passwords,delpass);
|
|
|
|
if (ln) listDelNode(u->passwords,ln);
|
|
|
|
sdsfree(delpass);
|
2019-01-11 07:03:50 -05:00
|
|
|
} else {
|
|
|
|
return C_ERR;
|
|
|
|
}
|
|
|
|
return C_OK;
|
2019-01-11 05:25:55 -05:00
|
|
|
}
|
|
|
|
|
2019-01-10 10:39:32 -05:00
|
|
|
/* Initialization of the ACL subsystem. */
|
|
|
|
void ACLInit(void) {
|
|
|
|
Users = raxNew();
|
2019-01-11 05:02:55 -05:00
|
|
|
DefaultUser = ACLCreateUser("default",7);
|
2019-01-15 06:58:54 -05:00
|
|
|
ACLSetUser(DefaultUser,"+@all",-1);
|
|
|
|
ACLSetUser(DefaultUser,"on",-1);
|
2019-01-10 10:39:32 -05:00
|
|
|
}
|
|
|
|
|
2018-12-21 11:16:22 -05:00
|
|
|
/* Check the username and password pair and return C_OK if they are valid,
|
|
|
|
* otherwise C_ERR is returned and errno is set to:
|
|
|
|
*
|
|
|
|
* EINVAL: if the username-password do not match.
|
|
|
|
* ENONENT: if the specified user does not exist at all.
|
|
|
|
*/
|
|
|
|
int ACLCheckUserCredentials(robj *username, robj *password) {
|
|
|
|
/* For now only the "default" user is allowed. When the RCP1 ACLs
|
|
|
|
* will be implemented multiple usernames will be supproted. */
|
|
|
|
if (username != NULL && strcmp(username->ptr,"default")) {
|
|
|
|
errno = ENOENT;
|
|
|
|
return C_ERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* For now we just compare the password with the system wide one. */
|
|
|
|
if (!time_independent_strcmp(password->ptr, server.requirepass)) {
|
|
|
|
return C_OK;
|
|
|
|
} else {
|
|
|
|
errno = EINVAL;
|
|
|
|
return C_ERR;
|
|
|
|
}
|
|
|
|
}
|
2019-01-09 15:31:29 -05:00
|
|
|
|
|
|
|
/* For ACL purposes, every user has a bitmap with the commands that such
|
|
|
|
* user is allowed to execute. In order to populate the bitmap, every command
|
|
|
|
* should have an assigned ID (that is used to index the bitmap). This function
|
|
|
|
* creates such an ID: it uses sequential IDs, reusing the same ID for the same
|
|
|
|
* 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) {
|
|
|
|
static rax *map = NULL;
|
2019-01-09 15:47:43 -05:00
|
|
|
static unsigned long nextid = 0;
|
2019-01-09 15:31:29 -05:00
|
|
|
|
|
|
|
if (map == NULL) map = raxNew();
|
|
|
|
void *id = raxFind(map,(unsigned char*)cmdname,strlen(cmdname));
|
|
|
|
if (id != raxNotFound) return (unsigned long)id;
|
|
|
|
raxInsert(map,(unsigned char*)cmdname,strlen(cmdname),(void*)nextid,NULL);
|
|
|
|
return nextid++;
|
|
|
|
}
|
2019-01-10 10:33:48 -05:00
|
|
|
|
|
|
|
/* Return an username by its name, or NULL if the user does not exist. */
|
|
|
|
user *ACLGetUserByName(const char *name, size_t namelen) {
|
2019-01-10 10:40:45 -05:00
|
|
|
void *myuser = raxFind(Users,(unsigned char*)name,namelen);
|
|
|
|
if (myuser == raxNotFound) return NULL;
|
|
|
|
return myuser;
|
2019-01-10 10:33:48 -05:00
|
|
|
}
|
2019-01-10 10:35:55 -05:00
|
|
|
|
2019-01-14 07:19:50 -05:00
|
|
|
/* Check if the command ready to be excuted in the client 'c', and already
|
|
|
|
* referenced by c->cmd, can be executed by this client according to the
|
|
|
|
* ACls associated to the client user c->user.
|
|
|
|
*
|
|
|
|
* If the user can execute the command C_OK is returned, otherwise
|
|
|
|
* C_ERR is returned. */
|
|
|
|
int ACLCheckCommandPerm(client *c) {
|
2019-01-14 12:35:21 -05:00
|
|
|
user *u = c->user;
|
|
|
|
uint64_t id = c->cmd->id;
|
|
|
|
|
2019-01-14 07:19:50 -05:00
|
|
|
/* If there is no associated user, the connection can run anything. */
|
2019-01-14 12:35:21 -05:00
|
|
|
if (u == NULL) return C_OK;
|
|
|
|
|
|
|
|
/* We have to deny every command with an ID that overflows the Redis
|
|
|
|
* internal structures. Very unlikely to happen. */
|
|
|
|
if (c->cmd->id >= USER_MAX_COMMAND_BIT) return C_ERR;
|
2019-01-14 07:19:50 -05:00
|
|
|
|
|
|
|
/* Check if the user can execute this command. */
|
2019-01-14 12:35:21 -05:00
|
|
|
if (!(u->flags & USER_FLAG_ALLCOMMANDS)) {
|
|
|
|
uint64_t wordid = id / sizeof(u->allowed_commands[0]) / 8;
|
|
|
|
uint64_t bit = 1 << (id % (sizeof(u->allowed_commands[0] * 8)));
|
|
|
|
/* If the bit is not set we have to check further, in case the
|
|
|
|
* command is allowed just with that specific subcommand. */
|
|
|
|
if (!(u->allowed_commands[wordid] & bit)) {
|
|
|
|
/* Check if the subcommand matches. */
|
|
|
|
if (u->allowed_subcommands == NULL || c->argc < 2) return C_ERR;
|
|
|
|
long subid = 0;
|
|
|
|
while (1) {
|
|
|
|
if (u->allowed_subcommands[id][subid] == NULL) return C_ERR;
|
|
|
|
if (!strcasecmp(c->argv[1]->ptr,
|
|
|
|
u->allowed_subcommands[id][subid]))
|
|
|
|
break; /* Subcommand match found. Stop here. */
|
|
|
|
subid++;
|
|
|
|
}
|
|
|
|
}
|
2019-01-14 07:19:50 -05:00
|
|
|
}
|
|
|
|
|
2019-01-14 12:35:21 -05:00
|
|
|
/* Check if the user can execute commands explicitly touching the keys
|
|
|
|
* mentioned in the command arguments. */
|
|
|
|
if (!(c->user->flags & USER_FLAG_ALLKEYS) &&
|
|
|
|
(c->cmd->getkeys_proc || c->cmd->firstkey))
|
|
|
|
{
|
2019-01-14 07:19:50 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/* If we survived all the above checks, the user can execute the
|
|
|
|
* command. */
|
|
|
|
return C_OK;
|
|
|
|
}
|
|
|
|
|
2019-01-10 10:35:55 -05:00
|
|
|
/* =============================================================================
|
|
|
|
* ACL related commands
|
|
|
|
* ==========================================================================*/
|
2019-01-15 03:36:12 -05:00
|
|
|
|
|
|
|
/* ACL -- show and modify the configuration of ACL users.
|
|
|
|
* ACL help
|
|
|
|
* ACL list
|
|
|
|
* ACL setuser <username> ... user attribs ...
|
|
|
|
* ACL deluser <username>
|
|
|
|
* ACL getuser <username>
|
|
|
|
*/
|
|
|
|
void aclCommand(client *c) {
|
|
|
|
char *sub = c->argv[1]->ptr;
|
|
|
|
if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
|
|
|
|
sds username = c->argv[2]->ptr;
|
|
|
|
user *u = ACLGetUserByName(username,sdslen(username));
|
|
|
|
if (!u) u = ACLCreateUser(username,sdslen(username));
|
|
|
|
serverAssert(u != NULL);
|
|
|
|
for (int j = 3; j < c->argc; j++) {
|
2019-01-15 06:58:54 -05:00
|
|
|
if (ACLSetUser(u,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK) {
|
|
|
|
addReplyErrorFormat(c,
|
|
|
|
"Syntax error in ACL SETUSER modifier '%s'",
|
2019-01-15 03:36:12 -05:00
|
|
|
c->argv[j]->ptr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
addReply(c,shared.ok);
|
|
|
|
} else if (!strcasecmp(sub,"help")) {
|
|
|
|
const char *help[] = {
|
|
|
|
"LIST -- List all the registered users.",
|
|
|
|
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
|
|
|
"DELUSER <username> -- Delete a user.",
|
|
|
|
"GETUSER <username> -- Get the user details.",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
addReplyHelp(c,help);
|
|
|
|
} else {
|
|
|
|
addReplySubcommandSyntaxError(c);
|
|
|
|
}
|
|
|
|
}
|