跳转到主要内容

使用访问控制列表 (ACL)

KeyDB ACL,即访问控制列表(Access Control List)的缩写,是允许限制某些连接可执行的命令和可访问的键的功能。其工作方式是,客户端连接后需要提供用户名和有效密码进行身份验证:如果身份验证成功,该连接会与指定用户关联,并受该用户的限制。KeyDB 可以配置为新连接默认已通过一个“default”用户进行身份验证(这是默认配置),因此,配置默认用户的一个副作用是,可以为未显式进行身份验证的连接提供一个特定的功能子集。

在默认配置下,KeyDB 6(首个包含 ACL 的版本)的工作方式与旧版 KeyDB 完全相同,即每个新连接都能调用所有可能的命令并访问所有键,因此 ACL 功能与旧客户端和应用程序向后兼容。此外,旧的密码配置方式,即使用 requirepass 配置指令,仍然按预期工作,但现在它的作用只是为默认用户设置密码。

KeyDB 的 AUTH 命令在 KeyDB 6 中得到了扩展,现在可以使用双参数形式:

AUTH <username> <password>

当按照旧形式使用时,即:

AUTH <password>

发生的情况是,用于身份验证的用户名是“default”,因此只指定密码意味着我们希望针对默认用户进行身份验证。这提供了与过去完美的向后兼容性。

ACL 何时有用#

在使用 ACL 之前,您可能需要问自己,实现这一层保护的目标是什么。通常,ACL 可以很好地服务于两个主要目标:

  1. 您希望通过限制对命令和键的访问来提高安全性,以便不受信任的客户端无法访问,而受信任的客户端仅拥有执行所需工作所需的最低数据库访问级别。例如,某些客户端可能只能执行只读命令。
  2. 您希望提高操作安全性,以便访问 KeyDB 的进程或人员不会因为软件错误或手动失误而破坏数据或配置。例如,一个从 KeyDB 中获取延迟作业的工作进程没有理由能够调用 FLUSHALL 命令。

ACL 的另一个典型用途与托管的 KeyDB 实例有关。KeyDB 通常作为托管服务提供,既可以由公司内部团队为其他内部客户处理 KeyDB 基础设施,也可以由云提供商以软件即服务(SaaS)的形式提供。在这两种设置中,我们都希望确保客户无法使用配置命令。过去通过命令重命名来实现这一点,这种技巧使我们能够在没有 ACL 的情况下长期生存,但并不理想。

使用 ACL 命令配置 ACL#

ACL 是使用一种 DSL(领域特定语言)定义的,该语言描述了给定用户能做什么或不能做什么。这些规则总是从头到尾、从左到右地实现,因为有时规则的顺序对于理解用户真正能做什么很重要。

默认情况下,定义了一个名为 *default* 的用户。我们可以使用 ACL LIST 命令来检查当前活动的 ACL,并验证一个刚刚启动、使用默认配置的 KeyDB 实例的配置是怎样的:

> ACL LIST
1) "user default on nopass ~* &* +@all"

上述命令以与 KeyDB 配置文件中使用的相同格式报告用户列表,它将当前为用户设置的 ACL 转换回其描述。

每行的前两个词是“user”和用户名。接下来的词是描述不同内容的 ACL 规则。我们将详细展示规则如何工作,但目前只需知道,默认用户被配置为活动状态(on)、不需要密码(nopass)、可以访问所有可能的键(~*)和发布/订阅通道(&*),并且能够调用所有可能的命令(+@all)。

此外,在默认用户的特殊情况下,拥有 *nopass* 规则意味着新连接会自动以默认用户身份进行验证,无需任何显式的 AUTH 调用。

ACL 规则#

以下是有效的 ACL 规则列表。某些规则只是单个单词,用于激活或移除标志,或对用户 ACL 执行特定更改。其他规则是字符前缀,与命令或类别名称、键模式等连接在一起。

启用和禁用用户

  • on:启用用户:可以作为此用户进行身份验证。
  • off:禁用用户:不能再使用此用户进行身份验证,但已验证的连接仍然有效。请注意,如果默认用户被标记为 *off*,新连接将以未验证状态启动,并且需要用户发送 AUTH 或带有 AUTH 选项的 HELLO 以某种方式进行身份验证,无论默认用户的配置如何。

允许和禁止命令

  • +<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:* 模式的键。

