我们在阅读redis-server初始化的过程中,在initServerConfig()之后会看到一个关于ACLInit的内容,且注释写道:

  • The ACL subsystem must be initialized ASAP because thebasic networking code and client creation depends on it.也就是说,ACL子系统必须在初始化的主流程中今早的初始化,因为后面网络通信和客户端的操作连接都依赖于这个ACL子系统。

那么这个ACL子系统到底是什么呢,他在网络通信和客户端的连接操作中都起着什么样的作用呢,我们从官方的注解来看

,我们仅选择一些重要内容进行分析,感兴趣的各位可以参照官方的文档操作一遍即可熟悉。

Redis ACL是Access ControlList的缩写,它允许某些连接在可执行的命令和可访问的密钥方面受到限制。它的工作方式是,在连接之后,客户端需要通过提供用户名和有效密码进行身份验证:如果身份验证阶段成功,则连接将与给定的用户以及该用户的限制相关联。可以配置Redis,这样新的连接已经通过“默认”用户进行身份验证(这是默认配置),因此配置默认用户的副作用是,只能为未显式验证的连接提供特定的功能子集。
在默认配置中,Redis 6(第一个具有ACL的版本)的工作方式与旧版本的Redis完全相同,即每个新连接都能够调用所有可能的命令和访问每个键,因此ACL功能与旧客户端和应用程序向后兼容。同样,使用requirepass配置指令来配置密码的旧方法仍然可以正常工作,但是现在它所做的只是为默认用户设置一个密码。

那么我们为什么要使用ACL系统呢:

  • 您希望通过限制对命令和密钥的访问来提高安全性,以便不受信任的客户端没有访问权限,受信任的客户端只有对数据库的最低访问级别,以便执行所需的工作。例如,某些客户端可能只能够执行只读命令。
  • 您希望提高操作安全性,以便不允许进程或人员访问Redis,因为软件错误或手动错误而损坏数据或配置。例如,从Redis获取延迟作业的worker没有理由调用FLUSHALL命令。

ACL的另一个典型用法与托管Redis实例有关。Redis通常是作为托管服务提供的,既有为其他内部客户处理Redis基础设施的内部公司团队提供的,也有由云提供商在软件即服务设置中提供的服务。在这两种设置中,我们都希望确保排除客户的配置命令。过去通过命令重命名来实现这一点的方法是一个技巧,它允许我们在没有ACL的情况下生存很长一段时间,但并不理想。

ACL有很多命令,具体的可以使用help命令查看:

127.0.0.1:6379> ACL HELP
 1) ACL <subcommand> arg arg ... arg. Subcommands are:
 2) LOAD                             -- Reload users from the ACL file.从ACL文件中读取用户信息
 3) SAVE                             -- Save the current config to the ACL file.保存当前用户信息到ACL文件
 4) LIST                             -- Show user details in config file format.查看所有的用户的权限信息
 5) USERS                            -- List all the registered usernames.查看所有的已注册用户的信息
 6) SETUSER <username> [attribs ...] -- Create or modify a user.修改一个用户的权限
 7) GETUSER <username>               -- Get the user details.获取某个用户的信息
 8) DELUSER <username> [...]         -- Delete a list of users.删除一个或者多个用户
 9) CAT                              -- List available categories.查看可以使用的命令
10) CAT <category>                   -- List commands inside category.查看细分的命令
11) GENPASS [<bits>]                 -- Generate a secure user password.生成密码
12) WHOAMI                           -- Return the current connection username.查看当前登录用户
13) LOG [<count> | RESET]            -- Show the ACL log entries.ACL的日志

我们针对以上命令,做一个简单的示例:

1、 ACL SETUSER alice   //创建alice这个用户
OK
2、 ACL LIST     //查看alice是否创建成功,以及这个用户用哪些权限
1) "user alice off -@all"
2) "user default on nopass ~* +@all"
3、 ACL SETUSER alice on >p1pp0 ~cached:* +get //如上面描述,刚创建的用户是没有任何权限的,现在我们给他赋予一个字符串cached的get权限
OK
4、 AUTH alice p1pp0  //使用aclice这个用户登录
OK
5、 GET foo  //直接使用get是没有权限的
(error) NOPERM this user has no permissions to access one of the keys used as arguments
6、 GET cached:1234   //针对cached的get是有权限的
(nil)
7、 SET cached:1234 zap  //针对cached的set命令也是没有权限的
(error) NOPERM this user has no permissions to run the 'set' command or its subcommand

