Bitwarden 本地开发库 MySQL 探索指南:只读连接、双重防护与方言适配

Bitwarden 本地开发库 MySQL 探索指南:只读连接、双重防护与方言适配 Bitwarden 本地开发库 MySQL 探索指南只读连接、双重防护与方言适配【免费下载链接】serverBitwarden infrastructure/backend (API, database, Docker, etc).项目地址: https://gitcode.com/GitHub_Trending/ser/server导读本文面向需要在本地开发环境中对 Bitwarden server 的 MySQL 数据库进行只读探索查询组织/用户/密码库数据、核对 seeder 灌入的夹具、检查表结构的开发者与 AI Agent。文章完整继承仓库内 MySQL 数据源参考文档 的全部实操内容并补充 docker-compose 服务拓扑、EF Core 实现证据与 schema 真相来源帮助你掌握环境变量校验、SET SESSION TRANSACTION READ ONLY只读防护、宿主机/容器两种连接方式、heredoc 多语句执行以及从 MSSQL 示例迁移到 MySQL 时的全部方言差异。该文档是仓库中 exploring-bitwarden-data 技能以mssql/mysql/postgresql三种数据库为后端的只读数据探索技能的三份 Provider 参考之一与 MSSQL 参考、PostgreSQL 参考 配合使用。一、本地开发环境中的 MySQLcompose 服务与连接拓扑当前仓库通过docker compose提供本地 MySQL 服务定义位于 dev/docker-compose.ymlmysql: image: mysql:8.0 ports: - 3306:3306 command: - --default-authentication-pluginmysql_native_password - --innodb-print-all-deadlocksON environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: vault_dev volumes: - mysql_dev_data:/var/lib/mysql profiles: - mysql - ef据此可以得到本地开发环境的默认拓扑compose 服务名mysql容器名为bitwardenserver-mysql-1compose 项目名前缀bitwardenserver拼服务名再加序号宿主机端口3306数据卷mysql_dev_data持久化到/var/lib/mysql数据库名vault_dev由MYSQL_DATABASE在容器首次初始化时自动创建服务 profilemysql与ef意味着按--profile mysql或--profile ef启动 compose 时才会拉起该服务。注意compose 直接暴露的是root超级用户但数据探索技能约定一律不直接使用 root而是使用环境变量指向的只读账号bitwarden_data_reader见下一节。这一点与 PostgreSQL 参考 中的BW_POSTGRES_USERNAME同样指向只读登录保持一致的纪律。二、连接前置四枚环境变量与一次性校验在任何连接工作之前先确认以下四个环境变量已就绪它们是技能与本地开发环境之间传递凭据的约定环境变量含义BW_MYSQL_SERVERMySQL 服务地址宿主机已装客户端时优先使用如localhost/127.0.0.1BW_MYSQL_DB_NAME目标数据库名本地开发环境为vault_devBW_MYSQL_USERNAME只读账号bitwarden_data_reader不是rootBW_MYSQL_PASSWORD只读账号的密码文档给出了一次性校验命令任一变量缺失即报 MISSING[ -n ${BW_MYSQL_SERVER:-} ] [ -n ${BW_MYSQL_DB_NAME:-} ] \ [ -n ${BW_MYSQL_USERNAME:-} ] [ -n ${BW_MYSQL_PASSWORD:-} ] \ echo Bitwarden MySQL env vars OK || echo MISSING Bitwarden MySQL env var使用:-默认值展开保证了在未定义变量时不会因set -u而直接中断而是走||分支输出缺失提示适合在脚本开头作为前置断言。三、只读防护连接层账号 会话层事务双重防线这是整个技能最重要的安全纪律MySQL 参考文档称之为Read-only guard — required on every connection连接层以BW_MYSQL_USERNAMEbitwarden_data_reader登录该账号在服务端被配置为SELECT-only任何写操作在服务器层面就会被拒绝与客户端发送什么无关。会话层作为纵深防御defense-in-depth每次会话开头都要显式执行SET SESSION TRANSACTION READ ONLY;文档明确标注了这一防护的已验证行为在此状态下执行写入会得到错误ERROR 1792: Cannot execute statement in a READ ONLY transaction并且由于 autocommit 语句本身就是隐式事务普通INSERT也在此覆盖范围内。同时文档也点出了该防护的局限它是按会话生效的只有在会话中真正发出这条SET语句才有效——因此每条连接都必须带上而不是依赖某个全局默认值。这一双重防线与 SKILL.md 中声明的技能边界完全一致只允许SELECT、WITHCTE以及INFORMATION_SCHEMA自省查询不用于编写存储过程、迁移或任何数据修改。四、连接方式一宿主机 mysql 客户端推荐当宿主机安装了mysql客户端时优先使用它配合上面的环境变量连接。关键实践细节密码通过MYSQL_PWD环境变量传递绝不用-p——-p会在命令行上暴露密码并打印 warning连接的语句内必须保留SET只读防护即第一条-e语句就是SET SESSION TRANSACTION READ ONLY;客户端成功时静默无输出错误打印到 stderr——所以不要笼统地丢弃 stderr否则失败会被无声吞掉。MYSQL_PWD$BW_MYSQL_PASSWORD mysql \ -h $BW_MYSQL_SERVER -u$BW_MYSQL_USERNAME $BW_MYSQL_DB_NAME \ -e SET SESSION TRANSACTION READ ONLY; SELECT COUNT(*) FROM Organization;-h指定服务地址、-u紧跟用户名此处用-u...的形式避免空格歧义、数据库名作为位置参数-e执行单条可包含分号分隔的多语句SQL。五、连接方式二容器回退宿主机无客户端时当宿主机没有安装mysql客户端时改为进入容器执行但必须把密码注入容器环境docker exec -e MYSQL_PWD...连接身份依然是只读的BW_MYSQL_USERNAMEdocker exec -e MYSQL_PWD$BW_MYSQL_PASSWORD -i bitwardenserver-mysql-1 \ mysql -u$BW_MYSQL_USERNAME $BW_MYSQL_DB_NAME \ -e SET SESSION TRANSACTION READ ONLY; SELECT COUNT(*) FROM Organization;两个值得注意的细节%的只读账号匹配的是 socket 连接容器内的 mysql 客户端默认走本地 socket恰好与bitwarden_data_reader账号主机部分为%匹配只有当 socket 连接被拒绝时才需要补-h 127.0.0.1强制走 TCP 回路。-i参数必须保留-i保持 stdin 打开这是下面 heredoc 方式能拿到输入的前提如果省略stdin 未挂接输出会静默为空——一个非常隐蔽的失败模式。六、多语句执行heredoc 与 docker exec -i 的配合当需要执行一段包含多条语句或 CTE 的脚本时使用 heredoc 形式-iattach stdin是必需的docker exec -e MYSQL_PWD$BW_MYSQL_PASSWORD -i bitwardenserver-mysql-1 \ mysql -u$BW_MYSQL_USERNAME $BW_MYSQL_DB_NAME MYSQL SET SESSION TRANSACTION READ ONLY; SELECT Name, Seats FROM Organization ORDER BY Name LIMIT 10; MYSQL两点纪律与 SKILL.md 的跨 Provider 规则呼应heredoc 标签要用单引号包裹MYSQL——不带引号时 bash 会在 SQL 到达 mysql 客户端之前展开其中的$破坏列引用例如$.GUID这类 JSON 路径表达式展示结果时去掉 CLI 尾部信息如Query OK、N rows affected并在呈现前回显实际执行的 SQL保证可追溯。七、MySQL 方言适配从 MSSQL 示例迁移的完整差异清单数据探索技能的母版查询示例以 MSSQL 为主见 MSSQL 参考 中的sqlcmd例子而 MySQL 是 EF 生成的 schema方言差异集中体现在以下几点参考文档逐条列出7.1 标识符PascalCase 免引号但Group是保留字Bitwarden 的 EF schema 使用 PascalCase 表名/列名如Organization、Cipher、OrganizationUser在 Linux 容器上不加引号也能正常工作这与 PostgreSQL 截然不同——PG 参考文档强调必须为每个 PascalCase 标识符加双引号Group是 MySQL 保留字必须用反引号转义Group不使用 MSSQL 的[方括号]语法。7.2 函数与子句替换对照表MSSQL 写法MySQL 等价写法说明SELECT TOP nSELECT ... LIMIT nMySQL 没有TOP nGETUTCDATE()UTC_TIMESTAMP()取 UTC 当前时间WITH (NOLOCK)删除MySQL InnoDB 默认 MVCC 读无需 NOLOCK 提示[brackets]反引号仅保留字需要见 7.17.3 按用户 JSON 列longtext与JSON_UNQUOTE(JSON_EXTRACT(...))Cipher表上的Archives/Favorites/Folders三个按用户区分的 JSON 列在 MySQL 中是longtext类型而非原生JSON类型因此查询时需要用标准的文本-JSON 提取组合JSON_UNQUOTE(JSON_EXTRACT(col, $.UPPERCASE-GUID))这里有个容易踩坑的语义背景同样记录在 SKILL.md 的 Grounding Rules 中归档状态存放在Cipher.Archives里、以大写用户 GUID 为键的每用户 JSON 中而ArchivedDate列虽然存在但归档流程从不写入它Cipher_Archive.sql 用的是JSON_MODIFY操作Archives。JSON 键是大小写敏感的而 SQL Server 的UNIQUEIDENTIFIER渲染为大写——因此查询时必须按$.UPPERCASE-GUID的大小写形态插值键名。7.4 没有存储过程与 TVF从 sources.md 重建访问控制逻辑MSSQL 参考中反复使用的两个规范化表值函数TVF——UserCipherDetails用户 X 能看到哪些密码库项与UserCollectionDetails用户 X 能访问哪些集合及其权限——在 MySQL/EF 环境中不存在。这是因为 EF 的三种 ProviderMySQL 的 Pomelo、PostgreSQL、SQLite没有存储过程或函数层整个 schema 由迁移生成见下文第八节。因此在 MySQL 上做访问控制相关查询时需要从函数源码重建其逻辑而不是直接调用。函数源码位置在 sources.md 中有完整索引UserCipherDetails→ src/Sql/dbo/Vault/Functions/UserCipherDetails.sql每用户密码库投影含按用户计算的 ArchivedDate→ src/Sql/dbo/Vault/Functions/CipherDetails.sqlUserCollectionDetails含有效权限→ src/Sql/dbo/AdminConsole/Functions/UserCollectionDetails.sql这些函数封装了成员状态、组织启用状态以及直接授权优先于组授权的优先级语义手写 JOIN 极易在细节上重建出错——这也是技能文档要求优先从规范函数源码重建、而非凭直觉拼 JOIN的原因。八、源码印证为什么 MySQL 侧没有存储过程上面的无 TVF结论可以从仓库源码得到印证数据访问层使用 EF Core Pomelo MySQL ProviderInfrastructure.EntityFramework.csproj 引用了Pomelo.EntityFrameworkCore.MySql8.0.2Provider 注册时对 MySQL 分支调用options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), b b.MigrationsAssembly(MySqlMigrations))见 EntityFrameworkServiceCollectionExtensions.cs即自动探测服务器版本并用迁移程序集MySqlMigrations对应的迁移项目位于 util/MySqlMigrations其中的Migrations/目录下有数百个按版本组织的迁移文件另有 util/PostgresMigrations 与 SQLite 迁移表明三种 EF 后端的 schema 完全由迁移脚本驱动与 MSSQL 的 SSDT 工程src/Sql/dbo 下的 Tables/Views/Functions/Stored Procedures是两套并行 schema存储过程与 TVF 只存在于 MSSQL 一侧。因此在 MySQL 上查不到UserCipherDetails函数不是配置问题而是架构使然——这正是方言适配章节存在的原因。九、实践要点速查把 MySQL 参考文档的纪律压缩成一张自查清单连前必查四个BW_MYSQL_*环境变量齐备BW_MYSQL_USERNAME是bitwarden_data_reader而非root连时必带MYSQL_PWD传密码不用-p每条会话首句执行SET SESSION TRANSACTION READ ONLY;防ERROR 1792容器回退docker exec -e MYSQL_PWD... -i bitwardenserver-mysql-1 mysql -u$BW_MYSQL_USERNAME $BW_MYSQL_DB_NAMEsocket 被拒才加-h 127.0.0.1多语句单引号 heredocMYSQL-i参数否则输出静默为空方言Group加反引号LIMIT替代TOPUTC_TIMESTAMP()替代GETUTCDATE()不写WITH (NOLOCK)JSON 列Archives/Favorites/Folders是longtext用JSON_UNQUOTE(JSON_EXTRACT(col, $.UPPERCASE-GUID))无 TVF访问控制逻辑从 sources.md 列出的函数源码重建schema 自省用INFORMATION_SCHEMA可参考 schema-discovery-queries.md 中查询思路的 MySQL 对应版结果呈现少于 20 行转 Markdown 表格更大集合给 top-N 总数并回显实际执行的 SQL剥离 CLI 尾部噪音。【免费下载链接】serverBitwarden infrastructure/backend (API, database, Docker, etc).项目地址: https://gitcode.com/GitHub_Trending/ser/server创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考