From 0047702aabdf53651e65cda8f1e7f7ed432353e7 Mon Sep 17 00:00:00 2001 From: Wen Hui Date: Mon, 19 Oct 2020 00:33:55 -0400 Subject: 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 --- sentinel.conf | 23 +++++++++++++++++++ src/acl.c | 19 +++++++++++----- src/sentinel.c | 71 +++++++++++++++++++++++++++++++++++++++++++++------------- src/server.c | 1 + src/server.h | 1 + 5 files changed, 94 insertions(+), 21 deletions(-) diff --git a/sentinel.conf b/sentinel.conf index b6ff05f25..50a36a314 100644 --- a/sentinel.conf +++ b/sentinel.conf @@ -131,6 +131,29 @@ sentinel down-after-milliseconds mymaster 30000 # other Sentinels. So you need to configure all your Sentinels in a given # group with the same "requirepass" password. Check the following documentation # 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 as usually, or more explicitly with AUTH default +# 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 +# +# You can configure Sentinel to authenticate with other Sentinels with specific +# user name. + +# sentinel sentinel-pass +# +# 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 # diff --git a/src/acl.c b/src/acl.c index aa3ed197f..d9ee774c8 100644 --- a/src/acl.c +++ b/src/acl.c @@ -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 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 { const char *name; 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 * are unloaded and later reloaded. */ unsigned long ACLGetCommandID(const char *cmdname) { - static rax *map = NULL; - static unsigned long nextid = 0; sds lowername = sdsnew(cmdname); sdstolower(lowername); - if (map == NULL) map = raxNew(); - void *id = raxFind(map,(unsigned char*)lowername,sdslen(lowername)); + if (commandId == NULL) commandId = raxNew(); + void *id = raxFind(commandId,(unsigned char*)lowername,sdslen(lowername)); if (id != raxNotFound) { sdsfree(lowername); return (unsigned long)id; } - raxInsert(map,(unsigned char*)lowername,strlen(lowername), + raxInsert(commandId,(unsigned char*)lowername,strlen(lowername), (void*)nextid,NULL); sdsfree(lowername); unsigned long thisid = nextid; @@ -1060,6 +1062,13 @@ unsigned long ACLGetCommandID(const char *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 = raxFind(Users,(unsigned char*)name,namelen); diff --git a/src/sentinel.c b/src/sentinel.c index a346a2547..31a66a6ff 100644 --- a/src/sentinel.c +++ b/src/sentinel.c @@ -257,6 +257,8 @@ struct sentinelState { unsigned long simfailure_flags; /* Failures simulation. */ int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script 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; /* A script execution job. */ @@ -451,19 +453,20 @@ void sentinelPublishCommand(client *c); void sentinelRoleCommand(client *c); struct redisCommand sentinelcmds[] = { - {"ping",pingCommand,1,"",0,NULL,0,0,0,0,0}, - {"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0}, - {"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0}, - {"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0}, - {"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0}, - {"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0}, - {"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0}, - {"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0}, - {"role",sentinelRoleCommand,1,"ok-loading",0,NULL,0,0,0,0,0}, - {"client",clientCommand,-2,"read-only no-script",0,NULL,0,0,0,0,0}, - {"shutdown",shutdownCommand,-1,"",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}, - {"hello",helloCommand,-2,"no-auth no-script fast",0,NULL,0,0,0,0,0} + {"ping",pingCommand,1,"fast @connection",0,NULL,0,0,0,0,0}, + {"sentinel",sentinelCommand,-2,"admin",0,NULL,0,0,0,0,0}, + {"subscribe",subscribeCommand,-2,"pub-sub",0,NULL,0,0,0,0,0}, + {"unsubscribe",unsubscribeCommand,-1,"pub-sub",0,NULL,0,0,0,0,0}, + {"psubscribe",psubscribeCommand,-2,"pub-sub",0,NULL,0,0,0,0,0}, + {"punsubscribe",punsubscribeCommand,-1,"pub-sub",0,NULL,0,0,0,0,0}, + {"publish",sentinelPublishCommand,3,"pub-sub fast",0,NULL,0,0,0,0,0}, + {"info",sentinelInfoCommand,-1,"random @dangerous",0,NULL,0,0,0,0,0}, + {"role",sentinelRoleCommand,1,"fast read-only @dangerous",0,NULL,0,0,0,0,0}, + {"client",clientCommand,-2,"admin random @connection",0,NULL,0,0,0,0,0}, + {"shutdown",shutdownCommand,-1,"admin",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 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 @@ -480,12 +483,16 @@ void initSentinel(void) { /* Remove usual Redis commands from the command table, then just add * the SENTINEL command. */ dictEmpty(server.commands,NULL); + dictEmpty(server.orig_commands,NULL); + ACLClearCommandID(); for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) { int retval; struct redisCommand *cmd = sentinelcmds+j; - + cmd->id = ACLGetCommandID(cmd->name); /* Assign the ID used for ACL. */ retval = dictAdd(server.commands, sdsnew(cmd->name), cmd); 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 * set of flags. */ @@ -505,6 +512,8 @@ void initSentinel(void) { sentinel.announce_port = 0; sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE; 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)); } @@ -1765,6 +1774,14 @@ char *sentinelHandleConfiguration(char **argv, int argc) { return "Please specify yes or no for the " "deny-scripts-reconfig options."; } + } else if (!strcasecmp(argv[0],"sentinel-user") && argc == 2) { + /* sentinel-user */ + if (strlen(argv[1])) + sentinel.sentinel_auth_user = sdsnew(argv[1]); + } else if (!strcasecmp(argv[0],"sentinel-pass") && argc == 2) { + /* sentinel-pass */ + if (strlen(argv[1])) + sentinel.sentinel_auth_pass = sdsnew(argv[1]); } else { return "Unrecognized sentinel configuration statement."; } @@ -1938,6 +1955,19 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) { 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); } @@ -1993,8 +2023,17 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) { auth_pass = ri->master->auth_pass; auth_user = ri->master->auth_user; } else if (ri->flags & SRI_SENTINEL) { - auth_pass = server.requirepass; - auth_user = NULL; + /* If sentinel_auth_user is NULL, AUTH will use default user + 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) { diff --git a/src/server.c b/src/server.c index 43d7c337c..67d050ea0 100644 --- a/src/server.c +++ b/src/server.c @@ -5453,6 +5453,7 @@ int main(int argc, char **argv) { } } } else { + ACLLoadUsersAtStartup(); InitServerLast(); sentinelIsRunning(); if (server.supervised_mode == SUPERVISED_SYSTEMD) { diff --git a/src/server.h b/src/server.h index 33ce2b89c..5d53d9473 100644 --- a/src/server.h +++ b/src/server.h @@ -1945,6 +1945,7 @@ void ACLInit(void); int ACLCheckUserCredentials(robj *username, robj *password); int ACLAuthenticateUser(client *c, robj *username, robj *password); unsigned long ACLGetCommandID(const char *cmdname); +void ACLClearCommandID(void); user *ACLGetUserByName(const char *name, size_t namelen); int ACLCheckCommandPerm(client *c, int *keyidxptr); int ACLSetUser(user *u, const char *op, ssize_t oplen); -- cgit v1.2.1