我们也可以使用getuser命令查看我们刚创建的alice用户有哪些权限:

> ACL GETUSER alice
1) "flags"
2) 1) "on"
3) "passwords"
4) 1) "2d9c75..."
5) "commands"
6) "-@all +get"
7) "keys"
8) 1) "cached:*"

如何理解多次调用SETUSER这个命令的时候发生了什么也是非常重要的,要知道我们每次调用SETUSER不会重置用户,而只是修改当前被操作用户的权限。只有在以前未知的情况下才会重置该用户:在这种情况下相当于重新创建了一个账户,且该用户没有任何操作的权限。

1、ACL SETUSER myuser +set  //给用户设置set命令的权限
OK
2、ACL SETUSER myuser +get  //给用户设置get命令的权限
OK
3、ACL LIST
1) "user default on nopass ~* +@all"
2) "user myuser off -@all +set +get"   //现在可以看到这个用户即拥有了get和set的权限
set cached:1234 zap                   //再次使用set命令就是可以执行的了
OK

也许你会说,这么多命令这么多用户,一个个设置起来是不是太麻烦了,我自己在操作练习的过程中也感觉到了繁琐,所以ACL提供了一个类似一键设置的按钮:

ACL SETUSER antirez on +@all -@dangerous >42a979... ~*

通过使用+@all和-@dangerous可以设置大多数的安全命令,当然除了模块的命令。
那么我们如何知道有哪些命令呢:

> ACL CAT
 1) "keyspace"
 2) "read"
 3) "write"
 4) "set"
 5) "sortedset"
 6) "list"
 7) "hash"
 8) "string"
 9) "bitmap"
10) "hyperloglog"
11) "geo"
12) "stream"
13) "pubsub"
14) "admin"
15) "fast"
16) "slow"
17) "blocking"
18) "dangerous"
19) "connection"
20) "transaction"
21) "scripting"

假设针对geo命令又有哪些子命令呢:

> ACL CAT geo
1) "geohash"
2) "georadius_ro"
3) "georadiusbymember"
4) "geopos"
5) "geoadd"
6) "georadiusbymember_ro"
7) "geodist"
8) "georadius"

通常情况下排除或包含整个命令的能力是不够的。许多Redis命令根据作为参数传递的子命令执行多种操作。例如可以使用CLIENT命令来执行危险和非危险操作。许多部署可能不愿意向非管理级别的用户提供执行CLIENT KILL的功能,但可能仍然希望他们能够运行CLIENT SETNAME命令。

ACL SETUSER myuser -client +client|setname +client|getname

-client命令开始删除CLIENT命令,后来添加了两个允许的子命令。请注意,不能进行相反的操作,只能添加子命令,不能排除子命令,因为将来可能会添加新的子命令:指定对某些用户有效的所有子命令会更安全。此外,如果添加一个关于尚未禁用的命令的子命令,则会生成一个错误。

通过上面的介绍我们可以简单的针对某个具体的用户操作针对某些具体命令的操作。Redis在内部存储使用SHA256散列的密码,如果设置密码并检查LIST或GETUSER的输出,您会看到一个看起来像伪随机的长十六进制字符串,这是redis6.0之后才有的特性,对编程有一点了解的应该都熟悉SHA256的生成,不做具体介绍了。

那么这个添加用户、设置用户权限,在redis的代码里面是怎么实现的呢,这就回到了我们的主题,ACLInit(),即ACL子系统的初始化,我们仔细阅读会发现,在redis-server的初始化过程中就创建了一个默认用户default

void ACLInit(void) {
    Users = raxNew();
    UsersToLoad = listCreate();
    ACLLog = listCreate();
    ACLInitDefaultUser();
    server.requirepass = NULL; /* Only used for backward compatibility. */
}
void ACLInitDefaultUser(void) {
    DefaultUser = ACLCreateUser("default",7);
    ACLSetUser(DefaultUser,"+@all",-1);
    ACLSetUser(DefaultUser,"~*",-1);
    ACLSetUser(DefaultUser,"on",-1);
    ACLSetUser(DefaultUser,"nopass",-1);
}

而每一个用户使用的存储结构体为raxNode即基数树,那么基数树这个结构体又有什么什么供我们学习的呢,放在后面我们一起来学习和阅读。

本文主要介绍了redis-server初始化过程中ACL子系统初始化的过程,以及针对不同的用户可以设置一些基础的命令操作权限的一些操作,最后将redis-server中default用户创建的过程展示出来,并引入raxNode基数树这个待阅读分析的概念。