make processCommand check publish channel permissions. (#8534)
Add publish channel permissions check in processCommand. processCommand didn't check publish channel permissions, so we can queue a publish command in a transaction. But when exec the transaction, it will fail with -NOPERM. We also union keys/commands/channels permissions check togegher in ACLCheckAllPerm. Remove pubsubCheckACLPermissionsOrReply in publishCommand/subscribeCommand/psubscribeCommand. Always check permissions in processCommand/execCommand/ luaRedisGenericCommand.
This commit is contained in:
parent
db6655deb4
commit
e138698e54
16
src/acl.c
16
src/acl.c
@ -1360,6 +1360,22 @@ int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check whether the command is ready to be exceuted by ACLCheckCommandPerm.
|
||||||
|
* If check passes, then check whether pub/sub channels of the command is
|
||||||
|
* ready to be executed by ACLCheckPubsubPerm */
|
||||||
|
int ACLCheckAllPerm(client *c, int *idxptr) {
|
||||||
|
int acl_retval = ACLCheckCommandPerm(c,idxptr);
|
||||||
|
if (acl_retval != ACL_OK)
|
||||||
|
return acl_retval;
|
||||||
|
if (c->cmd->proc == publishCommand)
|
||||||
|
acl_retval = ACLCheckPubsubPerm(c,1,1,0,idxptr);
|
||||||
|
else if (c->cmd->proc == subscribeCommand)
|
||||||
|
acl_retval = ACLCheckPubsubPerm(c,1,c->argc-1,0,idxptr);
|
||||||
|
else if (c->cmd->proc == psubscribeCommand)
|
||||||
|
acl_retval = ACLCheckPubsubPerm(c,1,c->argc-1,1,idxptr);
|
||||||
|
return acl_retval;
|
||||||
|
}
|
||||||
|
|
||||||
/* =============================================================================
|
/* =============================================================================
|
||||||
* ACL loading / saving functions
|
* ACL loading / saving functions
|
||||||
* ==========================================================================*/
|
* ==========================================================================*/
|
||||||
|
@ -204,9 +204,7 @@ void execCommand(client *c) {
|
|||||||
/* ACL permissions are also checked at the time of execution in case
|
/* ACL permissions are also checked at the time of execution in case
|
||||||
* they were changed after the commands were queued. */
|
* they were changed after the commands were queued. */
|
||||||
int acl_errpos;
|
int acl_errpos;
|
||||||
int acl_retval = ACLCheckCommandPerm(c,&acl_errpos);
|
int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
|
||||||
if (acl_retval == ACL_OK && c->cmd->proc == publishCommand)
|
|
||||||
acl_retval = ACLCheckPubsubPerm(c,1,1,0,&acl_errpos);
|
|
||||||
if (acl_retval != ACL_OK) {
|
if (acl_retval != ACL_OK) {
|
||||||
char *reason;
|
char *reason;
|
||||||
switch (acl_retval) {
|
switch (acl_retval) {
|
||||||
@ -217,7 +215,8 @@ void execCommand(client *c) {
|
|||||||
reason = "no permission to touch the specified keys";
|
reason = "no permission to touch the specified keys";
|
||||||
break;
|
break;
|
||||||
case ACL_DENIED_CHANNEL:
|
case ACL_DENIED_CHANNEL:
|
||||||
reason = "no permission to publish to the specified channel";
|
reason = "no permission to access one of the channels used "
|
||||||
|
"as arguments";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
reason = "no permission";
|
reason = "no permission";
|
||||||
|
18
src/pubsub.c
18
src/pubsub.c
@ -331,21 +331,6 @@ int pubsubPublishMessage(robj *channel, robj *message) {
|
|||||||
return receivers;
|
return receivers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This wraps handling ACL channel permissions for the given client. */
|
|
||||||
int pubsubCheckACLPermissionsOrReply(client *c, int idx, int count, int literal) {
|
|
||||||
/* Check if the user can run the command according to the current
|
|
||||||
* ACLs. */
|
|
||||||
int acl_chanpos;
|
|
||||||
int acl_retval = ACLCheckPubsubPerm(c,idx,count,literal,&acl_chanpos);
|
|
||||||
if (acl_retval == ACL_DENIED_CHANNEL) {
|
|
||||||
addACLLogEntry(c,acl_retval,acl_chanpos,NULL);
|
|
||||||
addReplyError(c,
|
|
||||||
"-NOPERM this user has no permissions to access "
|
|
||||||
"one of the channels used as arguments");
|
|
||||||
}
|
|
||||||
return acl_retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------------
|
/*-----------------------------------------------------------------------------
|
||||||
* Pubsub commands implementation
|
* Pubsub commands implementation
|
||||||
*----------------------------------------------------------------------------*/
|
*----------------------------------------------------------------------------*/
|
||||||
@ -353,7 +338,6 @@ int pubsubCheckACLPermissionsOrReply(client *c, int idx, int count, int literal)
|
|||||||
/* SUBSCRIBE channel [channel ...] */
|
/* SUBSCRIBE channel [channel ...] */
|
||||||
void subscribeCommand(client *c) {
|
void subscribeCommand(client *c) {
|
||||||
int j;
|
int j;
|
||||||
if (pubsubCheckACLPermissionsOrReply(c,1,c->argc-1,0) != ACL_OK) return;
|
|
||||||
if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) {
|
if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) {
|
||||||
/**
|
/**
|
||||||
* A client that has CLIENT_DENY_BLOCKING flag on
|
* A client that has CLIENT_DENY_BLOCKING flag on
|
||||||
@ -387,7 +371,6 @@ void unsubscribeCommand(client *c) {
|
|||||||
/* PSUBSCRIBE pattern [pattern ...] */
|
/* PSUBSCRIBE pattern [pattern ...] */
|
||||||
void psubscribeCommand(client *c) {
|
void psubscribeCommand(client *c) {
|
||||||
int j;
|
int j;
|
||||||
if (pubsubCheckACLPermissionsOrReply(c,1,c->argc-1,1) != ACL_OK) return;
|
|
||||||
if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) {
|
if ((c->flags & CLIENT_DENY_BLOCKING) && !(c->flags & CLIENT_MULTI)) {
|
||||||
/**
|
/**
|
||||||
* A client that has CLIENT_DENY_BLOCKING flag on
|
* A client that has CLIENT_DENY_BLOCKING flag on
|
||||||
@ -420,7 +403,6 @@ void punsubscribeCommand(client *c) {
|
|||||||
|
|
||||||
/* PUBLISH <channel> <message> */
|
/* PUBLISH <channel> <message> */
|
||||||
void publishCommand(client *c) {
|
void publishCommand(client *c) {
|
||||||
if (pubsubCheckACLPermissionsOrReply(c,1,1,0) != ACL_OK) return;
|
|
||||||
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
|
int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]);
|
||||||
if (server.cluster_enabled)
|
if (server.cluster_enabled)
|
||||||
clusterPropagatePublish(c->argv[1],c->argv[2]);
|
clusterPropagatePublish(c->argv[1],c->argv[2]);
|
||||||
|
@ -604,9 +604,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
|||||||
|
|
||||||
/* Check the ACLs. */
|
/* Check the ACLs. */
|
||||||
int acl_errpos;
|
int acl_errpos;
|
||||||
int acl_retval = ACLCheckCommandPerm(c,&acl_errpos);
|
int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
|
||||||
if (acl_retval == ACL_OK && c->cmd->proc == publishCommand)
|
|
||||||
acl_retval = ACLCheckPubsubPerm(c,1,1,0,&acl_errpos);
|
|
||||||
if (acl_retval != ACL_OK) {
|
if (acl_retval != ACL_OK) {
|
||||||
addACLLogEntry(c,acl_retval,acl_errpos,NULL);
|
addACLLogEntry(c,acl_retval,acl_errpos,NULL);
|
||||||
switch (acl_retval) {
|
switch (acl_retval) {
|
||||||
|
22
src/server.c
22
src/server.c
@ -4011,18 +4011,30 @@ int processCommand(client *c) {
|
|||||||
|
|
||||||
/* Check if the user can run this command according to the current
|
/* Check if the user can run this command according to the current
|
||||||
* ACLs. */
|
* ACLs. */
|
||||||
int acl_keypos;
|
int acl_errpos;
|
||||||
int acl_retval = ACLCheckCommandPerm(c,&acl_keypos);
|
int acl_retval = ACLCheckAllPerm(c,&acl_errpos);
|
||||||
if (acl_retval != ACL_OK) {
|
if (acl_retval != ACL_OK) {
|
||||||
addACLLogEntry(c,acl_retval,acl_keypos,NULL);
|
addACLLogEntry(c,acl_retval,acl_errpos,NULL);
|
||||||
if (acl_retval == ACL_DENIED_CMD)
|
switch (acl_retval) {
|
||||||
|
case ACL_DENIED_CMD:
|
||||||
rejectCommandFormat(c,
|
rejectCommandFormat(c,
|
||||||
"-NOPERM this user has no permissions to run "
|
"-NOPERM this user has no permissions to run "
|
||||||
"the '%s' command or its subcommand", c->cmd->name);
|
"the '%s' command or its subcommand", c->cmd->name);
|
||||||
else
|
break;
|
||||||
|
case ACL_DENIED_KEY:
|
||||||
rejectCommandFormat(c,
|
rejectCommandFormat(c,
|
||||||
"-NOPERM this user has no permissions to access "
|
"-NOPERM this user has no permissions to access "
|
||||||
"one of the keys used as arguments");
|
"one of the keys used as arguments");
|
||||||
|
break;
|
||||||
|
case ACL_DENIED_CHANNEL:
|
||||||
|
rejectCommandFormat(c,
|
||||||
|
"-NOPERM this user has no permissions to access "
|
||||||
|
"one of the channels used as arguments");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rejectCommandFormat(c, "no permission");
|
||||||
|
break;
|
||||||
|
}
|
||||||
return C_OK;
|
return C_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2092,7 +2092,7 @@ int isMutuallyExclusiveChildType(int type);
|
|||||||
extern rax *Users;
|
extern rax *Users;
|
||||||
extern user *DefaultUser;
|
extern user *DefaultUser;
|
||||||
void ACLInit(void);
|
void ACLInit(void);
|
||||||
/* Return values for ACLCheckCommandPerm() and ACLCheckPubsubPerm(). */
|
/* Return values for ACLCheckAllPerm(). */
|
||||||
#define ACL_OK 0
|
#define ACL_OK 0
|
||||||
#define ACL_DENIED_CMD 1
|
#define ACL_DENIED_CMD 1
|
||||||
#define ACL_DENIED_KEY 2
|
#define ACL_DENIED_KEY 2
|
||||||
@ -2103,8 +2103,7 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password);
|
|||||||
unsigned long ACLGetCommandID(const char *cmdname);
|
unsigned long ACLGetCommandID(const char *cmdname);
|
||||||
void ACLClearCommandID(void);
|
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 ACLCheckAllPerm(client *c, int *idxptr);
|
||||||
int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr);
|
|
||||||
int ACLSetUser(user *u, const char *op, ssize_t oplen);
|
int ACLSetUser(user *u, const char *op, ssize_t oplen);
|
||||||
sds ACLDefaultUserFirstPassword(void);
|
sds ACLDefaultUserFirstPassword(void);
|
||||||
uint64_t ACLGetCommandCategoryFlagByName(const char *name);
|
uint64_t ACLGetCommandCategoryFlagByName(const char *name);
|
||||||
|
@ -113,6 +113,22 @@ start_server {tags {"acl"}} {
|
|||||||
set e
|
set e
|
||||||
} {*NOPERM*channel*}
|
} {*NOPERM*channel*}
|
||||||
|
|
||||||
|
test {In transaction queue publish/subscribe/psubscribe to unauthorized channel will fail} {
|
||||||
|
r ACL setuser psuser +multi +discard
|
||||||
|
r MULTI
|
||||||
|
catch {r PUBLISH notexits helloworld} e
|
||||||
|
r DISCARD
|
||||||
|
assert_match {*NOPERM*} $e
|
||||||
|
r MULTI
|
||||||
|
catch {r SUBSCRIBE notexits foo:1} e
|
||||||
|
r DISCARD
|
||||||
|
assert_match {*NOPERM*} $e
|
||||||
|
r MULTI
|
||||||
|
catch {r PSUBSCRIBE notexits:* bar:*} e
|
||||||
|
r DISCARD
|
||||||
|
assert_match {*NOPERM*} $e
|
||||||
|
}
|
||||||
|
|
||||||
test {It's possible to allow subscribing to a subset of channels} {
|
test {It's possible to allow subscribing to a subset of channels} {
|
||||||
set rd [redis_deferring_client]
|
set rd [redis_deferring_client]
|
||||||
$rd AUTH psuser pspass
|
$rd AUTH psuser pspass
|
||||||
|
Loading…
x
Reference in New Issue
Block a user