在Redis6之前的版本,我们只能使用requirepass参数给default用户配置登录密码,同一个redis集群的所有开发都共享default用户,难免会出现误操作把别人的key删掉或者数据泄露的情况,那之前我们也可以使用rename command的方式给一些危险函数重命名或禁用,但是这样也防止不了自己的key被其他人访问。
因此Redis6版本推出了ACL(Access Control List)访问控制权限的功能,基于此功能,我们可以设置多个用户,并且给每个用户单独设置命令权限和数据权限。 为了保证向下兼容,Redis6保留了default用户和使用requirepass的方式给default用户设置密码,默认情况下default用户拥有Redis最大权限,我们使用redis-cli连接时如果没有指定用户名,用户也是默认default。
我们知道既然redis6.0之后允许创建多个用户,那么redis-cli在认证的过程中,认证的方式就会发生改变:
- 老的认证方式:AUTH <password>
- 新的认证方式:AUTH <username> <password>
一、开启ACL配置
在介绍ACLs之前,我们先开启ACL配置。配置ACL的方式有两种:
- 用户可以直接在redis.conf文件中指定;
- 可以指定外部ACL文件。
这两种方法是相互不兼容的,所以Redis会要求你使用其中一种。在redis.conf中指定用户适用于简单的用例。在复杂的环境中,当需要定义多个用户时,建议使用ACL文件。
在redis.conf和外部ACL文件中使用的格式是完全相同的,所以从一种方式切换到另一种方式的情况,不需要关注ACL命令格式的变化,如下所示:
user <username> ... acl rules ...
例如:
user worker +@list +@connection ~jobs:* on >efa7303c493aa09
注意:
两种配置方式,对于存储新用户配置所使用的命令有所不同:
- redis.conf方式:使用CONFIG REWRITE来通过重写文件来存储新的用户配置。
- ACL file方式:使用ACL SAVE来通过重写文件来存储新的用户配置。
我这里只ACL File的配置模式:
- 如果之前使用了config模式,redis.conf中会有之前已经生效的DSL,我们需要注释掉。例如:
#user default on nopass ~* &* +@all
- 可能我们的redis.conf文件中还会有requirepass配置,当我们开启ACL之后,requirepass将不在生效,这里我们也注释掉,当然如果不注释也不会有影响:
#requirepass default123
- 在config文件中配置aclfile的路径,需要在redis.conf中添加:
#这里需要注意如果是docker形式部署,引用的是docker容器中的挂载路径 aclfile /home/redis/conf/users.acl
需要注意对应的文件要先建立好,不然重启redis时会报错。
touch /home/redis/conf/users.acl && chmod -R 777 /home/redis/conf/
- 重启redis即生效。(配置完成之后,default用户属于无密码状态)
使用外部ACL文件的方式会更强大。可以执行以下操作:
- 如果手动修改了ACL文件后发现配置错误,我们想要恢复到之前Redis中ACL的旧配置时,可以使用ACL LOAD。注意,只有在正确指定了所有用户时,该命令才能加载文件。否则,将向用户报告错误,旧的配置仍然有效;(对于这方面,redis.conf方式想要实现ACL LOAD的效果,只能重启redis服务,在这一方面ACL file方式更强大)
- 使用ACL SAVE命令保存当前ACL配置到ACL文件中;
开启aclfile之后不能使用redis-cli -a xxx登陆,必须使用redis-cli --user xxx --pass yyy来登陆。
二、ACL规则
ACL是使用DSL(Domain specific language)定义的,该DSL描述了用户能够执行的操作。该规则始终从上到下,从左到右应用,因为规则的顺序对于理解用户的实际权限很重要。ACL规则可以在redis.conf文件以及users.acl文件中配置DSL,也可以在命令行中通过ACL命令配置。
1、启用和禁用用户
on
:启用用户:可以以该用户身份进行认证。off
:禁用用户:不再可以使用此用户进行身份验证,但是已经通过身份验证的连接仍然可以使用。
2、允许和禁止调用命令
- +<command>:将命令添加到用户可以调用的命令列表中。
- -<command>:将命令从用户可以调用的命令列表中移除。
- +@<category>:允许用户调用 <category> 类别中的所有命令,有效类别为@admin,@set,@sortedset等,可通过调用ACL CAT命令查看完整列表。特殊类别@all表示所有命令,包括当前和未来版本中存在的所有命令。
- -@<category>:禁止用户调用<category> 类别中的所有命令。
- +<command>|subcommand:允许使用已禁用命令的特定子命令。
- allcommands:+@all的别名。包括当前存在的命令以及将来通过模块加载的所有命令。
- nocommands:-@all的别名,禁止调用所有命令。
3、允许或禁止访问某些Key
- ~<pattern>:添加可以在命令中提及的键模式。例如~*和* allkeys 允许所有键。
- * resetkeys:使用当前模式覆盖所有允许的模式。如: ~foo:* ~bar:* resetkeys ~objects:* ,客户端只能访问匹配 object:* 模式的 KEY。
4、为用户配置有效密码
- ><password>:将此密码添加到用户的有效密码列表中。例如,>mypass将“mypass”添加到有效密码列表中。该命令会清除用户的nopass标记。每个用户可以有任意数量的有效密码。
- <<password>:从有效密码列表中删除此密码。若该用户的有效密码列表中没有此密码则会返回错误信息。
- #<hash>:将此SHA-256哈希值添加到用户的有效密码列表中。该哈希值将与为ACL用户输入的密码的哈希值进行比较。允许用户将哈希存储在users.acl文件中,而不是存储明文密码。仅接受SHA-256哈希值,因为密码哈希必须为64个字符且小写的十六进制字符。
- !<hash>:从有效密码列表中删除该哈希值。当不知道哈希值对应的明文是什么时很有用。
- nopass:移除该用户已设置的所有密码,并将该用户标记为nopass无密码状态:任何密码都可以登录。resetpass命令可以清除nopass这种状态。
- resetpass:情况该用户的所有密码列表。而且移除nopass状态。resetpass之后用户没有关联的密码同时也无法使用无密码登录,因此resetpass之后必须添加密码或改为nopass状态才能正常登录。
- reset:重置用户状态为初始状态。执行以下操作resetpass,resetkeys,off,-@all。
三、使用ACL命令来创建用户与权限配置
ACL是使用DSL(Domain specific language)定义的,该DSL描述了用户能够执行的操作。该规则始终从上到下,从左到右应用,因为规则的顺序对于理解用户的实际权限很重要。
接下来我们使用ACL命令来定义一个新的用户,创建一个简单的用户执行一下命令:
> ACL SETUSER alice
OK
这里我们简单的创建了一个username为alice的用户,并且没有指定任何的规则;
- 如果用户alice不存在则创建alice用户,如果alice已经存在则不做任何改动。
查看用户状态:
> ACL LIST
1) "user alice off resetchannels -@all"
2) "user default on nopass ~* &* +@all"
我们通过新建的alice用户,可以知道如下信息:
- 在off状态下,相关AUTH操作alice用户不生效;
例如我们使用alice进行登录,如果在default用户没有设置密码的情况下是可以正常登录的,但是当登录成功后,使用以下命令查看当前用户我们会发现不是alice而是default:./redis-cli -c -h 11.248.246.7 -p 6379 --user alice
11.248.246.15:6379> acl whoami "default"
- alice没有设置密码;
- alice用户没有使用任何命令的权限,我们使用default用户创建alice用户是没有指定相关权限设置,所以ACL LIST展示的为-@all;
- 因为没有key patterns相关的展示,所以alice用户可以使用;
- 因为没有Pub/Sub channels相关的展示,所以alice用户可以使用;
alice目前是无效的,接下来我们来开启alice用户,并设置用户的的密码,并且仅可以使用get命令key的前缀为cached:
> ACL SETUSER alice on >alice ~cached:* +get
OK
设置完成之后,我们需要知道的是:
- ./redis-cli -c -h 11.248.246.7 -p 6379 --user alice --pass alice 使用该命令进行登录的用户,上面的设置会生效;
- ./redis-cli -c -h 11.248.246.7 -p 6379 --user alice 不带密码我们发现也可以登录,这是因为当前使用的default用户登录的,可以使用acl whoami命令来进行查看(原因为当前default用户并没有设置密码);
- 所以基于以上两点,一般的情况都是屏蔽掉default用户,或者给default用户设置密码;
我们验证一下刚才的设置是否生效:
> AUTH alice alice
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
注意:
- 如果当前验证环境是一个集群的话并且发生了Redirected,那么该验证方式将会失败;
之前我们都是使用ACL LIST命令来查看用户信息,redis还有一个单独指定用户来查看用户信息的命令ACL GETUSER,该命令展示的用户信息更友好:
ACL GETUSER alice
1) "flags"
2) 1) "on"
3) "passwords"
4) (empty array)
5) "commands"
6) "-@all"
7) "keys"
8) ""
9) "channels"
10) ""
11) "selectors"
12) (empty array)
如果我们想一次设置多个key的使用,我们可以使用一下方式(不要使用alice用户,因为当前alice用户只有get权限,这里我们使用default用户):
11.248.246.21:6379> ACL SETUSER alice ~objects:* ~items:* ~public:*
OK
11.248.246.21:6379> ACL LIST
1) "user alice on ~objects:* ~items:* ~public:* resetchannels -@all"
2) "user default on nopass ~* &* +@all"
四、命令分类
日常工作中,一个一个的指定用户的权限会非常的繁琐;所以我们经常这样设置一个账号:
ACL SETUSER antirez on +@all -@dangerous >42a979... ~*
为了避免繁琐的配置,我们可能会使用通用的权限配置方式+@all和-@dangerous。antirez用户可以使用所有命令,但是在redis命令表中被标记为危险的命令是不可以使用的(-@dangerous);需要注意的是command categories不包含modules commands除了+@all;如果我们设置了+@all,那么该用户可以使用所有命令,甚至将来通过module systmen加载的新命令也可以使用。如果使用+@read或者是其他命令,modules commands也不包含在内。modules commands可能会暴漏一些风险,所以我们如果把用户设置+@all的话,是否这个用户真的需要使用modules commands。
command categories的相关说明:
- admin - 管理命令,通常应用是不需要这些命令的,包含REPLICAOF, CONFIG, DEBUG, SAVE, MONITOR, ACL, SHUTDOWN等;
- bitmap - 数据类型:bitmaps相关;
- blocking - 阻塞连接直到被其他命令释放;
- connection - 影响连接或其他连接的命令集,包含AUTH, SELECT, COMMAND, CLIENT, ECHO, PING, etc;
- dangerous - 潜在风险的命令集(每一个都应该考虑他的风险原因),包含FLUSHALL, MIGRATE, RESTORE, SORT, KEYS, CLIENT, DEBUG, INFO, CONFIG, SAVE, REPLICAOF, etc;
- geo - 数据类型:地理空间索引相关;
- hash - 数据类型:hashes相关;
- hyperloglog - 数据类型:基数统计相关;
- fast - 时间复杂度为O(1)的命令集,可以循环参数的数量,但不是键中的元素数量。
- keyspace - 以一种不可知的方式读或写keys、database、或他们的metadata,包含DEL, RESTORE, DUMP, RENAME, EXISTS, DBSIZE, KEYS, EXPIRE, TTL, FLUSHALL, 等;
- list - 数据类型:lists相关;
- pubsub - PubSub相关的命令集;
- read - 读取keys的值或元数据。需要注意的是,不与keys交互的命令既不能读也不能写;
- scripting - Scripting相关;
- set - 数据类型: sets相关;
- sortedset - 数据类型: sorted sets相关;
- slow - 所有不包含fast的命令集;
- stream - 数据类型: streams相关;
- string - 数据类型: strings相关;
- transaction - WATCH / MULTI / EXEC相关的命令集;
- write - 向keys中写 (values or metadata);
Redis提供了展示命令分类的列表命令,也提供可以列出每个分类中包含的命令:
ACL CAT -- Will just list all the categories available
ACL CAT <category-name> -- Will list all the commands inside the category
需要注意的是,有些命令可能属于多个分类,例如我们设置用户的权限为+@geo -@read,在我们使用ACL CAT查看时候,期望的是可以使用或陈列出geo相关命令,但是geo命令也是只读的命令,因为我们同是设置了-@read权限,所以导致geo无法正常展示与使用。
五、Selectors
上面的章节我们只是讲了如何基于添加/删除单个命令来定义ACL。
从Redis 7.0开始,Redis支持添加多组相互独立评估的规则,这些次要权限集称为选择器,通过将一组规则括在圆括号中来添加,为了执行命令,根权限(在圆括号外定义的规则)或任何选择器(在圆括号内定义的规则)必须与给定的命令匹配。在内部,首先检查根权限,然后按照添加的顺序检查选择器。
例如:
某个用户的ACL规则设置为+GET ~key1 (+SET ~key2),那么该用户可以执行
GET key1
和SET key2 hello,但是不能执行GET key2
或者SET key1 world。
与用户的根权限不同,selectors被添加后不可以被修改。相反,可以使用clearselectors关键字删除选择器,这将删除所有添加的选择器。 注意,clearselectors不会删除根权限。
六、Key permissions
从Redis7.0开始,键模式还可以用于定义命令如何能够触摸键。这是通过定义key权限的规则实现的。键权限的规则为%(<permission>)~<pattern>,权限定义为映射到以下键权限的单个字符:
- W (Write): 存储在key中的数据可以被更新或删除;
- R (Read): 从key中处理、复制或返回用户提供的数据。注意,这并不包括元数据,例如大小信息(例如STRLEN)、类型信息(例如type)或关于集合中是否存在某个值的信息(例如SISMEMBER)。
权限可以通过指定多个字符组合在一起。将权限指定为“RW”被认为是完全访问,类似于只传入~<pattern>。
举一个具体的例子,用户使用的ACL规则为+@all ~app1:* (+@readonly ~app2:*),该用户对app1:*具有完全访问权限,对app2:*具有只读访问权限,但是,有些命令支持从一个键读取数据,进行一些转换,并将其存储到另一个键中。COPY命令就是这样一个命令,它将数据从源key复制到目标key。示例中的ACL规则无法处理将数据从app2:user复制到app1:user的请求,因为根权限和选择器都不能完全匹配命令。如果可以达到上述需要使用COPY命令来复制数据的需求,可以使用键选择器可以定义一组ACL规则来处理此请求+@all ~app1:* %R~app2:*,我们观察到不同的是,%R的规则设置,R可以实现复制的功能。
我们可以通过文档https://redis.io/docs/reference/key-specs/https://redis.io/topics/key-specs#logical-operation-flags
来知道我们需要哪种类型的权限,这种权限类型基于keys的逻辑操作标志。插入、更新和删除标志映射到key的写权限,而访问标志映射到key的读权限,如果默写key没有逻辑操作标志,例如EXISTS,用户仍然需要具有键读或键写权限才能执行命令。
注意:
在评估执行命令是否需要读权限时,会忽略访问用户数据的侧信道(Side channels),这意味着一些返回关于修改后的key的元数据的写命令只需要对该key具有写权限即可执行,例如,考虑以下两个命令:
LPUSH key1 data
: 修改“key1”,但只返回关于它的元数据,即推送后的列表大小,因此该命令只需要对“key1”具有写权限即可执行;- LPOP key2: 修改“key2”,但也从列表中最左边的项返回数据,因此该命令需要对“key2”具有读写权限才能执行;
如果应用程序需要确保从key(包括侧通信)不访问任何数据,建议不提供对key的任何访问。
七、Redis的内部密码存储
Redis内部存储使用SHA256散列的密码。如果我们使用SHA256设置密码,当我们使用ACL LIST或ACL GETUSER来查看密码时,我们将看到一个长十六进制字符串,看起来是伪随机的。
下面的示例展示出Redis内部密码存储的方式:
>acl getuser antirez
1) "flags"
2) 1) "on"
3) "passwords"
4) 1) "4d1fb0e75bec80fe2ed59d7ed3c173b2553491d72e868eaf9150fe510e18b38d"
5) "commands"
6) "+@all -@dangerous"
7) "keys"
8) "~*"
9) "channels"
10) ""
11) "selectors"
12) (empty array)
使用SHA256的方式进行存储,避免了明文存储的,安全性会有所提高。但是ACL密码并不是真正的密码。它们是服务器和客户端之间共享的秘密,因为密码不是人工使用的身份验证令牌。例如:
- 没有长度限制,密码只会被一些客户端软件记住。在这种情况下,没有人需要找回密码;
- ACL密码不能保护其他任何东西。例如,它永远不会是某些电子邮件帐户的密码;
- 通常情况下,当你能够访问散列密码本身时,通过对给定服务器的Redis命令的完全访问,或者破坏系统本身,你已经访问了密码正在保护的东西:Redis实例的稳定性和它包含的数据;
因此,为了解决一种利用时间和空间的密码破解算法,而放慢密码的认证速度,这是一个非常糟糕的处理方式。相反,Redis团队建议生成强密码,这样就没有人能够使用字典或暴力攻击的方式来破解它,即使他们有哈希。所以Redis提供了一个特殊的ACL命令ACL GENPASS,它使用系统密码伪随机生成器生成密码:
>ACL GENPASS
"2d7bbe123a0ce7de8f54791b2fdf130e238fa251565fbe178f2c2e162dd5426c"
这个命令输出 32-byte(256-bit)的伪随机字符串并转换成64-byte的数字和字母混合字符串。这样足以避免攻击并便于管理、剪切、粘贴、存储等等。建议用户的密码设置尽量使用ACL GENPASS来进行生成。
八、关于Sentinel和Replicas的ACL规则
如果你不想提供Redis副本和Redis哨兵实例对你的Redis实例的完全访问,下面是一组必须允许的命令,以便可以正常运行:
对于Sentinel,允许用户在主实例和副本实例中访问以下命令:
- AUTH, CLIENT, SUBSCRIBE, SCRIPT, PUBLISH, PING, INFO, MULTI, SLAVEOF, CONFIG, CLIENT, EXEC.
哨兵不需要访问数据库中的任何键,但使用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
Redis副本需要在主实例上允许执行以下命令:
- PSYNC, REPLCONF, PING
Redis副本也不需要访问任何键,所以我们可以设置如下规则:
ACL setuser replica-user on >somepassword +psync +replconf +ping
我们需要知道的是不需要配置副本来允许主服务器能够执行任何一组命令。从副本的角度来看,主服务器总是作为根用户进行身份验证
总结
Redis是一种高性能的缓存数据库,每秒可处理百万级的请求,如果没有很好的ACL控制,很可能会被暴力破解;所以Redis6级以上版本解决了这一重大隐患。