简介
Redis ACL是6.0版本中推出的新功能。是一项可以限制用户连接可执行命令和键访问操作的功能。
客户端在连接服务器之后,客户端需要提供用户名和密码用于验证身份,如果验证成功,客户端连接会关联特定用户以及用户相应的权限。Redis可以配置新的客户端连接自动使用默认用户(default)进行验证(默认选项)。
此外,ACL功能对旧版客户端和应用都是向后兼容的,对于旧版配置用户密码的方式(requirepass)也是支持的。该命令设置的是default用户的密码,即auth password
等价于 auth default password
。没有指定用户的客户端连接使用的都是default,可以通过控制defalut用户权限来兼容老版本。
ACL使用场景
- 你希望限制用户访问命令和键以提高安全性。不在信任列表中的用户没有权限访问,而在信任列表中的用户拥有完成工作的最小访问权限。例如一些客户端只可以执行只读的命令。
- 你希望提供运维安全。避免程序出错或者人为操作失误导致数据或者配置受到损坏。比如执行flush、keys等。在之前版本中都是通过命令重命令的方式来对使用者隐藏,但是这样可能会存在其他隐患。
ACL使用
ACL规则
1 | on:启用用户 |
ACL命令使用
1 | /* ACL -- show and modify the configuration of ACL users. |
使用示例
1 | // 创建一个未启用的用户 |
实现
Redis新增了一个user结构体。结构体具体定义如下。
需要注意的是:
- Redis使用allowed_commands数据来记录是否拥有命令的执行权限。每一种命令对应其中的一个字节(使用全局的一棵rax树保存命令,id获取函数为
ACLGetCommandID
)。 - 密码使用的是一个list列表。表示一个用户允许多个密码登录。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32// 用户定义
typedef struct user {
// 用户名
sds name; /* The username as an SDS string. */
// 用户标记
uint64_t flags; /* See USER_FLAG_* */
/* The bit in allowed_commands is set if this user has the right to
* execute this command. In commands having subcommands, if this bit is
* set, then all the subcommands are also available.
*
* If the bit for a given command is NOT set and the command has
* subcommands, Redis will also check allowed_subcommands in order to
* understand if the command can be executed. */
// 允许执行的命令数组
uint64_t allowed_commands[USER_COMMAND_BITS_COUNT/64];
/* This array points, for each command ID (corresponding to the command
* bit set in allowed_commands), to an array of SDS strings, terminated by
* a NULL pointer, with all the sub commands that can be executed for
* this command. When no subcommands matching is used, the field is just
* set to NULL to avoid allocating USER_COMMAND_BITS_COUNT pointers. */
// 一个数组指针,表示在命令ID下允许执行的具体命令列表,以NULL结束。
// 如果没有允许执行的命令,这个值为NULL,避免分配USER_COMMAND_BITS_COUNT空间。
sds **allowed_subcommands;
// 密码
list *passwords; /* A list of SDS valid passwords for this user. */
// 运行执行命令的匹配符列表。如果这个值为NULL表示不允许执行任何命令。除非这个用户的flag为ALLKEYS。
list *patterns; /* A list of allowed key patterns. If this field is NULL
the user cannot mention any key in a command, unless
the flag ALLKEYS is set in the user. */
} user;
acl命令具体实现在acl.c -> aclCommand()
,下面我们将通过这个函数来分析acl命令的具体实现。
增加用户及权限
1 | if (!strcasecmp(sub,"setuser") && c->argc >= 3) { |
acl setuser
是Redis用来增加用户及权限的命令。我们可以看到在授权中,会先创建一个临时用户,先在临时用户上进行权限修改操作。如果在某阶段执行出错,前面执行成功的权限修改行为并不会影响到原用户。
调用的关键函数为:
ACLSetUser
:按照ACL规则来进行具体的授权行为。ACLCopyUser
:将一个用户的所有权限参数拷贝给一个新用户。
其余功能实现就不介绍了。。
权限检查
在所有命令执行前,都会调用processCommand
函数。在这个函数中,会进行一些命令执行前的常规检查,比如参数个数、实例状态、用户权限等。
该函数会调用acl.c -> ACLCheckCommandPerm()
函数来检查用户是否拥有命令和键值对的执行权限。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83/* 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 ACL_OK is returned, otherwise
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
* command cannot be executed because the user is not allowed to run such
* command, the second if the command is denied because the user is trying
* to access keys that are not among the specified patterns. */
// 检查当前客户端是否拥有执行命令的权限
int ACLCheckCommandPerm(client *c) {
user *u = c->user;
uint64_t id = c->cmd->id;
/* If there is no associated user, the connection can run anything. */
if (u == NULL) return ACL_OK;
/* Check if the user can execute this command. */
// 检查是否拥有执行命令的权限
if (!(u->flags & USER_FLAG_ALLCOMMANDS) &&
c->cmd->proc != authCommand)
{
/* If the bit is not set we have to check further, in case the
* command is allowed just with that specific subcommand. */
if (ACLGetUserCommandBit(u,id) == 0) {
/* Check if the subcommand matches. */
if (c->argc < 2 ||
u->allowed_subcommands == NULL ||
u->allowed_subcommands[id] == NULL)
{
return ACL_DENIED_CMD;
}
long subid = 0;
while (1) {
if (u->allowed_subcommands[id][subid] == NULL)
return ACL_DENIED_CMD;
if (!strcasecmp(c->argv[1]->ptr,
u->allowed_subcommands[id][subid]))
break; /* Subcommand match found. Stop here. */
subid++;
}
}
}
/* 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))
{
int numkeys;
int *keyidx = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
for (int j = 0; j < numkeys; j++) {
listIter li;
listNode *ln;
listRewind(u->patterns,&li);
/* Test this key against every pattern. */
int match = 0;
while((ln = listNext(&li))) {
sds pattern = listNodeValue(ln);
size_t plen = sdslen(pattern);
int idx = keyidx[j];
if (stringmatchlen(pattern,plen,c->argv[idx]->ptr,
sdslen(c->argv[idx]->ptr),0))
{
match = 1;
break;
}
}
if (!match) {
getKeysFreeResult(keyidx);
return ACL_DENIED_KEY;
}
}
getKeysFreeResult(keyidx);
}
/* If we survived all the above checks, the user can execute the
* command. */
return ACL_OK;
}