Files

70 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 可选只读模式
MySQL 新建及默认配置为安全读写,MongoDB、Redis、RabbitMQ 默认读写。这四种协议支持账号只读模式;RabbitMQ 为严格只读,禁止消费和确认。SSH、PostgreSQL 等协议不提供只读选项。切换前必须停止并断开现有连接,再启动使用新策略。
账号访问模式按协议提供:MySQL 为只读、读写、全放行,MongoDB、Redis、RabbitMQ 为只读、读写;PostgreSQL、SQL Server、HTTP、HTTPS 仅提供读写。2026-10-15 先修复了无协议策略却错误显示“只读”选项的问题,随后补齐 Redis 和 RabbitMQ 的实际拦截才重新开放选项。已有草稿若含不受支持的账号模式,保存前在对应账号字段提示重新选择,不自动将只读改成读写;服务端返回同类错误时显示中文原因与操作指引。无配置或数据迁移,需重新构建并重启后生效。
实际验证:先用新增回归复现错误选项、漏校验和英文提示,修复后访问模式/草稿 Node 测试 17 项通过。前端 TypeScript/Vite 构建通过,恢复 `web/dist/.gitkeep`;Go 1.27.1 新嵌入程序为 `bin/dev-build/redis-access-mode/linkport.exe`。该程序使用 18901/18902 端口和临时 SQLite 的 Playwright 专项 16 项通过(46.0 秒),覆盖 Redis 界面只提供读写、真实 POST 返回 201 并回读 `readwrite`、MySQL 只读账号保存及空密码保留、React 与 UI domain 回归。未连接真实 Redis/MySQL/MongoDB 服务器或修改真实配置。
全量 Playwright 发现 174 项,以 `--max-failures=1 --timeout=15000` 运行后,在既有 account-editor 的 `.target-card / 配置目标` 定位器超时处停止,173 项未运行,不记为全套通过。后端源码未变,未重复本会话已完成的 Go 全量和 vet;先前全量的 cluster MySQL fixture 空指针、pgproxy 身份隔离 fixture 间歇失败仍保留记录。构建与浏览器日志位于 `bin/dev-build/redis-access-mode/`;本次未部署或重启现有实例。
只读是在协议网关对请求执行白名单,并不是目标数据库账号的权限。无法解析或不在白名单的请求不会转发上游;已允许的视图、服务器扩展内部仍可能有副作用。需要数据库级绝对写权限隔离时,应同时使用目标数据库的只读账号。
## MySQL
MySQL 账号可选择只读、安全读写(默认)和普通读写。安全读写拒绝没有外层 WHERE 的 UPDATE/DELETE,普通读写不检查 WHERE,由上游控制 SQL 权限,详见 [MySQL 账号模式](mysql-access-modes.md)和[写入保护](mysql-write-safety.md)。只读模式继续拒绝所有写操作,网关级只读限制优先于账号设置。只读账号允许 STATISTICS、PROCESS INFO、复制注册和 Binlog 读取,复制权限仍由上游验证。
使用 AST 递归检查 SELECT、CTE、UNION、子查询、表达式和函数,允许计划中的常见 SHOW、DESCRIBE、普通 EXPLAIN SELECT 和有限会话/事务控制。包括 SHOW TABLE STATUS(可带数据库、LIKE、WHERE)、SHOW [FULL] PROCESSLIST、SHOW VARIABLES LIKE 和 information_schema.ENGINES 查询。
监控查询支持 `FROM_UNIXTIME`、`CHAR`(含 `USING` 字符集)和 `SUBSTRING_INDEX`,用于时间范围过滤及 `information_schema.USER_PRIVILEGES` 的当前账号匹配。普通 `EXPLAIN SELECT` 和 `EXPLAIN FORMAT=TRADITIONAL SELECT` 对内部查询执行相同递归检查;`EXPLAIN ANALYZE` 仍拒绝。允许的函数参数中包含未知函数、有副作用的函数或用户变量赋值时仍拒绝,带数据库限定名的自定义函数不因名称相同而放行。
此兼容调整无需数据迁移;现有运行实例需更新程序并重启后生效。
历史慢 SQL 监控另支持 `UNIX_TIMESTAMP`、`TIME_TO_SEC`、`SEC_TO_TIME`、`LEFT`,并检查其全部参数;允许 `CONVERT(... USING utf8mb4)` 与 `CAST(... AS CHAR)` 组合。放行函数不代表日志表具有查询索引,监控应用仍需单独检查执行计划,CSV 慢日志源仍可能无法安全读取。
允许最多 100 条符合上述策略的批量语句,先检查整个批次,再在同一目标会话逐条执行并返回多个结果集;批量写入不支持,读写模式仍允许单条写入。数据库运行错误发生后停止剩余语句,已执行语句不会自动回滚。
拒绝写入、DDL、管理操作、CALL、动态 PREPARE/EXECUTE、可执行版本注释、SELECT INTO、锁定读取、用户变量和未知函数。二进制预编译在 prepare 时审批,后续仅允许该连接中已批准的 statement ID,重置后清空。
为防止字符集和 SQL 模式导致网关解析结果与上游执行不同,只读连接固定解析兼容的 SQL 模式;SET NAMES 仅允许 utf8mb4、utf8、utf8mb3、ascii。解析器不支持的有效 MySQL 语法也会被拒绝。`SET character_set_results` 允许 NULL、DEFAULT 及相同静态字符集白名单,只影响返回编码,不改变后续 SQL 解析;其他字符集设置仍拒绝。
## MongoDB
按 BSON 命令、查询操作符、聚合阶段和表达式上下文递归检查,拒绝未知操作、重复字段及超深结构。允许的读取包括 find、count、distinct、aggregate、列表、游标和有限元数据读取。
嵌套 `$lookup`、`$facet`、`$unionWith` 同样检查;拒绝 `$out`、`$merge`、`$where`、`$function`、`$accumulator`、mapReduce、写入、DDL 和权限管理。`$literal` 内按数据处理。
只读模式不支持多文档事务,普通逻辑会话可用;getMore、killCursors 只能操作同一客户端连接中登记的游标。连接池把游标迁到其他连接时可能被拒绝,调用方需适配此限制。
完整设计白名单见 [开发计划](development-plan.md#4-可选只读模式),实际策略位于 `internal/protocol/mysqlproxy/policy.go` 和 `internal/protocol/mongoproxy/policy.go`,由测试覆盖。白名单不支持通过管理界面扩展。
## Redis
只读策略在单节点、Sentinel/复制解析后的连接和 Cluster 路由前执行同一命令白名单。允许常用字符串、哈希、列表、集合、有序集合读取、SCAN、TTL、流读取及有限元数据查询。AUTH/HELLO 继续映射网关凭据,SELECT 和有限 CLIENT 会话信息命令可用。
拒绝写入、发布、脚本(含 EVAL_RO)、事务、模块/未知命令、订阅推送、CLIENT TRACKING/REPLY、带写副作用的读取变体。具体白名单见 `internal/protocol/redisproxy/policy.go`;不根据命令名称前缀猜测权限。Redis 的 READONLY 命令不是权限控制,不能代替此策略。
单节点只读连接逐条读取完整 RESP 回复再处理下一条请求,混合流水线按原顺序返回读取结果和 NOPERM,拒绝的请求不会到达上游。单条回复沿用 Cluster 解析器的 32 MiB、65536 个数组项及 64 层嵌套限制。单节点已连接会话不能通过重新认证切换只读/读写模式,需重新连接;读写连接保持流式转发。默认模式和已有账号不自动改变,无数据迁移。Redis 自身的过期清理等内部维护仍正常运行。
## RabbitMQ
按用户选择实现 **严格只读**,不是消费者权限。AMQP 0-9-1 只允许连接/通道控制、心跳,以及带 passive 标志的已有队列/交换机检查。队列检查可以返回消息数和消费者数,但不读取消息正文。不允许空名称、主动声明、exclusive/auto-delete 等声明标志。
拒绝 basic.consume、basic.get、ack/reject/nack/recover、publish、队列/交换机创建、绑定、清空、删除、事务和未知方法;也拒绝客户端直接发送内容头/正文帧。拒绝先记录审计,再返回 AMQP connection.close 403 并断开整个连接,避免流水线发布的剩余内容帧穿透。客户端自动声明/绑定也会被拒绝,需使用被动检查。
账号默认仍为读写,无数据迁移;界面在选择 RabbitMQ 只读时显示禁止消费/确认的说明。
### AMQP 与管理页面双端口
同一个 RabbitMQ 网关可同时监听 AMQP 和 HTTP 管理端口。网关的 `listenPort` 为 AMQP;`managementListenPort` 为管理 HTTP,默认 0(关闭),可以设为 15672。两个端口使用同一个 `listenHost`、来源白名单、连接数上限、账号映射和审计。目标服务的 `targetPort` 通常为 5672;`managementPort` 默认 0 时使用 15672,`managementTLS` 可单独启用目标 HTTPS(例如 15671),共用目标 CA。管理页面地址为 `http://<网关主机>:<管理监听端口>/`,用网关账号登录;上游需要启用 management 插件并授予目标账号相应管理权限。
管理 HTTP 复用现有反向代理与 Basic 凭据映射:只读账号仅允许内置信息 API 的 GET/HEAD 及页面静态资源。拒绝 POST/PUT/PATCH/DELETE、消息获取接口、定义导入/导出、未知插件路径、协议升级和 CONNECT。特别拒绝 GET `aliveness-test`,因为它会创建队列并发送/消费探测消息。拒绝返回 HTTP 403,不转发目标。读写账号保留上游权限决定的管理功能。管理接口连接目标配置的主机和管理端口,不使用 AMQP 集群发现或 AMQP 持久连接;SSH/Kubernetes 出口使用管理端口独立拨号。
双端口全部绑定成功后才发布运行状态;管理端口占用或持久化失败会关闭本次新建监听。停止/关闭程序释放两个端口。已运行时修改管理监听端口或启用管理监听时的监听地址,必须先停网关;目标管理端口配置更新对新连接生效。诊断页面展示管理监听地址。启用管理端口时逐个创建网关,不支持批量自动分配两个端口。配置使用现有 JSON 存储,无数据库迁移;老配置不自动开放新端口。
## 错误与记录
2026-10-15 Redis/RabbitMQ 实现验证:Go 协议、model、gateway 和 API 专项及全量中的这些包通过;Node 18 项、TypeScript/Vite、go vet 和新嵌入程序构建通过。浏览器 React/UI domain 及 Redis 16 项通过,修正 RabbitMQ 测试默认账号后两协议保存回归 2 项通过。全量仍有既有 cluster MySQL fixture 崩溃和 account-editor 浏览器旧定位器失败,详情见 change-rationale.md;日志及程序位于 `bin/dev-build/readonly/`。未使用真实 Redis/RabbitMQ,也未部署到现有实例。
被拒绝请求使用协议原生错误返回;RabbitMQ 拒绝时关闭连接,其余协议普通策略拒绝不主动断开连接。命令审计记录拒绝,SQL/BSON 请求内容覆盖范围见开发计划。直连、SOCKS5 与 SSH 转发出口执行相同规则。