允许和禁止发布/订阅通道

  • &<pattern>:添加一个 glob 风格的 Pub/Sub 通道模式,用户可以访问。可以指定多个通道模式。请注意,模式匹配仅适用于 PUBLISHSUBSCRIBE 提到的通道,而 PSUBSCRIBE 要求其通道模式与用户允许的通道模式进行字面匹配。
  • allchannels&* 的别名,允许用户访问所有 Pub/Sub 通道。
  • resetchannels:清空允许的通道模式列表,如果用户的 Pub/Sub 客户端不再能访问其各自的通道和/或通道模式,则断开其连接。

为用户配置有效密码

  • ><password>:将此密码添加到用户的有效密码列表中。例如,>mypass 会将“mypass”添加到有效密码列表中。此指令会清除 *nopass* 标志(见后文)。每个用户可以有任意数量的密码。
  • <<password>:从此用户的有效密码列表中移除此密码。如果您尝试移除的密码实际上不存在,则会产生错误。
  • #<hash>:将此 SHA-256 哈希值添加到用户的有效密码列表中。此哈希值将与为 ACL 用户输入的密码的哈希进行比较。这允许用户在 acl.conf 文件中存储哈希值而不是明文密码。只接受 SHA-256 哈希值,因为密码哈希必须是 64 个字符,并且只包含小写十六进制字符。
  • !<hash>:从此用户的有效密码列表中移除此哈希值。当您不知道哈希值指定的密码但想从用户中移除该密码时,这很有用。
  • nopass:移除用户的所有已设密码,并将用户标记为不需要密码:这意味着任何密码都对此用户有效。如果此指令用于默认用户,则每个新连接将立即以默认用户身份进行验证,无需任何显式的 AUTH 命令。请注意,*resetpass* 指令将清除此条件。
  • resetpass:清空允许的密码列表。此外,还会移除 *nopass* 状态。在 *resetpass* 之后,用户没有任何关联的密码,也无法进行身份验证,除非稍后添加一些密码(或将其设置为 *nopass*)。

注意:一个未标记为 nopass 且没有有效密码列表的用户,实际上是无法使用的,因为没有办法以该用户身份登录。

重置用户

  • reset 执行以下操作:resetpass, resetkeys, resetchannels, off, -@all。用户返回到其创建后的初始状态。

使用 ACL SETUSER 命令创建和编辑用户 ACL#

创建和修改用户主要有两种方式:

  1. 使用 ACL 命令及其 ACL SETUSER 子命令。
  2. 修改服务器配置,可以在其中定义用户,然后重启服务器,或者如果我们正在使用*外部 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 尝试做到显式而非隐式。
  • 用户没有可以访问的键模式。
  • 用户可以访问所有发布/订阅频道。

默认情况下,新用户创建时具有限制性权限。从 KeyDB 6.2 开始,ACL 也提供了发布/订阅通道访问管理。为确保从 6.0 版本升级到 KeyDB 6.2 时的向后兼容性,新用户默认被授予 'allchannels' 权限。可以通过 `acl-pubsub-default` 配置指令将默认值设置为 `resetchannels`。

这样的用户是完全无用的。让我们尝试定义用户,使其处于活动状态,有密码,并且只能使用 GET 命令访问以字符串“cached:”开头的键名。

> ACL SETUSER alice on >p1pp0 ~cached:* +get
OK

现在这个用户可以做一些事情,但会拒绝做其他事情:

> AUTH alice p1pp0
OK
> GET foo
(error) NOPERM 此用户无权访问用作参数的键之一
> GET cached:1234
(nil)
> SET cached:1234 zap
(error) NOPERM 此用户无权运行 'set' 命令或其子命令

事情按预期进行。为了检查用户 alice(请记住用户名区分大小写)的配置,可以使用 ACL LIST 的替代方法,该方法更适合计算机读取,而 ACL LIST 更偏向于人类阅读。

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

ACL GETUSER 返回一个字段-值数组,以更易于解析的术语描述用户。输出包括标志集、键模式列表、密码等。如果我们使用 RESP3,输出可能会更易读,因为它会以映射回复的形式返回:

> ACL GETUSER alice
1# "flags" => 1~ "on"
2~ "allchannels"
2# "passwords" => 1) "2d9c75273d72b32df726fb545c8a4edc719f0a95a6fd993950b10c474ad9c927"
3# "commands" => "-@all +get"
4# "keys" => 1) "cached:*"
5# "channels" => 1) "*"

注意:从现在起,我们将继续使用 KeyDB 的默认协议版本 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

将导致 myuser 能够同时调用 GETSET

