• 正文
  • 相关推荐
申请入驻 产业图谱

一次 RCE 漏洞,一场升级:Redis 8.10.x ACL 权限精细化实践手记

21小时前
170
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

感谢各位看友来访,更多最新技术专栏文章尽在作者『 全栈工程师修炼指南』星球,加入一起持续学习吧!

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,从业书籍笔记,人生职场认识等方面资料或文章。

相关推荐