Merge branch 'unstable' of https://github.com/antirez/redis into unstable
This commit is contained in:
commit
b4fb131c61
127
redis.conf
127
redis.conf
|
@ -493,20 +493,127 @@ replica-priority 100
|
|||
|
||||
################################## SECURITY ###################################
|
||||
|
||||
# Require clients to issue AUTH <PASSWORD> before processing any other
|
||||
# commands. This might be useful in environments in which you do not trust
|
||||
# others with access to the host running redis-server.
|
||||
#
|
||||
# This should stay commented out for backward compatibility and because most
|
||||
# people do not need auth (e.g. they run their own servers).
|
||||
#
|
||||
# Warning: since Redis is pretty fast an outside user can try up to
|
||||
# 150k passwords per second against a good box. This means that you should
|
||||
# use a very strong password otherwise it will be very easy to break.
|
||||
# 1 million passwords per second against a modern box. This means that you
|
||||
# should use very strong passwords, otherwise they will be very easy to break.
|
||||
# Note that because the password is really a shared secret between the client
|
||||
# and the server, and should not be memorized by any human, the password
|
||||
# can be easily a long string from /dev/urandom or whatever, so by using a
|
||||
# long and unguessable password no brute force attack will be possible.
|
||||
|
||||
# Redis ACL users are defined in the following format:
|
||||
#
|
||||
# user <username> ... acl rules ...
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
|
||||
#
|
||||
# The special username "default" is used for new connections. If this user
|
||||
# has the "nopass" rule, then new connections will be immediately authenticated
|
||||
# as the "default" user without the need of any password provided via the
|
||||
# AUTH command. Otherwise if the "default" user is not flagged with "nopass"
|
||||
# the connections will start in not authenticated state, and will require
|
||||
# AUTH (or the HELLO command AUTH option) in order to be authenticated and
|
||||
# start to work.
|
||||
#
|
||||
# The ACL rules that describe what an user can do are the following:
|
||||
#
|
||||
# on Enable the user: it is possible to authenticate as this user.
|
||||
# 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
|
||||
# +@<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
|
||||
# the Redis command table is described and defined.
|
||||
# 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 "+".
|
||||
# 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.
|
||||
# ~<pattern> Add a pattern of keys that can be mentioned as part of
|
||||
# commands. For instance ~* allows all the keys. The pattern
|
||||
# is a glob-style pattern like the one of KEYS.
|
||||
# It is possible to specify multiple patterns.
|
||||
# allkeys Alias for ~*
|
||||
# resetkeys Flush the list of allowed keys patterns.
|
||||
# ><password> Add this passowrd to the list of valid password for the user.
|
||||
# For example >mypass will add "mypass" to the list.
|
||||
# This directive clears the "nopass" flag (see later).
|
||||
# <<password> Remove this password from the list of valid passwords.
|
||||
# nopass All the set passwords of the user are removed, and the user
|
||||
# is flagged as requiring no password: it means that every
|
||||
# password will work against this user. If this directive is
|
||||
# used for the default user, every new connection will be
|
||||
# immediately authenticated with the default user without
|
||||
# any explicit AUTH command required. Note that the "resetpass"
|
||||
# directive will clear this condition.
|
||||
# resetpass Flush the list of allowed passwords. Moreover removes the
|
||||
# "nopass" status. After "resetpass" the user has no associated
|
||||
# passwords and there is no way to authenticate without adding
|
||||
# some password (or setting it as "nopass" later).
|
||||
# reset Performs the following actions: resetpass, resetkeys, off,
|
||||
# -@all. The user returns to the same state it has immediately
|
||||
# after its creation.
|
||||
#
|
||||
# ACL rules can be specified in any order: for instance you can start with
|
||||
# passwords, then flags, or key patterns. However note that the additive
|
||||
# and subtractive rules will CHANGE MEANING depending on the ordering.
|
||||
# For instance see the following example:
|
||||
#
|
||||
# user alice on +@all -DEBUG ~* >somepassword
|
||||
#
|
||||
# This will allow "alice" to use all the commands with the exception of the
|
||||
# DEBUG command, since +@all added all the commands to the set of the commands
|
||||
# alice can use, and later DEBUG was removed. However if we invert the order
|
||||
# of two ACL rules the result will be different:
|
||||
#
|
||||
# user alice on -DEBUG +@all ~* >somepassword
|
||||
#
|
||||
# Now DEBUG was removed when alice had yet no commands in the set of allowed
|
||||
# commands, later all the commands are added, so the user will be able to
|
||||
# execute everything.
|
||||
#
|
||||
# Basically ACL rules are processed left-to-right.
|
||||
#
|
||||
# For more information about ACL configuration please refer to
|
||||
# the Redis web site at https://redis.io/topics/acl
|
||||
|
||||
# Using an external ACL file
|
||||
#
|
||||
# Instead of configuring users here in this file, it is possible to use
|
||||
# a stand-alone file just listing users. The two methods cannot be mixed:
|
||||
# if you configure users here and at the same time you activate the exteranl
|
||||
# ACL file, the server will refuse to start.
|
||||
#
|
||||
# The format of the external ACL user file is exactly the same as the
|
||||
# format that is used inside redis.conf to describe users.
|
||||
#
|
||||
# aclfile /etc/redis/users.acl
|
||||
|
||||
# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatiblity
|
||||
# layer on top of the new ACL system. The option effect will be just setting
|
||||
# the password for the default user. Clients will still authenticate using
|
||||
# AUTH <password> as usually, or more explicitly with AUTH default <password>
|
||||
# if they follow the new protocol: both will work.
|
||||
#
|
||||
# requirepass foobared
|
||||
|
||||
# Command renaming.
|
||||
# Command renaming (DEPRECATED).
|
||||
#
|
||||
# ------------------------------------------------------------------------
|
||||
# WARNING: avoid using this option if possible. Instead use ACLs to remove
|
||||
# commands from the default user, and put them only in some admin user you
|
||||
# create for administrative purposes.
|
||||
# ------------------------------------------------------------------------
|
||||
#
|
||||
# It is possible to change the name of dangerous commands in a shared
|
||||
# environment. For instance the CONFIG command may be renamed into something
|
||||
|
|
575
src/acl.c
575
src/acl.c
|
@ -34,10 +34,19 @@
|
|||
* ==========================================================================*/
|
||||
|
||||
rax *Users; /* Table mapping usernames to user structures. */
|
||||
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. */
|
||||
|
||||
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. */
|
||||
|
||||
list *UsersToLoad; /* This is a list of users found in the configuration file
|
||||
that we'll need to load in the final stage of Redis
|
||||
initialization, after all the modules are already
|
||||
loaded. Every list element is a NULL terminated
|
||||
array of SDS pointers: the first is the user name,
|
||||
all the remaining pointers are ACL rules in the same
|
||||
format as ACLSetUser(). */
|
||||
|
||||
struct ACLCategoryItem {
|
||||
const char *name;
|
||||
|
@ -64,10 +73,24 @@ struct ACLCategoryItem {
|
|||
{"connection", CMD_CATEGORY_CONNECTION},
|
||||
{"transaction", CMD_CATEGORY_TRANSACTION},
|
||||
{"scripting", CMD_CATEGORY_SCRIPTING},
|
||||
{"",0} /* Terminator. */
|
||||
{NULL,0} /* Terminator. */
|
||||
};
|
||||
|
||||
struct ACLUserFlag {
|
||||
const char *name;
|
||||
uint64_t flag;
|
||||
} ACLUserFlags[] = {
|
||||
{"on", USER_FLAG_ENABLED},
|
||||
{"off", USER_FLAG_DISABLED},
|
||||
{"allkeys", USER_FLAG_ALLKEYS},
|
||||
{"allcommands", USER_FLAG_ALLCOMMANDS},
|
||||
{"nopass", USER_FLAG_NOPASS},
|
||||
{NULL,0} /* Terminator. */
|
||||
};
|
||||
|
||||
void ACLResetSubcommandsForCommand(user *u, unsigned long id);
|
||||
void ACLResetSubcommands(user *u);
|
||||
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
|
||||
|
||||
/* =============================================================================
|
||||
* Helper functions for the rest of the ACL implementation
|
||||
|
@ -141,6 +164,11 @@ void ACLListFreeSds(void *item) {
|
|||
sdsfree(item);
|
||||
}
|
||||
|
||||
/* Method to duplicate list elements from ACL users password/ptterns lists. */
|
||||
void *ACLListDupSds(void *item) {
|
||||
return sdsdup(item);
|
||||
}
|
||||
|
||||
/* 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.
|
||||
|
@ -150,19 +178,80 @@ user *ACLCreateUser(const char *name, size_t namelen) {
|
|||
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
|
||||
user *u = zmalloc(sizeof(*u));
|
||||
u->name = sdsnewlen(name,namelen);
|
||||
u->flags = 0;
|
||||
u->flags = USER_FLAG_DISABLED;
|
||||
u->allowed_subcommands = NULL;
|
||||
u->passwords = listCreate();
|
||||
u->patterns = listCreate();
|
||||
listSetMatchMethod(u->passwords,ACLListMatchSds);
|
||||
listSetFreeMethod(u->passwords,ACLListFreeSds);
|
||||
listSetDupMethod(u->passwords,ACLListDupSds);
|
||||
listSetMatchMethod(u->patterns,ACLListMatchSds);
|
||||
listSetFreeMethod(u->patterns,ACLListFreeSds);
|
||||
listSetDupMethod(u->patterns,ACLListDupSds);
|
||||
memset(u->allowed_commands,0,sizeof(u->allowed_commands));
|
||||
raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
|
||||
return u;
|
||||
}
|
||||
|
||||
/* This function should be called when we need an unlinked "fake" user
|
||||
* we can use in order to validate ACL rules or for other similar reasons.
|
||||
* The user will not get linked to the Users radix tree. The returned
|
||||
* user should be released with ACLFreeUser() as usually. */
|
||||
user *ACLCreateUnlinkedUser(void) {
|
||||
char username[64];
|
||||
for (int j = 0; ; j++) {
|
||||
snprintf(username,sizeof(username),"__fakeuser:%d__",j);
|
||||
user *fakeuser = ACLCreateUser(username,strlen(username));
|
||||
if (fakeuser == NULL) continue;
|
||||
int retval = raxRemove(Users,(unsigned char*) username,
|
||||
strlen(username),NULL);
|
||||
serverAssert(retval != 0);
|
||||
return fakeuser;
|
||||
}
|
||||
}
|
||||
|
||||
/* Release the memory used by the user structure. Note that this function
|
||||
* will not remove the user from the Users global radix tree. */
|
||||
void ACLFreeUser(user *u) {
|
||||
sdsfree(u->name);
|
||||
listRelease(u->passwords);
|
||||
listRelease(u->patterns);
|
||||
ACLResetSubcommands(u);
|
||||
zfree(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) {
|
||||
listRelease(dst->passwords);
|
||||
listRelease(dst->patterns);
|
||||
dst->passwords = listDup(src->passwords);
|
||||
dst->patterns = listDup(src->patterns);
|
||||
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) {
|
||||
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++)
|
||||
{
|
||||
ACLAddAllowedSubcommand(dst, j,
|
||||
src->allowed_subcommands[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Free all the users registered in the radix tree 'users' and free the
|
||||
* radix tree itself. */
|
||||
void ACLFreeUsersSet(rax *users) {
|
||||
raxFreeWithCallback(users,(void(*)(void*))ACLFreeUser);
|
||||
}
|
||||
|
||||
/* Given a command ID, this function set by reference 'word' and 'bit'
|
||||
* so that user->allowed_commands[word] will address the right word
|
||||
* where the corresponding bit for the provided ID is stored, and
|
||||
|
@ -224,6 +313,7 @@ int ACLSetUserCommandBitsForCategory(user *u, const char *category, int value) {
|
|||
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);
|
||||
|
@ -360,6 +450,58 @@ sds ACLDescribeUserCommandRules(user *u) {
|
|||
return rules;
|
||||
}
|
||||
|
||||
/* This is similar to ACLDescribeUserCommandRules(), however instead of
|
||||
* describing just the user command rules, everything is described: user
|
||||
* flags, keys, passwords and finally the command rules obtained via
|
||||
* the ACLDescribeUserCommandRules() function. This is the function we call
|
||||
* when we want to rewrite the configuration files describing ACLs and
|
||||
* in order to show users with ACL LIST. */
|
||||
sds ACLDescribeUser(user *u) {
|
||||
sds res = sdsempty();
|
||||
|
||||
/* Flags. */
|
||||
for (int j = 0; ACLUserFlags[j].flag; j++) {
|
||||
/* Skip the allcommands and allkeys flags because they'll be emitted
|
||||
* later as ~* and +@all. */
|
||||
if (ACLUserFlags[j].flag == USER_FLAG_ALLKEYS ||
|
||||
ACLUserFlags[j].flag == USER_FLAG_ALLCOMMANDS) continue;
|
||||
if (u->flags & ACLUserFlags[j].flag) {
|
||||
res = sdscat(res,ACLUserFlags[j].name);
|
||||
res = sdscatlen(res," ",1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Passwords. */
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(u->passwords,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
sds thispass = listNodeValue(ln);
|
||||
res = sdscatlen(res,">",1);
|
||||
res = sdscatsds(res,thispass);
|
||||
res = sdscatlen(res," ",1);
|
||||
}
|
||||
|
||||
/* Key patterns. */
|
||||
if (u->flags & USER_FLAG_ALLKEYS) {
|
||||
res = sdscatlen(res,"~* ",3);
|
||||
} else {
|
||||
listRewind(u->patterns,&li);
|
||||
while((ln = listNext(&li))) {
|
||||
sds thispat = listNodeValue(ln);
|
||||
res = sdscatlen(res,"~",1);
|
||||
res = sdscatsds(res,thispat);
|
||||
res = sdscatlen(res," ",1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Command rules. */
|
||||
sds rules = ACLDescribeUserCommandRules(u);
|
||||
res = sdscatsds(res,rules);
|
||||
sdsfree(rules);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
|
@ -500,7 +642,9 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
|
|||
if (oplen == -1) oplen = strlen(op);
|
||||
if (!strcasecmp(op,"on")) {
|
||||
u->flags |= USER_FLAG_ENABLED;
|
||||
u->flags &= ~USER_FLAG_DISABLED;
|
||||
} else if (!strcasecmp(op,"off")) {
|
||||
u->flags |= USER_FLAG_DISABLED;
|
||||
u->flags &= ~USER_FLAG_ENABLED;
|
||||
} else if (!strcasecmp(op,"allkeys") ||
|
||||
!strcasecmp(op,"~*"))
|
||||
|
@ -655,9 +799,9 @@ sds ACLDefaultUserFirstPassword(void) {
|
|||
return listNodeValue(first);
|
||||
}
|
||||
|
||||
/* Initialization of the ACL subsystem. */
|
||||
void ACLInit(void) {
|
||||
Users = raxNew();
|
||||
/* Initialize the default user, that will always exist for all the process
|
||||
* lifetime. */
|
||||
void ACLInitDefaultUser(void) {
|
||||
DefaultUser = ACLCreateUser("default",7);
|
||||
ACLSetUser(DefaultUser,"+@all",-1);
|
||||
ACLSetUser(DefaultUser,"~*",-1);
|
||||
|
@ -665,6 +809,13 @@ void ACLInit(void) {
|
|||
ACLSetUser(DefaultUser,"nopass",-1);
|
||||
}
|
||||
|
||||
/* Initialization of the ACL subsystem. */
|
||||
void ACLInit(void) {
|
||||
Users = raxNew();
|
||||
UsersToLoad = listCreate();
|
||||
ACLInitDefaultUser();
|
||||
}
|
||||
|
||||
/* Check the username and password pair and return C_OK if they are valid,
|
||||
* otherwise C_ERR is returned and errno is set to:
|
||||
*
|
||||
|
@ -679,7 +830,7 @@ int ACLCheckUserCredentials(robj *username, robj *password) {
|
|||
}
|
||||
|
||||
/* Disabled users can't login. */
|
||||
if ((u->flags & USER_FLAG_ENABLED) == 0) {
|
||||
if (u->flags & USER_FLAG_DISABLED) {
|
||||
errno = EINVAL;
|
||||
return C_ERR;
|
||||
}
|
||||
|
@ -827,6 +978,289 @@ int ACLCheckCommandPerm(client *c) {
|
|||
return ACL_OK;
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* ACL loading / saving functions
|
||||
* ==========================================================================*/
|
||||
|
||||
/* Given an argument vector describing a user in the form:
|
||||
*
|
||||
* user <username> ... ACL rules and flags ...
|
||||
*
|
||||
* this function validates, and if the syntax is valid, appends
|
||||
* the user definition to a list for later loading.
|
||||
*
|
||||
* The rules are tested for validity and if there obvious syntax errors
|
||||
* the function returns C_ERR and does nothing, otherwise C_OK is returned
|
||||
* and the user is appended to the list.
|
||||
*
|
||||
* Note that this function cannot stop in case of commands that are not found
|
||||
* and, in that case, the error will be emitted later, because certain
|
||||
* commands may be defined later once modules are loaded.
|
||||
*
|
||||
* When an error is detected and C_ERR is returned, the function populates
|
||||
* by reference (if not set to NULL) the argc_err argument with the index
|
||||
* of the argv vector that caused the error. */
|
||||
int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) {
|
||||
if (argc < 2 || strcasecmp(argv[0],"user")) {
|
||||
if (argc_err) *argc_err = 0;
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Try to apply the user rules in a fake user to see if they
|
||||
* are actually valid. */
|
||||
user *fakeuser = ACLCreateUnlinkedUser();
|
||||
|
||||
for (int j = 2; j < argc; j++) {
|
||||
if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) == C_ERR) {
|
||||
if (errno != ENOENT) {
|
||||
ACLFreeUser(fakeuser);
|
||||
if (argc_err) *argc_err = j;
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Rules look valid, let's append the user to the list. */
|
||||
sds *copy = zmalloc(sizeof(sds)*argc);
|
||||
for (int j = 1; j < argc; j++) copy[j-1] = sdsdup(argv[j]);
|
||||
copy[argc-1] = NULL;
|
||||
listAddNodeTail(UsersToLoad,copy);
|
||||
ACLFreeUser(fakeuser);
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(UsersToLoad,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
sds *aclrules = listNodeValue(ln);
|
||||
sds username = aclrules[0];
|
||||
user *u = ACLCreateUser(username,sdslen(username));
|
||||
if (!u) {
|
||||
u = ACLGetUserByName(username,sdslen(username));
|
||||
serverAssert(u != NULL);
|
||||
ACLSetUser(u,"reset",-1);
|
||||
}
|
||||
|
||||
/* Load every rule defined for this user. */
|
||||
for (int j = 1; aclrules[j]; j++) {
|
||||
if (ACLSetUser(u,aclrules[j],sdslen(aclrules[j])) != C_OK) {
|
||||
char *errmsg = ACLSetUserStringError();
|
||||
serverLog(LL_WARNING,"Error loading ACL rule '%s' for "
|
||||
"the user named '%s': %s",
|
||||
aclrules[j],aclrules[0],errmsg);
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
|
||||
/* Having a disabled user in the configuration may be an error,
|
||||
* warn about it without returning any error to the caller. */
|
||||
if (u->flags & USER_FLAG_DISABLED) {
|
||||
serverLog(LL_NOTICE, "The user '%s' is disabled (there is no "
|
||||
"'on' modifier in the user description). Make "
|
||||
"sure this is not a configuration error.",
|
||||
aclrules[0]);
|
||||
}
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* This function loads the ACL from the specified filename: every line
|
||||
* is validated and shold be either empty or in the format used to specify
|
||||
* users in the redis.conf configuration or in the ACL file, that is:
|
||||
*
|
||||
* user <username> ... rules ...
|
||||
*
|
||||
* Note that this function considers comments starting with '#' as errors
|
||||
* because the ACL file is meant to be rewritten, and comments would be
|
||||
* lost after the rewrite. Yet empty lines are allowed to avoid being too
|
||||
* strict.
|
||||
*
|
||||
* One important part of implementing ACL LOAD, that uses this function, is
|
||||
* to avoid ending with broken rules if the ACL file is invalid for some
|
||||
* reason, so the function will attempt to validate the rules before loading
|
||||
* each user. For every line that will be found broken the function will
|
||||
* collect an error message.
|
||||
*
|
||||
* IMPORTANT: If there is at least a single error, nothing will be loaded
|
||||
* and the rules will remain exactly as they were.
|
||||
*
|
||||
* 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) {
|
||||
FILE *fp;
|
||||
char buf[1024];
|
||||
|
||||
/* Open the ACL file. */
|
||||
if ((fp = fopen(filename,"r")) == NULL) {
|
||||
sds errors = sdscatprintf(sdsempty(),
|
||||
"Error loading ACLs, opening file '%s': %s",
|
||||
filename, strerror(errno));
|
||||
return errors;
|
||||
}
|
||||
|
||||
/* Load the whole file as a single string in memory. */
|
||||
sds acls = sdsempty();
|
||||
while(fgets(buf,sizeof(buf),fp) != NULL)
|
||||
acls = sdscat(acls,buf);
|
||||
fclose(fp);
|
||||
|
||||
/* Split the file into lines and attempt to load each line. */
|
||||
int totlines;
|
||||
sds *lines, errors = sdsempty();
|
||||
lines = sdssplitlen(acls,strlen(acls),"\n",1,&totlines);
|
||||
|
||||
/* We need a fake user to validate the rules before making changes
|
||||
* to the real user mentioned in the ACL line. */
|
||||
user *fakeuser = ACLCreateUnlinkedUser();
|
||||
|
||||
/* We do all the loading in a fresh insteance of the Users radix tree,
|
||||
* so if there are errors loading the ACL file we can rollback to the
|
||||
* old version. */
|
||||
rax *old_users = Users;
|
||||
user *old_default_user = DefaultUser;
|
||||
Users = raxNew();
|
||||
ACLInitDefaultUser();
|
||||
|
||||
/* Load each line of the file. */
|
||||
for (int i = 0; i < totlines; i++) {
|
||||
sds *argv;
|
||||
int argc;
|
||||
int linenum = i+1;
|
||||
|
||||
lines[i] = sdstrim(lines[i]," \t\r\n");
|
||||
|
||||
/* Skip blank lines */
|
||||
if (lines[i][0] == '\0') continue;
|
||||
|
||||
/* Split into arguments */
|
||||
argv = sdssplitargs(lines[i],&argc);
|
||||
if (argv == NULL) {
|
||||
errors = sdscatprintf(errors,
|
||||
"%s:%d: unbalanced quotes in acl line. ",
|
||||
server.acl_filename, linenum);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Skip this line if the resulting command vector is empty. */
|
||||
if (argc == 0) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* The line should start with the "user" keyword. */
|
||||
if (strcmp(argv[0],"user") || argc < 2) {
|
||||
errors = sdscatprintf(errors,
|
||||
"%s:%d should start with user keyword followed "
|
||||
"by the username. ", server.acl_filename,
|
||||
linenum);
|
||||
sdsfreesplitres(argv,argc);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Try to process the line using the fake user to validate iif
|
||||
* the rules are able to apply cleanly. */
|
||||
ACLSetUser(fakeuser,"reset",-1);
|
||||
int j;
|
||||
for (j = 2; j < argc; j++) {
|
||||
if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) != C_OK) {
|
||||
char *errmsg = ACLSetUserStringError();
|
||||
errors = sdscatprintf(errors,
|
||||
"%s:%d: %s. ",
|
||||
server.acl_filename, linenum, errmsg);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/* Apply the rule to the new users set only if so far there
|
||||
* are no errors, otherwise it's useless since we are going
|
||||
* to discard the new users set anyway. */
|
||||
if (sdslen(errors) != 0) {
|
||||
sdsfreesplitres(argv,argc);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* We can finally lookup the user and apply the rule. If the
|
||||
* user already exists we always reset it to start. */
|
||||
user *u = ACLCreateUser(argv[1],sdslen(argv[1]));
|
||||
if (!u) {
|
||||
u = ACLGetUserByName(argv[1],sdslen(argv[1]));
|
||||
serverAssert(u != NULL);
|
||||
ACLSetUser(u,"reset",-1);
|
||||
}
|
||||
|
||||
/* Note that the same rules already applied to the fake user, so
|
||||
* we just assert that everything goess well: it should. */
|
||||
for (j = 2; j < argc; j++)
|
||||
serverAssert(ACLSetUser(u,argv[j],sdslen(argv[j])) == C_OK);
|
||||
|
||||
sdsfreesplitres(argv,argc);
|
||||
}
|
||||
|
||||
ACLFreeUser(fakeuser);
|
||||
sdsfreesplitres(lines,totlines);
|
||||
DefaultUser = old_default_user; /* This pointer must never change. */
|
||||
|
||||
/* Check if we found errors and react accordingly. */
|
||||
if (sdslen(errors) == 0) {
|
||||
/* The default user pointer is referenced in different places: instead
|
||||
* of replacing such occurrences it is much simpler to copy the new
|
||||
* default user configuration in the old one. */
|
||||
user *new = ACLGetUserByName("default",7);
|
||||
serverAssert(new != NULL);
|
||||
ACLCopyUser(DefaultUser,new);
|
||||
ACLFreeUser(new);
|
||||
raxInsert(Users,(unsigned char*)"default",7,DefaultUser,NULL);
|
||||
raxRemove(old_users,(unsigned char*)"default",7,NULL);
|
||||
ACLFreeUsersSet(old_users);
|
||||
sdsfree(errors);
|
||||
return NULL;
|
||||
} else {
|
||||
ACLFreeUsersSet(Users);
|
||||
Users = old_users;
|
||||
errors = sdscat(errors,"WARNING: ACL errors detected, no change to the previously active ACL rules was performed");
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called once the server is already running, modules are
|
||||
* loaded, and we are ready to start, in order to load the ACLs either from
|
||||
* the pending list of users defined in redis.conf, or from the ACL file.
|
||||
* The function will just exit with an error if the user is trying to mix
|
||||
* both the loading methods. */
|
||||
void ACLLoadUsersAtStartup(void) {
|
||||
if (server.acl_filename[0] != '\0' && listLength(UsersToLoad) != 0) {
|
||||
serverLog(LL_WARNING,
|
||||
"Configuring Redis with users defined in redis.conf and at "
|
||||
"the same setting an ACL file path is invalid. This setup "
|
||||
"is very likely to lead to configuration errors and security "
|
||||
"holes, please define either an ACL file or declare users "
|
||||
"directly in your redis.conf, but not both.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (ACLLoadConfiguredUsers() == C_ERR) {
|
||||
serverLog(LL_WARNING,
|
||||
"Critical error while loading ACLs. Exiting.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (server.acl_filename[0] != '\0') {
|
||||
sds errors = ACLLoadFromFile(server.acl_filename);
|
||||
if (errors) {
|
||||
serverLog(LL_WARNING,
|
||||
"Aborting Redis startup because of ACL errors: %s", errors);
|
||||
sdsfree(errors);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
* ACL related commands
|
||||
* ==========================================================================*/
|
||||
|
@ -834,7 +1268,9 @@ int ACLCheckCommandPerm(client *c) {
|
|||
/* ACL -- show and modify the configuration of ACL users.
|
||||
* ACL HELP
|
||||
* ACL LIST
|
||||
* ACL SETUSER <username> ... user attribs ...
|
||||
* ACL USERS
|
||||
* ACL CAT [<category>]
|
||||
* ACL SETUSER <username> ... acl rules ...
|
||||
* ACL DELUSER <username>
|
||||
* ACL GETUSER <username>
|
||||
*/
|
||||
|
@ -855,12 +1291,44 @@ void aclCommand(client *c) {
|
|||
}
|
||||
}
|
||||
addReply(c,shared.ok);
|
||||
} else if (!strcasecmp(sub,"whoami")) {
|
||||
if (c->user != NULL) {
|
||||
addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name));
|
||||
} else {
|
||||
addReplyNull(c);
|
||||
} else if (!strcasecmp(sub,"deluser") && c->argc >= 3) {
|
||||
int deleted = 0;
|
||||
for (int j = 2; j < c->argc; j++) {
|
||||
sds username = c->argv[j]->ptr;
|
||||
if (!strcmp(username,"default")) {
|
||||
addReplyError(c,"The 'default' user cannot be removed");
|
||||
return;
|
||||
}
|
||||
user *u;
|
||||
if (raxRemove(Users,(unsigned char*)username,
|
||||
sdslen(username),
|
||||
(void**)&u))
|
||||
{
|
||||
/* When a user is deleted we need to cycle the active
|
||||
* connections in order to kill all the pending ones that
|
||||
* are authenticated with such user. */
|
||||
ACLFreeUser(u);
|
||||
listIter li;
|
||||
listNode *ln;
|
||||
listRewind(server.clients,&li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
if (c->user == u) {
|
||||
/* We'll free the conenction asynchronously, so
|
||||
* in theory to set a different user is not needed.
|
||||
* However if there are bugs in Redis, soon or later
|
||||
* this may result in some security hole: it's much
|
||||
* more defensive to set the default user and put
|
||||
* it in non authenticated mode. */
|
||||
c->user = DefaultUser;
|
||||
c->authenticated = 0;
|
||||
freeClientAsync(c);
|
||||
}
|
||||
}
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
addReplyLongLong(c,deleted);
|
||||
} else if (!strcasecmp(sub,"getuser") && c->argc == 3) {
|
||||
user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
|
||||
if (u == NULL) {
|
||||
|
@ -874,24 +1342,11 @@ void aclCommand(client *c) {
|
|||
addReplyBulkCString(c,"flags");
|
||||
void *deflen = addReplyDeferredLen(c);
|
||||
int numflags = 0;
|
||||
if (u->flags & USER_FLAG_ENABLED) {
|
||||
addReplyBulkCString(c,"on");
|
||||
numflags++;
|
||||
} else {
|
||||
addReplyBulkCString(c,"off");
|
||||
numflags++;
|
||||
}
|
||||
if (u->flags & USER_FLAG_ALLKEYS) {
|
||||
addReplyBulkCString(c,"allkeys");
|
||||
numflags++;
|
||||
}
|
||||
if (u->flags & USER_FLAG_ALLCOMMANDS) {
|
||||
addReplyBulkCString(c,"allcommands");
|
||||
numflags++;
|
||||
}
|
||||
if (u->flags & USER_FLAG_NOPASS) {
|
||||
addReplyBulkCString(c,"nopass");
|
||||
numflags++;
|
||||
for (int j = 0; ACLUserFlags[j].flag; j++) {
|
||||
if (u->flags & ACLUserFlags[j].flag) {
|
||||
addReplyBulkCString(c,ACLUserFlags[j].name);
|
||||
numflags++;
|
||||
}
|
||||
}
|
||||
setDeferredSetLen(c,deflen,numflags);
|
||||
|
||||
|
@ -926,13 +1381,59 @@ void aclCommand(client *c) {
|
|||
addReplyBulkCBuffer(c,thispat,sdslen(thispat));
|
||||
}
|
||||
}
|
||||
} else if ((!strcasecmp(sub,"list") || !strcasecmp(sub,"users")) &&
|
||||
c->argc == 2)
|
||||
{
|
||||
int justnames = !strcasecmp(sub,"users");
|
||||
addReplyArrayLen(c,raxSize(Users));
|
||||
raxIterator ri;
|
||||
raxStart(&ri,Users);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
user *u = ri.data;
|
||||
if (justnames) {
|
||||
addReplyBulkCBuffer(c,u->name,sdslen(u->name));
|
||||
} else {
|
||||
/* Return information in the configuration file format. */
|
||||
sds config = sdsnew("user ");
|
||||
config = sdscatsds(config,u->name);
|
||||
config = sdscatlen(config," ",1);
|
||||
sds descr = ACLDescribeUser(u);
|
||||
config = sdscatsds(config,descr);
|
||||
sdsfree(descr);
|
||||
addReplyBulkSds(c,config);
|
||||
}
|
||||
}
|
||||
raxStop(&ri);
|
||||
} else if (!strcasecmp(sub,"whoami") && c->argc == 2) {
|
||||
if (c->user != NULL) {
|
||||
addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name));
|
||||
} else {
|
||||
addReplyNull(c);
|
||||
}
|
||||
} else if (!strcasecmp(sub,"load") && c->argc == 2) {
|
||||
if (server.acl_filename[0] == '\0') {
|
||||
addReplyError(c,"This Redis instance is not configured to use an ACL file. You may want to specify users via the ACL SETUSER command and then issue a CONFIG REWRITE (assuming you have a Redis configuration file set) in order to store users in the Redis configuration.");
|
||||
return;
|
||||
} else {
|
||||
sds errors = ACLLoadFromFile(server.acl_filename);
|
||||
if (errors == NULL) {
|
||||
addReply(c,shared.ok);
|
||||
} else {
|
||||
addReplyError(c,errors);
|
||||
sdsfree(errors);
|
||||
}
|
||||
}
|
||||
} else if (!strcasecmp(sub,"help")) {
|
||||
const char *help[] = {
|
||||
"LIST -- List all the registered users.",
|
||||
"LIST -- Show user details in config file format.",
|
||||
"USERS -- List all the registered usernames.",
|
||||
"SETUSER <username> [attribs ...] -- Create or modify a user.",
|
||||
"DELUSER <username> -- Delete a user.",
|
||||
"GETUSER <username> -- Get the user details.",
|
||||
"WHOAMI -- Return the current username.",
|
||||
"DELUSER <username> -- Delete a user.",
|
||||
"CAT -- List available categories.",
|
||||
"CAT <category> -- List commands inside category.",
|
||||
"WHOAMI -- Return the current connection username.",
|
||||
NULL
|
||||
};
|
||||
addReplyHelp(c,help);
|
||||
|
|
48
src/config.c
48
src/config.c
|
@ -283,6 +283,9 @@ void loadServerConfigFromString(char *config) {
|
|||
}
|
||||
fclose(logfp);
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"aclfile") && argc == 2) {
|
||||
zfree(server.acl_filename);
|
||||
server.acl_filename = zstrdup(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"always-show-logo") && argc == 2) {
|
||||
if ((server.always_show_logo = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
|
@ -791,6 +794,16 @@ void loadServerConfigFromString(char *config) {
|
|||
"Allowed values: 'upstart', 'systemd', 'auto', or 'no'";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"user") && argc >= 2) {
|
||||
int argc_err;
|
||||
if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) {
|
||||
char buf[1024];
|
||||
char *errmsg = ACLSetUserStringError();
|
||||
snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s",
|
||||
argv[argc_err],errmsg);
|
||||
err = buf;
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) {
|
||||
queueLoadModule(argv[1],&argv[2],argc-2);
|
||||
} else if (!strcasecmp(argv[0],"sentinel")) {
|
||||
|
@ -1345,6 +1358,7 @@ void configGetCommand(client *c) {
|
|||
config_get_string_field("cluster-announce-ip",server.cluster_announce_ip);
|
||||
config_get_string_field("unixsocket",server.unixsocket);
|
||||
config_get_string_field("logfile",server.logfile);
|
||||
config_get_string_field("aclfile",server.acl_filename);
|
||||
config_get_string_field("pidfile",server.pidfile);
|
||||
config_get_string_field("slave-announce-ip",server.slave_announce_ip);
|
||||
config_get_string_field("replica-announce-ip",server.slave_announce_ip);
|
||||
|
@ -1907,6 +1921,38 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) {
|
|||
rewriteConfigMarkAsProcessed(state,"save");
|
||||
}
|
||||
|
||||
/* Rewrite the user option. */
|
||||
void rewriteConfigUserOption(struct rewriteConfigState *state) {
|
||||
/* If there is a user file defined we just mark this configuration
|
||||
* directive as processed, so that all the lines containing users
|
||||
* inside the config file gets discarded. */
|
||||
if (server.acl_filename[0] != '\0') {
|
||||
rewriteConfigMarkAsProcessed(state,"user");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Otherwise scan the list of users and rewrite every line. Note that
|
||||
* in case the list here is empty, the effect will just be to comment
|
||||
* all the users directive inside the config file. */
|
||||
raxIterator ri;
|
||||
raxStart(&ri,Users);
|
||||
raxSeek(&ri,"^",NULL,0);
|
||||
while(raxNext(&ri)) {
|
||||
user *u = ri.data;
|
||||
sds line = sdsnew("user ");
|
||||
line = sdscatsds(line,u->name);
|
||||
line = sdscatlen(line," ",1);
|
||||
sds descr = ACLDescribeUser(u);
|
||||
line = sdscatsds(line,descr);
|
||||
sdsfree(descr);
|
||||
rewriteConfigRewriteLine(state,"user",line,1);
|
||||
}
|
||||
raxStop(&ri);
|
||||
|
||||
/* Mark "user" as processed in case there are no defined users. */
|
||||
rewriteConfigMarkAsProcessed(state,"user");
|
||||
}
|
||||
|
||||
/* Rewrite the dir option, always using absolute paths.*/
|
||||
void rewriteConfigDirOption(struct rewriteConfigState *state) {
|
||||
char cwd[1024];
|
||||
|
@ -2172,10 +2218,12 @@ int rewriteConfig(char *path) {
|
|||
rewriteConfigNumericalOption(state,"replica-announce-port",server.slave_announce_port,CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT);
|
||||
rewriteConfigEnumOption(state,"loglevel",server.verbosity,loglevel_enum,CONFIG_DEFAULT_VERBOSITY);
|
||||
rewriteConfigStringOption(state,"logfile",server.logfile,CONFIG_DEFAULT_LOGFILE);
|
||||
rewriteConfigStringOption(state,"aclfile",server.acl_filename,CONFIG_DEFAULT_ACL_FILENAME);
|
||||
rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,CONFIG_DEFAULT_SYSLOG_ENABLED);
|
||||
rewriteConfigStringOption(state,"syslog-ident",server.syslog_ident,CONFIG_DEFAULT_SYSLOG_IDENT);
|
||||
rewriteConfigSyslogfacilityOption(state);
|
||||
rewriteConfigSaveOption(state);
|
||||
rewriteConfigUserOption(state);
|
||||
rewriteConfigNumericalOption(state,"databases",server.dbnum,CONFIG_DEFAULT_DBNUM);
|
||||
rewriteConfigYesNoOption(state,"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err,CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR);
|
||||
rewriteConfigYesNoOption(state,"rdbcompression",server.rdb_compression,CONFIG_DEFAULT_RDB_COMPRESSION);
|
||||
|
|
|
@ -684,6 +684,7 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
|
|||
cp->rediscmd->calls = 0;
|
||||
dictAdd(server.commands,sdsdup(cmdname),cp->rediscmd);
|
||||
dictAdd(server.orig_commands,sdsdup(cmdname),cp->rediscmd);
|
||||
cp->rediscmd->id = ACLGetCommandID(cmdname); /* ID used for ACL. */
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
|
@ -2696,6 +2697,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
|
|||
/* Create the client and dispatch the command. */
|
||||
va_start(ap, fmt);
|
||||
c = createClient(-1);
|
||||
c->user = NULL; /* Root user. */
|
||||
argv = moduleCreateArgvFromUserFormat(cmdname,fmt,&argc,&flags,ap);
|
||||
replicate = flags & REDISMODULE_ARGV_REPLICATE;
|
||||
va_end(ap);
|
||||
|
@ -4659,6 +4661,7 @@ void moduleInitModulesSystem(void) {
|
|||
moduleKeyspaceSubscribers = listCreate();
|
||||
moduleFreeContextReusedClient = createClient(-1);
|
||||
moduleFreeContextReusedClient->flags |= CLIENT_MODULE;
|
||||
moduleFreeContextReusedClient->user = NULL; /* root user. */
|
||||
|
||||
moduleRegisterCoreAPI();
|
||||
if (pipe(server.module_blocked_pipe) == -1) {
|
||||
|
|
|
@ -164,7 +164,7 @@ client *createClient(int fd) {
|
|||
return c;
|
||||
}
|
||||
|
||||
/* This funciton puts the client in the queue of clients that should write
|
||||
/* This function puts the client in the queue of clients that should write
|
||||
* their output buffers to the socket. Note that it does not *yet* install
|
||||
* the write handler, to start clients are put in a queue of clients that need
|
||||
* to write, so we try to do that before returning in the event loop (see the
|
||||
|
@ -1253,7 +1253,7 @@ void resetClient(client *c) {
|
|||
}
|
||||
}
|
||||
|
||||
/* This funciton is used when we want to re-enter the event loop but there
|
||||
/* This function is used when we want to re-enter the event loop but there
|
||||
* is the risk that the client we are dealing with will be freed in some
|
||||
* way. This happens for instance in:
|
||||
*
|
||||
|
|
|
@ -2267,6 +2267,7 @@ void initServerConfig(void) {
|
|||
server.pidfile = NULL;
|
||||
server.rdb_filename = zstrdup(CONFIG_DEFAULT_RDB_FILENAME);
|
||||
server.aof_filename = zstrdup(CONFIG_DEFAULT_AOF_FILENAME);
|
||||
server.acl_filename = zstrdup(CONFIG_DEFAULT_ACL_FILENAME);
|
||||
server.rdb_compression = CONFIG_DEFAULT_RDB_COMPRESSION;
|
||||
server.rdb_checksum = CONFIG_DEFAULT_RDB_CHECKSUM;
|
||||
server.stop_writes_on_bgsave_err = CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR;
|
||||
|
@ -4907,6 +4908,7 @@ int main(int argc, char **argv) {
|
|||
linuxMemoryWarnings();
|
||||
#endif
|
||||
moduleLoadFromQueue();
|
||||
ACLLoadUsersAtStartup();
|
||||
loadDataFromDisk();
|
||||
if (server.cluster_enabled) {
|
||||
if (verifyClusterConfigWithData() == C_ERR) {
|
||||
|
|
16
src/server.h
16
src/server.h
|
@ -148,6 +148,7 @@ typedef long long mstime_t; /* millisecond time type. */
|
|||
#define CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC 1
|
||||
#define CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE 0
|
||||
#define CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG 10
|
||||
#define CONFIG_DEFAULT_ACL_FILENAME ""
|
||||
#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
|
||||
#define NET_PEER_ID_LEN (NET_IP_STR_LEN+32) /* Must be enough for ip:port */
|
||||
#define CONFIG_BINDADDR_MAX 16
|
||||
|
@ -740,9 +741,10 @@ typedef struct readyList {
|
|||
command ID we can set in the user
|
||||
is USER_COMMAND_BITS_COUNT-1. */
|
||||
#define USER_FLAG_ENABLED (1<<0) /* The user is active. */
|
||||
#define USER_FLAG_ALLKEYS (1<<1) /* The user can mention any key. */
|
||||
#define USER_FLAG_ALLCOMMANDS (1<<2) /* The user can run all commands. */
|
||||
#define USER_FLAG_NOPASS (1<<3) /* The user requires no password, any
|
||||
#define USER_FLAG_DISABLED (1<<1) /* The user is disabled. */
|
||||
#define USER_FLAG_ALLKEYS (1<<2) /* The user can mention any key. */
|
||||
#define USER_FLAG_ALLCOMMANDS (1<<3) /* The user can run all commands. */
|
||||
#define USER_FLAG_NOPASS (1<<4) /* The user requires no password, any
|
||||
provided password will work. For the
|
||||
default user, this also means that
|
||||
no AUTH is needed, and every
|
||||
|
@ -1336,6 +1338,8 @@ struct redisServer {
|
|||
/* Latency monitor */
|
||||
long long latency_monitor_threshold;
|
||||
dict *latency_events;
|
||||
/* ACLs */
|
||||
char *acl_filename; /* ACL Users file. NULL if not configured. */
|
||||
/* Assert & bug reporting */
|
||||
const char *assert_failed;
|
||||
const char *assert_file;
|
||||
|
@ -1724,6 +1728,7 @@ void sendChildInfo(int process_type);
|
|||
void receiveChildInfo(void);
|
||||
|
||||
/* acl.c -- Authentication related prototypes. */
|
||||
extern rax *Users;
|
||||
extern user *DefaultUser;
|
||||
void ACLInit(void);
|
||||
/* Return values for ACLCheckUserCredentials(). */
|
||||
|
@ -1737,6 +1742,11 @@ int ACLCheckCommandPerm(client *c);
|
|||
int ACLSetUser(user *u, const char *op, ssize_t oplen);
|
||||
sds ACLDefaultUserFirstPassword(void);
|
||||
uint64_t ACLGetCommandCategoryFlagByName(const char *name);
|
||||
int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err);
|
||||
char *ACLSetUserStringError(void);
|
||||
int ACLLoadConfiguredUsers(void);
|
||||
sds ACLDescribeUser(user *u);
|
||||
void ACLLoadUsersAtStartup(void);
|
||||
|
||||
/* Sorted sets data type */
|
||||
|
||||
|
|
Loading…
Reference in New Issue