> 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,我们包含了所有命令,然后移除了在 KeyDB 命令表中被标记为危险的所有命令。请注意,命令类别**永远不包括模块命令**,除了 +@all。如果您说 +@all,用户可以执行所有命令,甚至包括未来通过模块系统加载的命令。但是,如果您使用 ACL 规则 +@read 或任何其他规则,模块命令总是被排除的。这一点非常重要,因为您应该只信任 KeyDB 内部命令表的健全性。模块可能会暴露危险的东西,在 ACL 只是加法的情况下,即形式为 +@all -...,您应该绝对确定您不会包含您不打算包含的东西。

以下是命令类别及其含义的列表:

  • admin - 管理命令。普通应用程序永远不需要使用这些命令。包括 REPLICAOFCONFIGDEBUGSAVEMONITORACLSHUTDOWN 等。
  • bitmap - 数据类型:与位图相关。
  • blocking - 可能阻塞连接,直到被另一个命令释放。
  • connection - 影响连接或其他连接的命令。这包括 AUTHSELECTCOMMANDCLIENTECHOPING 等。
  • dangerous - 潜在危险的命令(出于各种原因,每个都应谨慎考虑)。这包括 FLUSHALLMIGRATERESTORESORTKEYSCLIENTDEBUGINFOCONFIGSAVEREPLICAOF 等。
  • geo - 数据类型:与地理空间索引相关。
  • hash - 数据类型:与哈希相关。
  • hyperloglog - 数据类型:与 hyperloglog 相关。
  • fast - 快速的 O(1) 命令。可能会根据参数数量循环,但不会根据键中元素的数量循环。
  • keyspace - 以与类型无关的方式从键、数据库或其元数据中写入或读取。包括 DELRESTOREDUMPRENAMEEXISTSDBSIZEKEYSEXPIRETTLFLUSHALL 等。可能修改键空间、键或元数据的命令也会有 write 类别。只读取键空间、键或元数据的命令将有 read 类别。
  • list - 数据类型:与列表相关。
  • pubsub - 与 PubSub 相关的命令。
  • read - 从键(值或元数据)中读取。请注意,不与键交互的命令既不会有 read 也不会有 write
  • scripting - 与脚本相关。
  • set - 数据类型:与集合相关。
  • sortedset - 数据类型:与有序集合相关。
  • slow - 所有不是 fast 的命令。
  • stream - 数据类型:与流相关。
  • string - 数据类型:与字符串相关。
  • transaction - 与 WATCH / MULTI / EXEC 相关的命令。
  • write - 写入键(值或元数据)。

KeyDB 还可以使用 KeyDB 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 命令被排除,因为它们是只读命令。

添加子命令#

通常,仅能整体排除或包含一个命令是不够的。许多 KeyDB 命令会根据作为参数传递的子命令执行不同的操作。例如,CLIENT 命令可用于执行危险和非危险的操作。许多部署可能不乐意向非管理员级别的用户提供执行 CLIENT KILL 的能力,但可能仍希望他们能够运行 CLIENT SETNAME

注意:新的 RESP3 HELLO 握手命令提供了一个 SETNAME 选项,但这仍然是子命令控制的一个好例子。

在这种情况下,我可以按以下方式更改用户的 ACL:

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

我首先移除了 CLIENT 命令,然后添加了两个允许的子命令。请注意,**反向操作是不可能的**,子命令只能添加,不能排除,因为未来可能会添加新的子命令:为某个用户指定所有有效的子命令要安全得多。此外,如果您为一个尚未被禁用的命令添加子命令,将会产生错误,因为这只能是 ACL 规则中的一个错误:

> 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 VS -@all#

在上一节中,我们观察了如何基于添加/移除单个命令来定义命令 ACL。

密码在内部是如何存储的#

KeyDB 内部使用 SHA256 对密码进行哈希存储,如果您设置了密码并查看 ACL LISTGETUSER 的输出,您会看到一个看起来像是伪随机的长十六进制字符串。这里有一个例子,因为在前面的例子中,为了简洁起见,长十六进制字符串被截断了:

> ACL GETUSER default
1) "flags"
2) 1) "on"
2) "allkeys"
3) "allcommands"
4) "allchannels"
3) "passwords"
4) 1) "2d9c75273d72b32df726fb545c8a4edc719f0a95a6fd993950b10c474ad9c927"
5) "commands"
6) "+@all"
7) "keys"
8) 1) "*"
9) "channels"
10) 1) "*"

此外,从 KeyDB 6 开始,旧命令 CONFIG GET requirepass 将不再返回明文密码,而是返回哈希后的密码。

