感谢各位看友来访,更多最新技术专栏文章尽在作者『 全栈工程师修炼指南』星球,加入一起持续学习吧!
0x00 前言简述
描述:近日,某网安中心发布关于Redis存在远程代码执行漏洞(QVD-2026-55651)的预警通报,该漏洞是 CVE-2026-23479 的未完全修复变种,因handleClientsBlockedOnKey() 函数处理阻塞客户端列表时存在堆使用后释放缺陷导致。攻击者可通过发送阻塞命令、多客户端同Key阻塞及客户端驱逐操作,篡改堆布局实现远程代码执行,以redis-server进程权限控制服务器,造成系统失陷。该漏洞影响 Redis8.8.0 及存在对应逻辑修复不彻底的相关版本,以及覆盖CVE-2026-23479 影响范围(7.2.0及以上至修复前所有版本),目前官方已发布安全补丁,需要升级到 8.8.2 版本或更高。
作者随即对企业中现有 Redis 内存数据库进行一一排查以并与相关开发人员进行沟通交流,决定升级到 8.10.x 版本并启用ACL功能,以实现更精细化的权限控制,从而保障 Redis 数据库的安全性。
本章将介绍简单介绍 Redis ACL 和相关管理命令,以及如何在容器(Docker、K8S)中部署 Redis 8.10.x 版本数据库并启用 ACL (访问控制列表)功能,同时配置基本的持久化、ACL文件权限,数据库安全及性能优化策略,最后再将容器部署配置时所遇问题进行总结,从而遇到相同问题的道友快速解决异常。
Redis ACL 介绍
Redis ACL 是 Access Control List 的缩写,它允许在可以执行的命令和可以访问的密钥方面限制某些连接,在 Redis 6.x 之前的版本中大家常常使用 requirepass Redis8101@2026 配置指令指定默认 default 用户的访问密码,在生产环境中存在一个问题是未限制你功能子集,只要有该密码就等同于获取最高管理员权限。并且随着企业中的业务越来越多,不可能每一个应用都创建一个 Redis 数据库,一是不便于管理,二是占用资源,三是权限存在风险。Redis ACL 为解决相关问题应运而生,其可单独为应用创建一个用户和访问密码,并赋予其可以操作哪些前缀的键,以及拥有哪些权限,从而提高业务和数据库的安全性。
ACL 是通过一种 DSL(领域特定语言)定义的,该语言描述了 特定用户被允许这样做,此类规则总是由 从头到尾,从左到右,默认情况下,只有一个用户定义默认。
> ACL LIST
> # user 后是用户名,on 表示是否被激活,nopass 表示无需密码,~* 表示操作所有键,&* +@all" 表示拥有全部权限
1) "user default on nopass ~* &* +@all"
由于 不同 Redis 版本新增了一些特性必然也有一些新的命令,大家可参考 Redis 官网命令来做权限控制,或者利用 acl 划分好的类,来赋予给用户,例如:Redis 8.10 Commands Reference | Docs
ACL 规则
启用和禁止用户:
on:启用用户:可以以该用户身份进行身份验证。
off:禁止该用户:无法再与该用户进行身份验证,不过之前认证过的连接仍然可以使用。
允许和拒绝命令:
+<command>:将该命令添加到用户可调用的命令列表中。
-<command>:将该命令从用户可调用的命令列表中移除。从 Redis 7.0 开始,它可以配合使用来阻止子命令(例如 “-config|set”)。
+@<category>:添加该类别中用户要调用的所有命令,有效类别如@admin、@set、@sortedset等。
-@<category>:类似但从客户端可以调用的命令列表中删除命令。
+<command>|first arg :单独允许禁用命令的特定,例如允许普通用户获取当前登录用户,+acl|whoami。
+@all: allcommands ,表示允许通过模块系统加载的所有未来命令。
-@all: nocommands ,表示不允许任何命令。
允许和拒绝键(key):
~<pattern>添加一个可以作为命令一部分提及的键模式,比如 允许所有键 ~*。
%R~<pattern>:(可在 Redis 7.0 及以上版本中提供)添加指定的读键模式。
W~<pattern>:(可在 Redis 7.0 及以上版本中提供)添加指定的写键值模式。
%RW~<pattern>:(可在 Redis 7.0 及更高版本中提供)加指定的读写模式。
allkeys:别名 ~* 及所有键。
resetkeys:清除允许密钥模式列表。
允许或禁止发布/订阅频道:
allchannels:别名 &*` 该名称允许用户访问所有发布/订阅频道。
&<pattern>:(可在 Redis 6.2 及以上版本中提供)添加一个用户可访问的 Pub/Sub 频道的整体模式
resetchannels:清除允许的信道模式列表,如果用户的发布/订阅客户端无法访问各自的信道和/或信道模式,则断开连接。
配置用户有效密码:
><password>将此密码添加到用户的有效密码列表中。
<<password>:将此密码从有效密码列表中移除。
#<hash>:将该SHA-256哈希值添加到用户有效密码列表中。
!<hash>:将该哈希值从有效密码列表中移除。
nopass:用户的所有设置密码都会被移除,用户被标记为无需密码,这意味着所有密码都对该用户有效,但 resetpass 指令会清除该条件。
resetpass:清除允许密码列表并移除nopass状态。resetpass 后,用户没有关联密码,也无法在不添加密码(或之后设置为nopass)的情况下进行身份验证,等同于禁用用户。
为用户配置选择器:
(<rule list>):(可在Redis 7.0及更高版本中使用)创建一个新的选择器来匹配规则。选择器在用户权限后进行评估,并根据其定义的顺序进行评估。如果命令符合用户权限或任何选择器,则允许使用。更多信息请参见选择者。
clearselectors:(可在Redis 7.0及更高版本中提供)删除所有附加到用户的选择器。
重置用户:
reset
- 执行以下操作:resetpass、resetkeys、resetchannels、allchannels(如果设置了acl-pubsub-default)、关闭、clearselectors、-@all。用户会回到创建后立即的状态。
更多 Redis 关于 ACL 相关知识请参考其官网(https://redis.io/docs/latest/operate/oss_and_stack/management/security/acl/)
Redis ACL 命令
描述:本小节将主要针对 Redis 数据库 ACL 相关子命令进行介绍与实践,帮助各位看有快速掌握其使用。
ACL 命令介绍
127.0.0.1:6379> acl help
# 语法
0) ACL <subcommand> [<arg> [value] [opt] ...]. Subcommands are:
# 子命令
1) CAT [<category>] 列出属于<category>的所有命令,或者在未指定类别的情况下列出所有命令类别。
2) DELUSER <username> [<username> ...] 删除一个或多个用户
3) DRYRUN <username> <command> [<arg> ...] 验证返回用户是否可以在不执行命令的情况下执行给定的命令
4) GETUSER <username> 查询指定用户的ACL账户及权限配置信息
5) GENPASS [<bits>] 生成安全的256位用户密码,“bits”参数可用于指定不同的大小。
6) LIST 显示全部的用户及acl规则
7) LOAD 从acl文件中重载acl规则
8) LOG [<count> | RESET] 显示acl命中的日志
9) SAVE 保存当前acl规则到acl文件
10) SETUSER <username> <attribute> [<attribute> ...] 创建用户及其操作权限
11) USERS 显示所有acl用户
12) WHOAMI 返回当前连接用户
13) HELP 获取帮助
ACL 自定义类型
目前共有29个不同的类别,
127.0.0.1:6379> acl cat
1) "keyspace" # 以与类型无关的方式写入或读取键、数据库或其元数据。包括DEL、RESTORE、DUMP、重命名、EXITS、DBSIZE、KEYS、SCAN、EXPIRE、TTL、FLUSHALL等。
2) "read" # 从键(值或元数据)读取。
3) "write" # 写入键(值或元数据)。
4) "set" # 所有与set相关的命令
5) "sortedset" # 所有排序集相关命令。
6) "list" # 所有与列表相关的命令。
7) "hash" # 所有哈希相关命令。
8) "string" # 所有与字符串相关的命令。
9) "array" # 所有与数组相关的命令。
10) "bitmap" # 所有位图相关命令。
11) "hyperloglog" # 所有与 hyperloglog 相关的命令。
12) "geo" # 所有与地理空间索引相关的命令。
13) "stream" # 所有与流相关的命令。
14) "pubsub" # 所有与pubsub相关的命令
15) "admin" # 超级管理员角色求安心。
16) "fast" # 循环变量数,但不循环键中元素数。
17) "slow" # 所有不快速的命令
18) "blocking" # 可能会阻塞连接,直到被另一个命令释放。
19) "dangerous" # 潜在危险命令,包括 FLUSHALL、MIGRATE、RESTORE、SORT、KEYS、CLIENT、DEBUG、INFO、CONFIG、SAVE、REPLICAOF 等。
20) "connection" # 影响连接或其他连接的命令。这包括 AUTH、SELECT、COMMAND、CLIENT、ECHO、PING 等。
21) "transaction" # 包含 WATCH/MULTI/EXEC 相关命令。
22) "scripting" # 脚本相关。
23) "bloom" # 所有与 Bloom filter 相关的命令。
24) "cuckoo" # 所有与 Cuckoo 过滤器相关的命令。
25) "cms" # count-min 草图相关命令。
26) "topk" # 数据类型:所有与top-k相关的命令。
27) "tdigest" # 数据类型:所有与t-digest相关的命令。
28) "search" # 只有访问索引创建期间定义的键前缀超集的ACL用户才能创建、修改或读取索引。
29) "timeseries" # 数据类型:所有与时间序列相关的命令。
30) "json" # 所有与JSON相关的命令。
ACL 命令示例
# 创建管理员用户(所有权限)
ACL SETUSER admin on >admin@123456 ~* &* +@all
# 创建应用用户(指定权限)
ACL SETUSER app on >app@123456 ~app:* &* +PING +GET +SET +SETEX +DEL +SELECT +EXISTS +EXPIRE +TTL +PERSIST +HGET +HSET +HDEL +HGETALL +HEXISTS +LPUSH +RPUSH +LPOP +RPOP +LRANGE +LLEN +SADD +SREM +SMEMBERS +SISMEMBER +ZADD +ZREM +ZRANGE +ZSCORE +INCR +DECR +INCRBY +SCAN +TYPE -FLUSHDB -FLUSHALL -KEYS -CONFIG -SHUTDOWN -DEBUG -MONITOR
# 使用命令行方式指定账号密码
redis-cli -a admin@123456 --user admin
# 或者交互执行的方式
redis-cli
127.0.0.1:6379> auth admin admin@123456
# 查看当前用户
acl whoami
"admin"
# 查看所有用户
acl users
1) "admin"
2) "app"
3) "default"
4) "reader"
5) "tester"
# 查看指定用户详细配置
acl getuser tester
1) "flags"
2) 1) "on"
2) "sanitize-payload"
3) "passwords"
4) 1) "672406e39f9da848f39ae8a80c32a7202f8dd3e227c4cafc00c112abe6df25f4"
5) "commands"
6) "-@all +@connection +ping +set +get +del +expire +ttl +pttl +exists +scan +type +strlen"
7) "keys"
8) "~test:*"
9) "channels"
10) "&*"
11) "selectors"
12) (empty array)
# 列举全部用户权限详细
acl list
1) "user admin on sanitize-payload #ff4bc8171792d0ac596bc8b061e7bbb492d2a56894cf176d24fd0d16d292bbe0 ~* &* +@all"
2) "user app on sanitize-payload #5cb152c8ea1c0fd760c1f9b79e9227bb822ec1da5ccc44779041016d7512ae1f ~app:* &* +@all +ping +get +set +setex +del +select +exists +expire +ttl +persist +hget +hset +hdel +hgetall +hexists +lpush +rpush +lpop +rpop +lrange +llen +sadd +srem +smembers +sismember +zadd +zrem +zrange +zscore +incr +decr +incrby +scan +type -flushdb -flushall -keys -config -shutdown -debug -monitor"
3) "user default off nopass sanitize-payload ~* &* +@all"
4) "user reader on sanitize-payload #e2218530523eb5f51366a7fc14001528c6bfe7397a7ce0a1717226c8524c99c9 ~cache:* ~test:* &* -@all +@connection +ping +get +exists +ttl +pttl +scan +type +strlen"
5) "user tester on sanitize-payload #672406e39f9da848f39ae8a80c32a7202f8dd3e227c4cafc00c112abe6df25f4 ~test:* &* -@all +@connection +ping +set +get +del +expire +ttl +pttl +exists +scan +type +strlen"
# 使用管理员账户模拟指定用户执行某条命令
# 模拟执行 SET(无权限,返回拒绝信息)
127.0.0.1:6379> ACL DRYRUN tester SET reader:value bar
"User reader has no permissions to run the 'set' command"
# 模拟执行 GET(无权限,返回拒绝信息)
127.0.0.1:6379> ACL DRYRUN tester GET test:data
OK
# 生成一个 128 、256 bit 位的字符串
127.0.0.1:6379> acl GENPASS 256
"d1683067d4b595b3f5c2ebca108cf7592fea75c2dbad8710ba69660980a23100"
127.0.0.1:6379> acl GENPASS 128
"e24b059003c8f136fc2b277f438eb865"
# 显示两条 acl 日志
127.0.0.1:6379> acl log 2
1) 1) "count"
2) (integer) 2
3) "reason"
4) "key"
5) "context"
6) "toplevel"
7) "object"
8) "admin:config"
9) "username"
2) "app"
3) "age-seconds"
4) "67860.008"
5) "client-info"
6) "id=3215 addr=10.20.176.103:61727 laddr=172.24.0.2:6379 fd=21 name=10.20.176.210-app age=6253 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 watch=0 qbuf=33 qbuf-free=20441 argv-mem=16 multi-mem=0 rbs=1024 rbp=40 obl=0 oll=0 omem=0 omem-shared=0 omem-unshared=0 tot-mem=22816 events=r cmd=type user=app redir=-1 resp=2 lib-name= lib-ver= io-thread=0 tot-net-in=10621 tot-net-out=73126 tot-cmds=661 read-events=674 avg-pipeline-len-sum=674 avg-pipeline-len-cnt=674"
7) "entry-id"
8) (integer) 117
9) "timestamp-created"
10) (integer) 1789462767155
11) "timestamp-last-updated"
12) (integer) 1789462767935
# 保存规则
127.0.0.1:6379> acl save
OK
# 手动修改acl文件加载规则(通常在DB迁移中使用)
127.0.0.1:6379> acl load
OK
ACL 命令实践
# 仅创建用户
# 缺省由于处于 off 状态因此不能执行 AUTH 且无设置设置密码,无法访问任何命令,用户无法访问任何密钥模式,用户无法访问任何发布/订阅频道。
> ACL SETUSER weiyigeek
OK
> ACL LIST
1) "user default on nopass ~* &* +@all"
2) "user weiyigeek off sanitize-payload resetchannels -@all"
# 启用 weiyigeek 用户并设置密码,仅用GET命令即可访问 对以字符串“test:”开头的键名。
ACL SETUSER weiyigeek on >password ~test:* ~cache:* +get
# 测试用户对 test: 开头的键有读权限,而无写权限
127.0.0.1:6379> ACL DRYRUN weiyigeek get test:data
OK
127.0.0.1:6379> ACL DRYRUN weiyigeek set test:weiyigeek value
"User weiyigeek has no permissions to run the 'set' command"
# 允许用户执行 acl whoami 命令
acl setuser weiyigeek +acl|whoami
OK
# 切换到weiyigeek用户,验证用户是否仅可执行 acl whoami 命令
auth weiyigeek password
127.0.0.1:6379> acl whoami
"weiyigeek"
127.0.0.1:6379> acl getuser
(error) ERR wrong number of arguments for 'acl|getuser' command
# 读取testL键以及尝试写键
127.0.0.1:6379> get test:data
"test_value"
127.0.0.1:6379> set test:id 1000 ex 60
(error) NOPERM User weiyigeek has no permissions to run the 'set' command
# 返回到 admin 用户
127.0.0.1:6379> auth admin admin@123456
OK
# 查看指定用户相关信息及acl规则
ACL GETUSER weiyigeek
1) "flags"
2) 1) "on"
2) "sanitize-payload"
3) "passwords"
4) 1) "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
5) "commands"
6) "-@all +get"
7) "keys"
8) "~cached:* ~test:*"
9) "channels"
10) ""
11) "selectors"
12) (empty array)
# 移除weiyigeek 对 cache:键前缀的操作
ACL SETUSER weiyigeek on >password ~cache:* resetkeys ~test:*
127.0.0.1:6379> ACL GETUSER weiyigeek
7) "keys"
8) "~test:*" # 再次执行,发现已经移除 ~cached:*
# 建议使用 ACL GENPASS 命令为用户生成密码 32 字节 256 位伪随机字符串
acl genpass
"e7bee8bfda7dc661ee9464da106745916855effc77898eaa370507fe61347fa0"
# 创建一个 tenant1 用户赋予全部权限,但不能执行 -flushall -flushdb -swapdb 以及危险组中的命令
ACL SETUSER tenant1 on >strong-password ~tenant1:* +@all -@dangerous -flushall -flushdb -swapdb
另外,使用上面创建的 tester 用户手动模拟一个分布式锁的基本流程
# 切换用户
127.0.0.1:6379> auth tester tester@123456
# 创建一个互斥锁键
- # `NX`:只在 key 不存在时才设置成功(保证互斥,谁先抢到谁持有锁)
- # `EX 30`:设置 30 秒过期时间(防止持有者宕机后锁永远不释放,形成死锁)。
127.0.0.1:6379> SET test:lock1 "value" NX EX 30
OK
# 在 30s 内再次执行显示 nil 表示锁存在。
127.0.0.1:6379> SET test:lock1 "lock_value" NX EX 30
(nil)
# 查看键值
127.0.0.1:6379> get test:lock1
"value" # 返回 `"value"`,确认锁存在,且 value 就是当时写入的
# 删除键值
127.0.0.1:6379> DEL test:lock1
(integer) 1 # 返回 `(integer) 1`,表示成功删除了 1 个 key,即释放锁。
温馨提示:在应用中设计使用分布式锁时,需注意用 DEL 直接解锁是不安全的,正确做法:value 用唯一标识 + Lua 原子校验删除,这样即使锁已经过期被别人拿走,A 也不会误删 B 的锁,例如:
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end" 1 test:lock1 "<uuid-of-client-A>"
温馨提示:Redis 中执行命令是不区分大小写的但是,键是区分大小的,这一点需要特别注意。
127.0.0.1:6379> set Test 123456 EX 100
OK
127.0.0.1:6379> set TEST weiyige.top EX 100
OK
温馨提示:对于 Sentinel 和 replicas ACL 权限说明
# Sentinel,允许用户在主实例和副本实例中访问以下命令:
- AUTH, CLIENT, SUBSCRIBE, SCRIPT, PUBLISH, PING, INFO, MULTI, SLAVEOF, CONFIG, CLIENT, EXEC.
ACL SETUSER sentinel-user on >somepassword allchannels +multi +slaveof +ping +exec +subscribe +config|rewrite +role +publish +info +client|setname +client|kill +script|kill
# Replicas,在主实例上允许以下命令,无需访问密钥,因此这可转化为以下规则:
- PSYNC, REPLCONF, PING
ACL setuser replica-user on >somepassword +psync +replconf +ping
部署实践
1.Docker 容器中部署 Redis 8.10.x 并启用 ACL 功能
描述:对于 Docker 环境安装作者不再累述,不会的童鞋可参考作者之前所写的 Docker 系列文章(公众号、知识星球:全栈工程师修炼指南),当然也可直接参考 Docker 官网文档。
操作步骤
步骤 01.创建 docker-compose 部署文件目录,和持久化目录及配置文件、日志目录。
# 创建持久化目录及配置文件、日志目录
mkdir -p /storage/dev/database/redis/new/{deploy,config,data,logs}
步骤 02.创建 Redis 配置文件,启用 ACL 权限管理。
tee /storage/dev/database/redis/new/config/redis.conf <<'EOF'
# 绑定任意接口、服务端口
bind 127.0.0.1 redis-new
port 6379
# redis服务pid进程文件名
pidfile "/var/run/redis.pid"
# 关闭守护进程(容器里必须设置为no)
daemonize no
supervised auto
# 关闭保护模式(容器必须设置为no)
protected-mode no
# redis服务pid进程文件名
pidfile "/var/run/redis.pid"
# 启用 ACL 权限管理
aclfile /etc/redis/acl/users.acl
# 配置默认用户使用密码访问(启用 acl 则会覆盖 requirepass 优先级)
# requirepass Redis8101@2026
# 数据文件保存路径,rdb/AOF文件也保存在这里
dir "/data"
# 日志文件记录文件及级别
logfile "/logs/redis.log"
loglevel notice
# 慢查询配置
slowlog-log-slower-than 6000
slowlog-max-len 1024
# 内存策略(可在配置文件中设置,也可以在运行时通过环境变量注入)
# maxmemory 2gb
# maxmemory-policy volatile-lru
# 最大客户端连接数
maxclients 10000
# 客户端连接空闲多久后断开连接,单位秒,0表示禁用
timeout 120
tcp-keepalive 120
# TCP Backlog 设置,应对高并发
tcp-backlog 511
# Redis 数据持久化混合模式(RDB/AOF)配置
# RDB 文件名
dbfilename "dump.rdb"
# 数据自动保存脚本条件, 例如900s中有10key发生变化
save 900 1
save 300 10
save 60 10000
# 启用增量式同步 (推荐),减少 RDB 保存期间的延迟毛刺
rdb-save-incremental-fsync yes
# 对RDB文件进行压缩,建议以(磁盘)空间换(CPU)时间。
rdbcompression yes
# 版本5的RDB有一个CRC64算法的校验和放在了文件的最后。这将使文件格式更加可靠。
rdbchecksum yes
# AOF开启
appendonly yes
# AOF文件名
appendfilename "appendonly.aof"
# 可选值 always, everysec,no,建议设置为everysec
appendfsync everysec
# 混合持久化,重写时使用RDB格式作为AOF开头,减少文件大小
aof-use-rdb-preamble yes
# 7.0+版本特性,除非需要否则关闭时间戳记录
aof-timestamp-enabled no
# 当前AOF文件比上次重写后文件大100%时触发重写
auto-aof-rewrite-percentage 100
# AOF文件至少达到64MB才会触发重写
auto-aof-rewrite-min-size 64mb
# 重写时增量式同步,避免大延迟峰值
aof-rewrite-incremental-fsync ye
# Redis 风险命令重命名
rename-command CONFIG RCONFIG
rename-command FLUSHDB b840fc02d524045429941cc15f59e41cb7be6c53
rename-command FLUSHALL b840fc02d524045429941cc15f59e41cb7be6c54
rename-command EVAL b840fc02d524045429941cc15f59e41cb7be6c55
rename-command DEBUG b840fc02d524045429941cc15f59e41cb7be6c56
rename-command SHUTDOWN RSHUTDOWN
EOF
温馨提示:上述文档中 allkeys-lru 和 volatile-lru 都是 Redis 的内存淘汰策略,核心区别在于候选键的范围不同,简单来说规 allkeys-* 作用于全部键,volatile-* 只作用于带 TTL 的键。
allkeys-lru:从所有键中淘汰最近最少使用的键。
volatile-lru:从设置了过期时间的键中淘汰最近最少使用的键。
步骤 03.创建 acl 文件以及设置相关文件权限,特别注意:容器中尽量不要以 root 用户运行,以防权限过大造成安全问题。
touch /storage/dev/database/redis/new/config/acl/users.acl
chmod 700 /storage/dev/database/redis/new/config/acl/
chmod 700 /storage/dev/database/redis/new/config/redis.conf
# Redis 官方容器镜像 redis 用户默认id/gid 为 999:999,此处将持久化目录的所有权设置为该用户
chown -R 999:999 /storage/dev/database/redis/new/
步骤 04.创建 docker-compose 文件,并启动 Redis 服务。
tee /storage/dev/database/redis/new/deploy/docker-compose.yml <<'EOF'
services:
redis:
image: redis:8.10.1-alpine3.23
container_name: redis-new
hostname: redis-new
user: "999:999"
ports:
- "6378:6379"
command: redis-server /etc/redis/redis.conf
volumes:
- /storage/dev/database/redis/new/data:/data
- /storage/dev/database/redis/new/logs:/logs
- /storage/dev/database/redis/new/config/redis.conf:/etc/redis/redis.conf
- /storage/dev/database/redis/new/config/acl/:/etc/redis/acl/
environment:
- REDIS_MAXMEMORY=4gb
- REDIS_MAXMEMORY_POLICY=volatile-lru
- REDIS_DISABLE_THP=yes
restart: unless-stopped
deploy:
resources:
limits:
memory: "4G"
EOF
# 创建并启动容器
docker-compose -f /storage/dev/database/redis/new/deploy/docker-compose.yml up -d
# 查看容器状态
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e37860d37122 redis:8.10.1-alpine3.23 "docker-entrypoint.s…" 5 days ago Up 5 days 0.0.0.0:6379->6379/tcp redis-new
步骤 05.创建 ACL 初始化脚本,依次执行下述命令
#!/bin/bash
# 初始化ACL规则文件
# 进入到容器内执行命令
docker exec -it redis-new redis-cli
127.0.0.1:6379> ping
# 创建管理员用户(运行操作全局)
ACL SETUSER admin on >admin@123456 ~* &* +@all
# 创建应用用户,仅限app:前缀的Key,允许常规操作(生产环境可用,当然也可对接开发确定到底使用了哪些命令)
ACL SETUSER app on >app@123456 ~app:* &* +PING +GET +SET +SETEX +DEL +SELECT +EXISTS +EXPIRE +TTL +PERSIST +HGET +HSET +HDEL +HGETALL +HEXISTS +LPUSH +RPUSH +LPOP +RPOP +LRANGE +LLEN +SADD +SREM +SMEMBERS +SISMEMBER +ZADD +ZREM +ZRANGE +ZSCORE +INCR +DECR +INCRBY +SCAN +TYPE -FLUSHDB -FLUSHALL -KEYS -CONFIG -SHUTDOWN -DEBUG -MONITOR
# 创建测试用户,仅限test:*前缀的Key,仅允许特定命令
ACL SETUSER tester on >tester@123456 ~test:* &* +PING +SET +GET +DEL +EXPIRE +TTL +PTTL +EXISTS +SCAN +TYPE +STRLEN
# 创建只读用户,对 cache:* 和 test:*前缀的Key
ACL SETUSER reader on >reader@123456 ~cache:* ~test:* &* +PING +GET +EXISTS +TTL +PTTL +SCAN +TYPE +STRLEN
# 禁用默认用户(可选)
ACL SETUSER default off
# 查看所有用户及ACL列表权限
ACL USERS
1) "admin"
2) "app"
3) "default"
4) "reader"
5) "tester"
ACL LIST
1) "user admin on sanitize-payload #ff4bc8171792d0ac596bc8b061e7bbb492d2a56894cf176d24fd0d16d292bbe0 ~* &* +@all"
2) "user app on sanitize-payload #5cb152c8ea1c0fd760c1f9b79e9227bb822ec1da5ccc44779041016d7512ae1f ~app:* &* +ping +get +set +setex +del +select +exists +expire +ttl +persist +hget +hset +hdel +hgetall +hexists +lpush +rpush +lpop +rpop +lrange +llen +sadd +srem +smembers +sismember +zadd +zrem +zrange +zscore +incr +decr +incrby +scan +type -flushdb -flushall -keys -config -shutdown -debug -monitor"
3) "user default off nopass sanitize-payload ~* &* +@all"
4) "user reader on sanitize-payload #e2218530523eb5f51366a7fc14001528c6bfe7397a7ce0a1717226c8524c99c9 ~cache:* ~test:* &* -@all +ping +get +exists +ttl +pttl +scan +type +strlen"
5) "user tester on sanitize-payload #672406e39f9da848f39ae8a80c32a7202f8dd3e227c4cafc00c112abe6df25f4 ~test:* &* -@all +@connection +ping +set +get +del +expire +ttl +pttl +exists +scan +type +strlen"
# 持久化ACL规则
ACL SAVE
步骤 06.测试 ACL 规则,由于 default 用户已被禁用,我们无法直接使用默认密码登录,后续需使用 admin 用户进行 redis 的管理。
# 上述创建的相关账户
echo "管理员用户: admin/admin123456"
echo "应用用户: admin/admin123456"
echo "测试用户: tester/tester123456"
echo "只读用户: reader/reader123456"
# 以 admin 创建几个不同前缀的键
AUTH admin admin@123456
ping
select 0 # 选择数据库db0 (缺省)
SET admin:config "production"
SET app:data "app_value"
SET test:data "test_value"
SET cache:user "cached_user"
# 查看创建的所有键
keys *
步骤 07.以 reader 登录数据库并测试读取有权限及无权限键。
# 执行客户端命令
redis-cli
# 使用reader用户认证
AUTH reader reader@123456
# 查看db中所有键,由于没有赋予 keys 命令权限,所以执行后报权限不足。
127.0.0.1:6379> keys *
(error) NOPERM User reader has no permissions to run the 'keys' command
# 使用SCAN命令扫描db库中的所有键
SCAN 0 MATCH * COUNT 100
1) "0"
2) 1) "cache:user"
3) "test:data"
4) "app:data"
5) "admin:config"
# 查询 test 前缀
127.0.0.1:6379> GET test:data
"test_value"
# 查询 app 前缀键,由于没权限则报权限不足。
127.0.0.1:6379> GET app:data
(error) NOPERM No permissions to access a key
温馨提示:若赋予 SCAN 命令权限给用户,可使用该命令查看到其它用户的键名,所以在开发时请勿将敏感数据作为键名。
步骤 08.最后使用图形化客户端连接数据库进行增删改查,以 app 用户为例。
至此,在 Docker 环境下部署最新 Redis 8.10.1 版本实践结束。
入坑出坑
1.执行ACL SAVE命令报 "Permission denied" 临时文件创建失败
问题问题:启用配置 ACL 规则后执行 ACL SAVE 持久化命令报 Opening temp ACL file for ACL SAVE: Permission denied 无法保存 ACL 规则。
问题原因:由于 Redis ACL SAVE 命令执行逻辑导致的,在保存时redis不会直接覆盖原acl文件,而是先在aclfile 所在目录下创建临时文件(如:.users.acl.tmp), 再将当前ACL规则写入临时文件后,再重命名替换原文件,若aclfile所在目录无写权限,第一步就会失败。
错误配置:将aclfile配置为/etc/redis/acl/users.acl,并通过Docker挂载单个文件, 而Docker镜像中/etc/redis/目录默认归root所有,Redis以redis用户运行,自然无法创建临时文件。
volumes:
- ./acl/users.acl:/etc/redis/acl/users.acl
解决办法:利用Redis镜像预设权限,目录挂载替代文件挂载,Redis官方Docker镜像对/data目录做了特殊优化——默认归属redis用户,自带读写权限,这是天然的"安全目录",但是作者建议是将 acl 文件单独放置在一个目录中,例如 /etc/redis/acl/,操作完毕后,重启启动容器后执行 ACL SAVE,返回OK即成功。
# 创建 acl 目录及acl文件
mkdir -vp /storage/dev/database/redis/new/config/acl/
touch /storage/dev/database/redis/new/config/acl/users.acl
# 权限赋予 Redis 官方镜像 UID 为 999
chown -R 999:999 /storage/dev/database/redis/new/config/acl/
chown 999:999 /storage/dev/database/redis/new/config/acl/users.acl
chmod 700 /storage/dev/database/redis/new/config/acl/users.acl
# 挂载示例
volumes:
- /storage/dev/database/redis/new/data:/data
- /storage/dev/database/redis/new/logs:/logs
- /storage/dev/database/redis/new/config/redis.conf:/etc/redis/redis.conf
- /storage/dev/database/redis/new/config/acl/:/etc/redis/acl/
特别注意:Docker 容器内部挂载的权限继承规则,对于目录挂载容器内目录的权限会继承本机目录的权限,对于文件挂载仅文件本身有权限,所在目录权限仍为容器镜像默认。
2.执行 KEYS 命令报 "NOPERM" 命令权限未开放
问题描述:使用test用户登录后,执行KEYS test:*查询Key时报 dockerfile NOPERM User test has no permissions to run the 'keys' command 错误。
问题原因:Redis ACL默认遵循"最小权限原则" 用户仅拥有明确开放的权限,我最初给test配置的 ACL 规则是 -@all +ping +set +get +del +expire +ttl +pttl +exists +scan +type +strlen 未指定未开放KEYS或SCAN命令,自然无法执行查询。
解决办法:按需开放命令,兼顾功能与安全,通常情况下需和开发对接到底需要哪些权限,但是尽量按照最小权限原则补全规则,对于确实需要 keys 命令的用户可在 acl 权限列表中赋予。
# 配置test用户权限(Redis 6.2+需要添加&*)
ACL SETUSER test on >test123456 ~test:* &* +PING +SET +GET +DEL +EXPIRE +TTL +SCAN
# 查看用户权限
ACL GETUSER test
# 将规则写入 users.acl 避免重启丢失
ACL SAVE
命令说明:
~test:*:仅允许操作该前缀的Key
&*:允许所有发布订阅通道(Redis 6.2+需要)
+SET/+GET/+DEL/+EXPIRE/+TTL:分布式锁核心命令
+SCAN:迭代查询Key,避免KEYS的阻塞问题
+@connection:基础连接命令组(如AUTH、PING、QUIT等),经实践测试 Redis 8.10.x 中默认自带无需使用该标识。
重要提示:自 Redis 6.2+版本需要显式配置&*来允许发布订阅ACL规则对已存在的连接不立即生效,需要重新连接修改默认用户(default)要谨慎,避免锁死系统,一定要先创建管理权限。
温馨提示:若文章代码块中存在乱码或不能复制,请联系作者,也可通过文末的阅读原文链接,加入知识星球中阅读,原文链接:https://articles.zsxq.com/id_ai83utl7knj8.html
学习:作者知识星球
『 全栈工程师修炼指南』星球,主要涉及全栈工程师(Full Stack Development)实践文章,持续更新包括但不限于企业SecDevOps和网络安全等保合规、安全渗透测试、编程开发、云原生(Cloud Native)、物联网工业控制(IOT)、人工智能Ai,从业书籍笔记,人生职场认识等方面资料或文章。
170