# 可选只读模式 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 转发出口执行相同规则。