使用 SHA256 可以在不存储明文密码的情况下,仍然允许非常快速的 AUTH 命令,这是 KeyDB 的一个非常重要的特性,并且与客户端对 KeyDB 的期望一致。

然而,ACL *密码* 并不是真正的密码:它们是服务器和客户端之间的共享秘密,因为在这种情况下,密码不是由人类使用的身份验证令牌。例如:

  • 没有长度限制,密码只会被记在某个客户端软件中,在这种情况下没有人需要记住密码。
  • ACL 密码不保护任何其他东西:例如,它永远不会是某个电子邮件帐户的密码。
  • 通常,当您能够访问哈希密码本身时,通过对给定服务器的 KeyDB 命令拥有完全访问权限,或破坏系统本身,您已经可以访问该密码所保护的内容:KeyDB 实例的稳定性和其包含的数据。

因此,为了使用一种耗费时间和空间的算法来减慢密码验证速度,以使密码破解变得困难,是一个非常糟糕的选择。我们建议的是生成非常强的密码,这样即使拥有哈希值,也没有人能够通过字典或暴力攻击来破解它。为此,有一个特殊的 ACL 命令,使用系统的密码学伪随机生成器来生成密码:

> ACL GENPASS
"dd721260bfe1b3d9601e7fbab36de6d04e2e67b0ef1c53de59d45950db0dd3cc"

该命令输出一个32字节(256位)的伪随机字符串,转换为一个64字节的字母数字字符串。这足够长以避免攻击,也足够短以便于管理、复制粘贴、存储等。这应该是您用来生成 KeyDB 密码的方法。

使用外部 ACL 文件#

在 KeyDB 配置中存储用户有两种方式。

  1. 用户可以直接在 keydb.conf 文件中指定。
  2. 可以指定一个外部 ACL 文件。

这两种方法是*互不兼容*的,KeyDB 会要求您使用其中一种。在 `keydb.conf` 中指定用户是一种非常简单的方法,适用于简单的使用场景。当需要定义多个用户,且环境复杂时,我们强烈建议您使用 ACL 文件。

keydb.conf 和外部 ACL 文件中使用的格式完全相同,因此从一种切换到另一种非常简单,格式如下:

user <username> ... acl rules ...

例如:

user worker +@list +@connection ~jobs:* on >ffa9203c493aa99

当您想使用外部 ACL 文件时,需要指定名为 aclfile 的配置指令,如下所示:

aclfile /etc/keydb/users.acl

当您只是直接在 `keydb.conf` 文件中指定几个用户时,您可以使用 `CONFIG REWRITE` 通过重写文件来将新的用户配置存储到文件中。

然而,外部 ACL 文件功能更强大。您可以执行以下操作:

  • 如果您手动修改了 ACL 文件并希望 KeyDB 重新加载新配置,请使用 ACL LOAD。请注意,此命令*仅在所有用户都正确指定的情况下*才能加载文件,否则会向用户报告错误,并且旧配置将保持有效。
  • 使用 ACL SAVE 将当前 ACL 配置保存到 ACL 文件。

请注意,CONFIG REWRITE 不会触发 ACL SAVE:当您使用 ACL 文件时,配置和 ACL 是分开处理的。

Sentinel 和副本的 ACL 规则#

如果您不想为 KeyDB 副本和 KeyDB Sentinel 实例提供对 KeyDB 实例的完全访问权限,以下是必须允许的命令集,以确保一切正常工作。

对于 Sentinel,允许用户在主实例和副本实例中访问以下命令:

  • AUTH, CLIENT, SUBSCRIBE, SCRIPT, PUBLISH, PING, INFO, MULTI, SLAVEOF, CONFIG, CLIENT, EXEC。

Sentinel 不需要访问数据库中的任何键,但会使用 Pub/Sub,因此 ACL 规则如下(注意:AUTH 不需要,因为它总是被允许的):

ACL SETUSER sentinel-user on >somepassword allchannels +multi +slaveof +ping +exec +subscribe +config|rewrite +role +publish +info +client|setname +client|kill +script|kill

KeyDB 副本需要在主实例上将以下命令列入白名单:

  • PSYNC, REPLCONF, PING

不需要访问任何键,所以这转化为以下规则:

ACL setuser replica-user on >somepassword +psync +replconf +ping

请注意,您不需要配置副本以允许主节点能够执行任何命令集:从副本的角度来看,主节点总是以 root 用户身份进行身份验证。