Support ACL for Sentinel Mode (#7888)

This commit implements ACL for Sentinel mode, main work of this PR includes:

- Update Sentinel command table in order to better support ACLs.
- Fix couple of things which currently blocks the support for ACL on sentinel mode.
- Provide "sentinel sentinel-user" and "sentinel sentinel-pass " configuration in order to let sentinel authenticate with a specific user in other sentinels.
- requirepass is kept just for compatibility with old config files

Co-authored-by: Oran Agra <oran@redislabs.com>
This commit is contained in:
Wen Hui 2020-10-19 00:33:55 -04:00 committed by GitHub
parent 457b7073b5
commit 0047702aab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 21 deletions

View File

@ -131,6 +131,29 @@ sentinel down-after-milliseconds mymaster 30000
# other Sentinels. So you need to configure all your Sentinels in a given # other Sentinels. So you need to configure all your Sentinels in a given
# group with the same "requirepass" password. Check the following documentation # group with the same "requirepass" password. Check the following documentation
# for more info: https://redis.io/topics/sentinel # for more info: https://redis.io/topics/sentinel
#
# IMPORTANT NOTE: starting with Redis 6.2 "requirepass" is a compatibility
# layer on top of the 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.
#
# New config files are advised to use separate authentication control for
# incoming connections (via ACL), and for outgoing connections (via
# sentinel-user and sentinel-pass)
#
# The requirepass is not compatable with aclfile option and the ACL LOAD
# command, these will cause requirepass to be ignored.
# sentinel sentinel-user <username>
#
# You can configure Sentinel to authenticate with other Sentinels with specific
# user name.
# sentinel sentinel-pass <password>
#
# The password for Sentinel to authenticate with other Sentinels. If sentinel-user
# is not configured, Sentinel will use 'default' user with sentinel-pass to authenticate.
# sentinel parallel-syncs <master-name> <numreplicas> # sentinel parallel-syncs <master-name> <numreplicas>
# #

View File

@ -53,6 +53,10 @@ list *UsersToLoad; /* This is a list of users found in the configuration file
list *ACLLog; /* Our security log, the user is able to inspect that list *ACLLog; /* Our security log, the user is able to inspect that
using the ACL LOG command .*/ using the ACL LOG command .*/
static rax *commandId = NULL; /* Command name to id mapping */
static unsigned long nextid = 0; /* Next command id that has not been assigned */
struct ACLCategoryItem { struct ACLCategoryItem {
const char *name; const char *name;
uint64_t flag; uint64_t flag;
@ -1031,18 +1035,16 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) {
* command name, so that a command retains the same ID in case of modules that * command name, so that a command retains the same ID in case of modules that
* are unloaded and later reloaded. */ * are unloaded and later reloaded. */
unsigned long ACLGetCommandID(const char *cmdname) { unsigned long ACLGetCommandID(const char *cmdname) {
static rax *map = NULL;
static unsigned long nextid = 0;
sds lowername = sdsnew(cmdname); sds lowername = sdsnew(cmdname);
sdstolower(lowername); sdstolower(lowername);
if (map == NULL) map = raxNew(); if (commandId == NULL) commandId = raxNew();
void *id = raxFind(map,(unsigned char*)lowername,sdslen(lowername)); void *id = raxFind(commandId,(unsigned char*)lowername,sdslen(lowername));
if (id != raxNotFound) { if (id != raxNotFound) {
sdsfree(lowername); sdsfree(lowername);
return (unsigned long)id; return (unsigned long)id;
} }
raxInsert(map,(unsigned char*)lowername,strlen(lowername), raxInsert(commandId,(unsigned char*)lowername,strlen(lowername),
(void*)nextid,NULL); (void*)nextid,NULL);
sdsfree(lowername); sdsfree(lowername);
unsigned long thisid = nextid; unsigned long thisid = nextid;
@ -1060,6 +1062,13 @@ unsigned long ACLGetCommandID(const char *cmdname) {
return thisid; 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. */ /* Return an username by its name, or NULL if the user does not exist. */
user *ACLGetUserByName(const char *name, size_t namelen) { user *ACLGetUserByName(const char *name, size_t namelen) {
void *myuser = raxFind(Users,(unsigned char*)name,namelen); void *myuser = raxFind(Users,(unsigned char*)name,namelen);

View File

@ -257,6 +257,8 @@ struct sentinelState {
unsigned long simfailure_flags; /* Failures simulation. */ unsigned long simfailure_flags; /* Failures simulation. */
int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script
paths at runtime? */ paths at runtime? */
char *sentinel_auth_pass; /* Password to use for AUTH against other sentinel */
char *sentinel_auth_user; /* Username for ACLs AUTH against other sentinel. */
} sentinel; } sentinel;
/* A script execution job. */ /* A script execution job. */
@ -451,19 +453,20 @@ void sentinelPublishCommand(client *c);
void sentinelRoleCommand(client *c); void sentinelRoleCommand(client *c);
struct redisCommand sentinelcmds[] = { struct redisCommand sentinelcmds[] = {
{"ping",pingCommand,1,"",0,NULL,0,0,0,0,0}, {"ping",pingCommand,1,"fast @connection",0,NULL,0,0,0,0,0},
{"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0}, {"sentinel",sentinelCommand,-2,"admin",0,NULL,0,0,0,0,0},
{"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0}, {"subscribe",subscribeCommand,-2,"pub-sub",0,NULL,0,0,0,0,0},
{"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0}, {"unsubscribe",unsubscribeCommand,-1,"pub-sub",0,NULL,0,0,0,0,0},
{"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0}, {"psubscribe",psubscribeCommand,-2,"pub-sub",0,NULL,0,0,0,0,0},
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0}, {"punsubscribe",punsubscribeCommand,-1,"pub-sub",0,NULL,0,0,0,0,0},
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0}, {"publish",sentinelPublishCommand,3,"pub-sub fast",0,NULL,0,0,0,0,0},
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0}, {"info",sentinelInfoCommand,-1,"random @dangerous",0,NULL,0,0,0,0,0},
{"role",sentinelRoleCommand,1,"ok-loading",0,NULL,0,0,0,0,0}, {"role",sentinelRoleCommand,1,"fast read-only @dangerous",0,NULL,0,0,0,0,0},
{"client",clientCommand,-2,"read-only no-script",0,NULL,0,0,0,0,0}, {"client",clientCommand,-2,"admin random @connection",0,NULL,0,0,0,0,0},
{"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}, {"shutdown",shutdownCommand,-1,"admin",0,NULL,0,0,0,0,0},
{"auth",authCommand,2,"no-auth no-script ok-loading ok-stale fast",0,NULL,0,0,0,0,0}, {"auth",authCommand,-2,"no-auth fast @connection",0,NULL,0,0,0,0,0},
{"hello",helloCommand,-2,"no-auth no-script fast",0,NULL,0,0,0,0,0} {"hello",helloCommand,-2,"no-auth fast @connection",0,NULL,0,0,0,0,0},
{"acl",aclCommand,-2,"admin",0,NULL,0,0,0,0,0,0}
}; };
/* This function overwrites a few normal Redis config default with Sentinel /* This function overwrites a few normal Redis config default with Sentinel
@ -480,12 +483,16 @@ void initSentinel(void) {
/* Remove usual Redis commands from the command table, then just add /* Remove usual Redis commands from the command table, then just add
* the SENTINEL command. */ * the SENTINEL command. */
dictEmpty(server.commands,NULL); dictEmpty(server.commands,NULL);
dictEmpty(server.orig_commands,NULL);
ACLClearCommandID();
for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) { for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) {
int retval; int retval;
struct redisCommand *cmd = sentinelcmds+j; struct redisCommand *cmd = sentinelcmds+j;
cmd->id = ACLGetCommandID(cmd->name); /* Assign the ID used for ACL. */
retval = dictAdd(server.commands, sdsnew(cmd->name), cmd); retval = dictAdd(server.commands, sdsnew(cmd->name), cmd);
serverAssert(retval == DICT_OK); serverAssert(retval == DICT_OK);
retval = dictAdd(server.orig_commands, sdsnew(cmd->name), cmd);
serverAssert(retval == DICT_OK);
/* Translate the command string flags description into an actual /* Translate the command string flags description into an actual
* set of flags. */ * set of flags. */
@ -505,6 +512,8 @@ void initSentinel(void) {
sentinel.announce_port = 0; sentinel.announce_port = 0;
sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE; sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG; sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG;
sentinel.sentinel_auth_pass = NULL;
sentinel.sentinel_auth_user = NULL;
memset(sentinel.myid,0,sizeof(sentinel.myid)); memset(sentinel.myid,0,sizeof(sentinel.myid));
} }
@ -1765,6 +1774,14 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
return "Please specify yes or no for the " return "Please specify yes or no for the "
"deny-scripts-reconfig options."; "deny-scripts-reconfig options.";
} }
} else if (!strcasecmp(argv[0],"sentinel-user") && argc == 2) {
/* sentinel-user <user-name> */
if (strlen(argv[1]))
sentinel.sentinel_auth_user = sdsnew(argv[1]);
} else if (!strcasecmp(argv[0],"sentinel-pass") && argc == 2) {
/* sentinel-pass <password> */
if (strlen(argv[1]))
sentinel.sentinel_auth_pass = sdsnew(argv[1]);
} else { } else {
return "Unrecognized sentinel configuration statement."; return "Unrecognized sentinel configuration statement.";
} }
@ -1938,6 +1955,19 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
rewriteConfigRewriteLine(state,"sentinel",line,1); rewriteConfigRewriteLine(state,"sentinel",line,1);
} }
/* sentinel sentinel-user. */
if (sentinel.sentinel_auth_user) {
line = sdscatprintf(sdsempty(), "sentinel sentinel-user %s", sentinel.sentinel_auth_user);
rewriteConfigRewriteLine(state,"sentinel",line,1);
}
/* sentinel sentinel-pass. */
if (sentinel.sentinel_auth_pass) {
line = sdscatprintf(sdsempty(), "sentinel sentinel-pass %s", sentinel.sentinel_auth_pass);
rewriteConfigRewriteLine(state,"sentinel",line,1);
}
dictReleaseIterator(di); dictReleaseIterator(di);
} }
@ -1993,8 +2023,17 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
auth_pass = ri->master->auth_pass; auth_pass = ri->master->auth_pass;
auth_user = ri->master->auth_user; auth_user = ri->master->auth_user;
} else if (ri->flags & SRI_SENTINEL) { } else if (ri->flags & SRI_SENTINEL) {
auth_pass = server.requirepass; /* If sentinel_auth_user is NULL, AUTH will use default user
auth_user = NULL; with sentinel_auth_pass to autenticate */
if (sentinel.sentinel_auth_pass) {
auth_pass = sentinel.sentinel_auth_pass;
auth_user = sentinel.sentinel_auth_user;
} else {
/* Compatibility with old configs. requirepass is used
* for both incoming and outgoing authentication. */
auth_pass = server.requirepass;
auth_user = NULL;
}
} }
if (auth_pass && auth_user == NULL) { if (auth_pass && auth_user == NULL) {

View File

@ -5453,6 +5453,7 @@ int main(int argc, char **argv) {
} }
} }
} else { } else {
ACLLoadUsersAtStartup();
InitServerLast(); InitServerLast();
sentinelIsRunning(); sentinelIsRunning();
if (server.supervised_mode == SUPERVISED_SYSTEMD) { if (server.supervised_mode == SUPERVISED_SYSTEMD) {

View File

@ -1945,6 +1945,7 @@ void ACLInit(void);
int ACLCheckUserCredentials(robj *username, robj *password); int ACLCheckUserCredentials(robj *username, robj *password);
int ACLAuthenticateUser(client *c, robj *username, robj *password); int ACLAuthenticateUser(client *c, robj *username, robj *password);
unsigned long ACLGetCommandID(const char *cmdname); unsigned long ACLGetCommandID(const char *cmdname);
void ACLClearCommandID(void);
user *ACLGetUserByName(const char *name, size_t namelen); user *ACLGetUserByName(const char *name, size_t namelen);
int ACLCheckCommandPerm(client *c, int *keyidxptr); int ACLCheckCommandPerm(client *c, int *keyidxptr);
int ACLSetUser(user *u, const char *op, ssize_t oplen); int ACLSetUser(user *u, const char *op, ssize_t oplen);