*ACL
Redis ACL,即访问控制列表(Access Control List),是一项允许限制某些连接可执行的命令和可访问的键的功能。其工作方式如下:连接后,客户端需要提供用户名和有效密码进行认证;如果认证阶段成功,该连接将与指定用户及其权限关联。Redis 可以配置为让新连接自动以 "default" 用户身份通过认证(这是默认配置),因此配置 default 用户的同时,也能为未显式认证的连接提供特定功能子集。
在默认配置下,Redis 6(首个支持 ACL 的版本)的工作方式与旧版本完全一致:每个新连接都可以调用所有命令并访问所有键,因此 ACL 功能与旧客户端和应用程序向后兼容。另外,过去通过 requirepass 配置指令设置密码的方式仍然有效,但现在它的作用只是为 default 用户设置密码。
Redis 的 AUTH 命令在 Redis 6 中得到了扩展,现在支持双参数形式:
AUTH <username> <password>
当按旧形式使用时:
AUTH <password>
实际上使用的用户名是 "default",因此仅指定密码就意味着我们要针对 default 用户进行认证。这提供了完美的向后兼容性。
*何时 ACL 有用
在使用 ACL 之前,你可能想问问自己希望通过这层保护实现什么目标。通常 ACL 能很好地服务于两个主要目标:
- 你希望通过限制对命令和键的访问来提高安全性,使不受信任的客户端无法访问,而受信任的客户端只拥有执行其工作所需的最低数据库访问级别。例如,某些客户端可能只能执行只读命令。
- 你希望通过操作安全性来防止进程或人员因软件错误或人为失误而损坏数据或配置。例如,从 Redis 中获取延迟作业的 worker 没有理由调用 FLUSHALL 命令。
ACL 的另一个典型用途与托管 Redis 实例有关。Redis 经常作为托管服务提供,要么由处理 Redis 基础设施的内部公司团队为其他内部客户提供服务,要么由云提供商以软件即服务的形式提供。在这两种情况下,我们都希望确保配置命令对客户不可用。过去通过命令重命名来实现这一点,这是一种在没有 ACL 的情况下让我们存活很长时间的技巧,但并不理想。
*使用 ACL 命令配置 ACL
ACL 使用一种 DSL(领域特定语言)来定义给定用户能做什么或不能做什么。这些规则总是从第一条到最后一条、从左到右实现的,因为有时规则的顺序对于理解用户真正能做什么很重要。
默认情况下定义了一个用户,称为 default。我们可以使用 ACL LIST 命令来检查当前活动的 ACL,并验证一个刚启动的、默认配置的 Redis 实例的配置:
> ACL LIST
1) "user default on nopass ~* +@all"
上述命令以 Redis 配置文件中使用的相同格式报告用户列表,通过将当前为用户设置的 ACL 转换回其描述。
每行的前两个词是 "user" 后跟用户名。接下来的词是描述不同内容的 ACL 规则。我们稍后将详细介绍这些规则的工作原理,但现在只需知道 default 用户被配置为激活状态(on),不需要密码(nopass),可以访问所有可能的键(~*),并能调用所有可能的命令(+@all)。
另外,在 default 用户的特殊情况下,拥有 nopass 规则意味着新连接会自动以 default 用户身份通过认证,无需任何显式的 AUTH 调用。
*ACL 规则
以下是有效 ACL 规则的列表。某些规则只是用于激活或移除标志、或对用户 ACL 执行给定更改的单个词。其他规则是字符前缀,与命令或类别名称、键模式等连接在一起。
启用和禁用用户:
on:启用用户:可以认证为此用户。off:禁用用户:不再能用此用户认证,但已经认证的连接仍然有效。注意,如果 default 用户被标记为 off,新连接将以未认证状态开始,并要求用户发送 AUTH 或使用带 AUTH 选项的 HELLO 以某种方式进行认证,无论 default 用户配置如何。
允许和禁止命令:
+<command>:将该命令添加到用户可以调用的命令列表中。-<command>:从用户可以调用的命令列表中移除该命令。+@<category>:将该类别中的所有命令添加到用户可调用列表中,有效类别如 @admin、@set、@sortedset 等,可通过调用 ACL CAT 命令查看完整列表。特殊类别 @all 表示所有命令,包括当前服务器中存在的命令以及将来通过模块加载的命令。-@<category>:类似+@<category>,但从客户端可以调用的命令列表中移除命令。+<command>|subcommand:允许某个被整体禁用的命令的特定子命令。注意,这种形式不能作为否定形式使用,如-DEBUG|SEGFAULT,只能以 "+" 开头进行添加。如果该命令已经整体激活,此 ACL 将导致错误。allcommands:+@all 的别名。注意它意味着能够执行通过模块系统加载的所有未来命令。nocommands:-@all 的别名。
允许和禁止某些键:
~<pattern>:添加可作为命令参数提及的键模式。例如~*允许所有键。该模式是类似 KEYS 命令使用的 glob 风格模式。可以指定多个模式。allkeys:~*的别名。resetkeys:刷新允许的键模式列表。例如 ACL~foo:* ~bar:* resetkeys ~objects:*,结果将导致客户端只能访问匹配模式objects:*的键。
为用户配置有效密码:
><password>:将此密码添加到用户的有效密码列表中。例如>mypass将 "mypass" 添加到有效密码列表中。此指令会清除 nopass 标志(见下文)。每个用户可以有任意数量的密码。<<password>:从有效密码列表中移除此密码。如果试图移除的密码实际上并未设置,则会报错。#<hash>:将此 SHA-256 哈希值添加到用户的有效密码列表中。该哈希值将与为 ACL 用户输入的密码哈希进行比较。这允许用户在acl.conf文件中存储哈希值而不是明文密码。只接受 SHA-256 哈希值作为密码哈希,必须是 64 个字符且只包含小写十六进制字符。!<hash>:从有效密码列表中移除此哈希值。当你不知道哈希值所指定的密码但希望从用户中移除该密码时,这很有用。nopass:移除用户所有已设置的密码,并将用户标记为不需要密码:这意味着任何密码对该用户都有效。如果此指令用于 default 用户,每个新连接将立即以 default 用户身份通过认证,无需任何显式的 AUTH 命令。注意 resetpass 指令将清除此状态。resetpass:刷新允许的密码列表。此外移除 nopass 状态。执行 resetpass 后,用户没有关联的密码,不添加某些密码(或稍后将其设置为 nopass)就无法进行认证。
注意:未标记 nopass 且没有有效密码列表的用户实际上无法使用,因为没有办法以此用户身份登录。
重置用户:
reset执行以下操作:resetpass、resetkeys、off、-@all。用户返回到其创建后立即所处的相同状态。
*使用 ACL SETUSER 命令创建和编辑用户 ACL
用户可以通过两种主要方式创建和修改:
- 使用 ACL 命令及其 ACL SETUSER 子命令。
- 修改服务器配置,在其中定义用户,然后重启服务器,或者如果我们使用 外部 ACL 文件,只需发出 ACL LOAD。
在本节中,我们将学习如何使用 ACL 命令定义用户。掌握这些知识后,通过配置文件执行相同操作将变得微不足道。在配置中定义用户值得单独一节讨论,将在后面单独介绍。
首先,让我们尝试最简单的 ACL SETUSER 命令调用:
> ACL SETUSER alice
OK
SETUSER 命令接受用户名和要应用于用户的 ACL 规则列表。然而在上面的示例中,我根本没有指定任何规则。如果用户不存在,这将只是使用刚创建用户的默认属性创建用户。如果用户已经存在,上述命令将完全不执行任何操作。
让我们检查默认用户状态:
> ACL LIST
1) "user alice off -@all"
2) "user default on nopass ~* +@all"
刚创建的用户 "alice" 具有以下状态:
- 处于 off 状态,即被禁用。AUTH 将无法工作。
- 不能访问任何命令。注意,默认情况下创建的用户不能访问任何命令,因此上面输出中的
-@all可以省略,但 ACL LIST 试图明确而不是隐含。 - 最后,该用户不能访问任何键模式。
- 该用户也没有设置密码。
这样的用户完全没有用处。让我们尝试定义用户,使其处于激活状态,拥有密码,并且只能访问以字符串 "cached:" 开头的键名,使用 GET 命令。
> ACL SETUSER alice on >p1pp0 ~cached:* +get
OK
现在用户可以执行某些操作,但会拒绝执行其他操作:
> AUTH alice p1pp0
OK
> GET foo
(error) NOPERM this user has no permissions to access one of the keys used as arguments
> GET cached:1234
(nil)
> SET cached:1234 zap
(error) NOPERM this user has no permissions to run the 'set' command or its subcommand
事情按预期工作。为了检查 alice 用户的配置(记住用户名是区分大小写的),可以使用 ACL LIST 的替代方案,它设计为更适合计算机读取,而 ACL LIST 更倾向于人类阅读。
> ACL GETUSER alice
1) "flags"
2) 1) "on"
3) "passwords"
4) 1) "2d9c75..."
5) "commands"
6) "-@all +get"
7) "keys"
8) 1) "cached:*"
ACL GETUSER 返回一个字段-值数组,以更易于解析的术语描述用户。输出包括标志集、键模式列表、密码等。如果我们使用 RESP3,输出可能更易读,因为它会作为映射回复返回:
> ACL GETUSER alice
1# "flags" => 1~ "on"
2# "passwords" => 1) "2d9c75..."
3# "commands" => "-@all +get"
4# "keys" => 1) "cached:*"
注意:从现在开始,我们将继续使用 Redis 默认协议版本 2,因为社区切换到新协议还需要一些时间。
使用另一个 ACL SETUSER 命令(来自不同用户,因为 alice 不能运行 ACL 命令),我们可以为用户添加多个模式:
> ACL SETUSER alice ~objects:* ~items:* ~public:*
OK
> ACL LIST
1) "user alice on >2d9c75... ~cached:* ~objects:* ~items:* ~public:* -@all +get"
2) "user default on nopass ~* +@all"
内存中的用户表示现在正如我们所期望的那样。
*多次调用 ACL SETUSER 会发生什么
理解多次调用 ACL SETUSER 时会发生什么非常重要。关键要知道的是,每次 SETUSER 调用都不会重置用户,而是将 ACL 规则应用于现有用户。只有在用户之前未知的情况下才会重置用户:在这种情况下会创建一个全新的用户,其 ACL 为零,即用户不能做任何事情,被禁用,没有密码等:为了安全起见,这是最好的默认设置。
然而后续调用只是增量地修改用户,因此以下序列:
> ACL SETUSER myuser +set
OK
> ACL SETUSER myuser +get
OK
> ACL LIST
1) "user default on nopass ~* +@all"
2) "user myuser off -@all +set +get"
*玩转命令类别
通过逐个指定所有命令来设置用户 ACL 真的很烦人,所以我们改为这样做:
> ACL SETUSER antirez on +@all -@dangerous >42a979... ~*
通过说 +@all 和 -@dangerous,我们包含了所有命令,然后移除了 Redis 命令表中被标记为危险的所有命令。请注意,命令类别从不包括模块命令,+@all 除外。如果你说 +@all,用户可以执行所有命令,甚至包括将来通过模块系统加载的命令。然而如果你使用像 +@read 这样的 ACL 规则或其他任何规则,模块命令总是被排除。这非常重要,因为你应该只信任 Redis 内部命令表的健全性。模块可能暴露危险的功能,在 ACL 只是累加的情况下,即以 +@all -... 的形式,你应该绝对确保你不会包含无意包含的内容。
然而要记住类别是如何定义的,以及每个类别具体包含哪些命令,是不可能的而且会非常无聊,所以 Redis ACL 命令导出了 CAT 子命令,可以以两种形式使用:
ACL CAT -- 仅列出所有可用类别
ACL CAT <category-name> -- 列出该类别中的所有命令
示例:
> 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"
如你所见,目前有 21 个不同的类别。现在让我们检查 geo 类别包含哪些命令:
> ACL CAT geo
1) "geohash"
2) "georadius_ro"
3) "georadiusbymember"
4) "geopos"
5) "geoadd"
6) "georadiusbymember_ro"
7) "geodist"
8) "georadius"
请注意,命令可能属于多个类别,因此例如像 +@geo -@read 这样的 ACL 规则将导致某些 geo 命令被排除,因为它们是只读命令。
*添加子命令
通常仅仅排除或包含整个命令的能力是不够的。许多 Redis 命令根据作为参数传递的子命令执行多种操作。例如 CLIENT 命令可用于执行危险和非危险操作。许多部署可能不希望向非管理员级别的用户提供执行 CLIENT KILL 的能力,但仍希望他们能够运行 CLIENT SETNAME。
注意:新的 RESP3 HELLO 命令可能很快会提供 SETNAME 选项,但这仍然是一个很好的示例。
在这种情况下,我可以按以下方式修改用户的 ACL:
ACL SETUSER myuser -client +client|setname +client|getname
我首先移除了 CLIENT 命令,然后添加了两个允许的子命令。请注意不能执行相反的操作,子命令只能添加而不能排除,因为将来可能会添加新的子命令:为某些用户指定所有有效的子命令要安全得多。此外,如果你为一个尚未整体禁用的命令添加子命令,将产生错误,因为这只能是 ACL 规则中的 bug:
> ACL SETUSER default +debug|segfault
(error) ERR Error in ACL SETUSER modifier '+debug|segfault': Adding a
subcommand of a command already fully added is not allowed. Remove the
command to start. Example: -DEBUG +DEBUG|DIGEST
请注意,子命令匹配可能会带来一些性能损失,但即使使用合成基准测试也很难衡量这种损失,而且额外的 CPU 成本仅在调用此类命令时才会支付,而不是在调用其他命令时。
*+@all 与 -@all
在前一节中,我们观察到了如何基于添加/移除单个命令来定义命令 ACL。
*密码在内部如何存储
Redis 在内部使用 SHA256 对密码进行哈希存储,如果你设置了一个密码并检查 ACL LIST 或 GETUSER 的输出,你会看到一个看起来像伪随机的长十六进制字符串。这里有一个示例,因为在前面的示例中,为了简洁起见,长十六进制字符串被截断了:
> ACL GETUSER default
1) "flags"
2) 1) "on"
2) "allkeys"
3) "allcommands"
3) "passwords"
4) 1) "2d9c75273d72b32df726fb545c8a4edc719f0a95a6fd993950b10c474ad9c927"
5) "commands"
6) "+@all"
7) "keys"
8) 1) "*"
此外,从 Redis 6 开始,旧的命令 CONFIG GET requirepass 将不再返回明文密码,而是返回哈希后的密码。
使用 SHA256 提供了避免以明文存储密码的能力,同时仍然允许非常快速的 AUTH 命令,这是 Redis 的一个非常重要的特性,并且与客户端对 Redis 的期望一致。
然而 ACL 密码 并不是真正的密码:它们是服务器和客户端之间的共享密钥,因为在这种情况下密码不是人类使用的认证令牌。例如:
* 没有长度限制,密码只会被某些客户端软件记住,在这种上下文中不需要人类记住密码。
* ACL 密码不保护任何其他东西:例如,它永远不会是某个电子邮件账户的密码。
* 通常当你能够通过完全访问给定服务器的 Redis 命令,或破坏系统本身来获得哈希密码时,你已经拥有了该密码所保护内容的访问权限:Redis 实例的稳定性和其中包含的数据。
因此,为了使用密码破解困难的算法而减慢密码认证速度,是一个非常糟糕的选择。我们反而建议生成非常强的密码,这样即使拥有哈希值,也没有人能够使用字典或暴力攻击破解它。为此,有一个特殊的 ACL 命令,使用系统加密伪随机生成器生成密码:
> ACL GENPASS
"dd721260bfe1b3d9601e7fbab36de6d04e2e67b0ef1c53de59d45950db0dd3cc"
该命令输出一个 32 字节(256 位)的伪随机字符串,转换为 64 字节的字母数字字符串。这足以避免攻击,同时又足够短,便于管理、复制粘贴、存储等。你应该使用此命令来生成 Redis 密码。
*使用外部 ACL 文件
有两种方式可以在 Redis 配置中存储用户。
1. 可以直接在 `redis.conf` 文件中指定用户。
2. 可以指定一个外部 ACL 文件。
这两种方法是互不兼容的,Redis 会要求你使用其中一种。在 redis.conf 中指定用户是一种非常简单的方式,适用于简单用例。当需要定义多个用户,在复杂环境中,我们强烈建议使用 ACL 文件。
在 redis.conf 和外部 ACL 文件中使用的格式完全相同,因此从一种方式切换到另一种方式非常简单,格式如下:
user <username> ... acl rules ...
例如:
user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
当你想使用外部 ACL 文件时,需要指定名为 aclfile 的配置指令,如下所示:
aclfile /etc/redis/users.acl
当你在 redis.conf 文件中直接指定少量用户时,可以使用 CONFIG REWRITE 来通过重写文件将新的用户配置存储在其中。
然而外部 ACL 文件功能更强大。你可以执行以下操作:
* 如果你手动修改了 ACL 文件并希望 Redis 重新加载新配置,请使用 [ACL LOAD](/commands/acl-load.html)。注意,此命令只有在所有用户都正确指定的情况下才能加载文件,否则会向用户报告错误,并且旧配置仍然有效。
* 使用 [ACL SAVE](/commands/acl-save.html) 将当前 ACL 配置保存到 ACL 文件中。
请注意,CONFIG REWRITE 不会触发 ACL SAVE:当你使用 ACL 文件时,配置和 ACL 是分开处理的。
*Sentinel 和副本的 ACL 规则
如果你不想为 Redis 副本和 Redis Sentinel 实例提供对 Redis 实例的完全访问权限,以下是必须允许的一组命令,以便一切正常工作。
对于 Sentinel,允许用户在主实例和副本实例上访问以下命令:
- AUTH, CLIENT, SUBSCRIBE, SCRIPT, PUBLISH, PING, INFO, MULTI, SLAVEOF, CONFIG, CLIENT, EXEC.
Sentinel 不需要访问数据库中的任何键,因此 ACL 规则如下(注意:AUTH 不需要,因为它总是被允许的):
ACL setuser sentinel-user >somepassword +client +subscribe +publish +ping +info +multi +slaveof +config +client +exec on
Redis 副本需要在主实例上允许以下命令:
- PSYNC, REPLCONF, PING
不需要访问任何键,因此转换为以下规则:
ACL setuser replica-user >somepassword +psync +replconf +ping on
请注意,你不需要配置副本以允许主实例执行任何命令:从副本的角度来看,主实例始终以 root 用户身份进行认证。
*本文档的待办事项列表
- 确保说明在添加/移除类别时模块命令被忽略。
- 用某些基准测试记录键匹配的成本。
- 记录 +@all 如何也包括模块命令和每个未来命令。
- 记录与 requirepass 和单参数 AUTH 的向后兼容性。