fix: persist provisioned Redis ACL users

This commit is contained in:
lunanting 2026-09-12 17:29:24 +08:00
parent 4f48a20670
commit e1772917ea
3 changed files with 8 additions and 4 deletions

View File

@ -13,4 +13,4 @@
SSH 隧道账号只接受由注册服务 CA 签发的短期证书,只允许转发到服务器 `127.0.0.1:3307`,并关闭终端、TTY、代理转发和密码认证。生产主机的 SSH 管理账号使用独立密钥,关闭密码登录。 SSH 隧道账号只接受由注册服务 CA 签发的短期证书,只允许转发到服务器 `127.0.0.1:3307`,并关闭终端、TTY、代理转发和密码认证。生产主机的 SSH 管理账号使用独立密钥,关闭密码登录。
Redis 原始端口不映射到宿主机。公网 `6380` 只接受 TLS,注册服务为每台设备创建独立 ACL 账号;停用安装会删除该账号。证书文件应归属 HAProxy 容器的 `99:99` 并保持 `0600`,续期后需重新生成 `/opt/cangku/shared/redis-tls.pem`、恢复该属主并重启 TLS 网关。 Redis 原始端口不映射到宿主机。公网 `6380` 只接受 TLS,注册服务为每台设备创建独立 ACL 账号;停用安装会删除该账号。ACL 保存在 Redis 数据卷的 `users.acl`,注册与撤销后立即执行 `ACL SAVE`,容器重启不会丢失设备账号。证书文件应归属 HAProxy 容器的 `99:99` 并保持 `0600`,续期后需重新生成 `/opt/cangku/shared/redis-tls.pem`、恢复该属主并重启 TLS 网关。

View File

@ -31,7 +31,7 @@ services:
restart: unless-stopped restart: unless-stopped
env_file: env_file:
- /opt/cangku/shared/redis.env - /opt/cangku/shared/redis.env
command: ["sh", "-c", "exec redis-server --appendonly yes --save 60 1000 --requirepass \"$$REDIS_PASSWORD\""] command: ["sh", "-c", "if [ ! -s /data/users.acl ]; then printf 'user default on >%s ~* +@all\\n' \"$$REDIS_PASSWORD\" > /data/users.acl; fi; exec redis-server --appendonly yes --save 60 1000 --aclfile /data/users.acl"]
volumes: volumes:
- /opt/cangku/data/redis:/data - /opt/cangku/data/redis:/data
healthcheck: healthcheck:

View File

@ -188,18 +188,22 @@ class Broker:
def _create_redis_user(self, username, password): def _create_redis_user(self, username, password):
if not re.fullmatch(r'cangku_[a-f0-9]{16}', username): if not re.fullmatch(r'cangku_[a-f0-9]{16}', username):
raise ValueError('Refusing to create an unexpected Redis username') raise ValueError('Refusing to create an unexpected Redis username')
self._redis_connection().execute_command( client = self._redis_connection()
client.execute_command(
'ACL', 'SETUSER', username, 'ACL', 'SETUSER', username,
'reset', 'on', f'>{password}', '~*', 'reset', 'on', f'>{password}', '~*',
'+ping', '+get', '+set', '+setex', '+del', '+unlink', '+exists', '+ping', '+get', '+set', '+setex', '+del', '+unlink', '+exists',
'+expire', '+ttl', '+scan', '+eval', '+quit', '+expire', '+ttl', '+scan', '+eval', '+quit',
'+client|setname', '+client|setinfo', '+client|setname', '+client|setinfo',
) )
client.execute_command('ACL', 'SAVE')
def _drop_redis_user(self, username): def _drop_redis_user(self, username):
if not username or not re.fullmatch(r'cangku_[a-f0-9]{16}', username): if not username or not re.fullmatch(r'cangku_[a-f0-9]{16}', username):
return return
self._redis_connection().execute_command('ACL', 'DELUSER', username) client = self._redis_connection()
client.execute_command('ACL', 'DELUSER', username)
client.execute_command('ACL', 'SAVE')
def _create_application_admin(self, username, password, label): def _create_application_admin(self, username, password, label):
password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12)).decode() password_hash = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12)).decode()