设计解析:从 `--initialize-insecure` 到 `--initialize-secure`)
TiDB 安全启动Secure Bootstrap设计解析从--initialize-insecure到--initialize-secure【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb导读本文基于 TiDB 官方设计文档 2021-09-29-secure-bootstrap.md深入讲解 TiDB 首次启动bootstrap阶段的账户安全模型。你将理解为什么 TiDB 默认创建一个无密码且绑定0.0.0.0/::的root账户存在安全隐患--initialize-secure与--initialize-insecure两个启动选项的语义与用法auth_socket认证插件如何在 Unix Socket 场景下用操作系统用户身份代替网络密码以及该改动在源码bootstrap.go、main.go、server.go、conn.go中的实际落地情况。读完本文你可以安全地配置一个从启动第一秒就默认收紧权限的 TiDB 实例并自行用auth_socket创建本地管理账户。背景默认无密码 root 账户的安全隐患在 TiDB 的历史默认行为中首次启动bootstrap阶段会创建一个无密码的超级管理员账户并且该账户的 Host 被设置为%即所有来源主机服务监听地址默认绑定0.0.0.0/::。设计文档明确指出这是潜在的安全问题风险场景包括用户没有正确配置防火墙导致任何人都能通过网络连接到 TiDB 的 3306 端口攻击者获得服务器本地访问权限后可以直接通过本地网络接口以 root 身份登录无密码账户意味着攻击者不需要破解任何凭据即可获得SUPER、GRANT OPTION等全部权限。为此TiDB 引入了两个 bootstrap 选项--initialize-insecure维持旧行为创建无密码的root%这是当前默认值--initialize-secure新的安全启动方式基于操作系统用户创建本地超级管理员并通过auth_socket插件认证。设计文档的意图非常明确当前行为等价于--initialize-insecure但一旦确认安全模式稳定就会把默认值切换到 secure。也就是说这是一次「默认安全」的迁移设计作者在文档中强调要用「最小破坏」的方式完成这次变更。两种启动方式的账户模型对比insecure无密码的 root%--initialize-insecure对应的 bootstrap SQL 完全保持不变CREATE USER root% IDENTIFIED WITH mysql_native_password AS REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; GRANT ALL PRIVILEGES ON *.* TO root% WITH GRANT OPTION;要点拆解认证插件为mysql_native_password认证串为空字符串即无密码Host 为%允许从任意主机连接拥有ALL PRIVILEGES ON *.*与WITH GRANT OPTION是名副其实的超级管理员。在源码中这一逻辑位于 pkg/session/bootstrap.go 的doDMLWorks函数当config.GetGlobalConfig().Security.SecureBootstrap为 false 时直接向mysql.user表插入一条 Host 为%、plugin 为mysql_native_password、authentication_string 为空的高权限记录并且这些语句在单个事务中执行mustExecute(s, BEGIN)保证 bootstrap 的原子性。secure基于 OS 用户的 auth_socket 账户--initialize-secure不再创建网络可访问的 root而是读取当前操作系统用户名创建只绑定localhost的超级管理员。以 OS 用户ubuntu为例CREATE USER ubuntulocalhost IDENTIFIED WITH auth_socket REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; GRANT ALL PRIVILEGES ON *.* TO ubuntulocalhost WITH GRANT OPTION;注意两个关键差异用户名不是固定的 root而是当前 OS 用户名如ubuntu、tidb等Host 不是%而是localhost认证插件是auth_socket不依赖密码。源码实现同样在doDMLWorks中pkg/session/bootstrap.go当SecureBootstrap为 true 时调用osuser.Current()获取当前进程的操作系统用户名然后插入一条(localhost, root, ?, auth_socket, ...)记录其中?参数即u.Username。值得注意的实现细节secure 模式插入的 User 字段固定为rootHost 为localhost而 auth_socket 插件在登录时会用真实 OS 用户名去匹配。也就是说最终能登录的管理员身份是「rootlocalhost本地 OS 用户」——这与设计文档中「创建基于 OS 用户的账户」的描述在最终效果上一致只有与 OS 用户名匹配的本机进程才能通过认证。启动完成后管理员可以立即通过 Unix Socket 以本机用户身份登录并创建常规的网络管理账户mysql -S /tmp/tidb.sockmysql CREATE USER root% IDENTIFIED BY securePassword; mysql GRANT ALL PRIVILEGES ON *.* TO root% WITH GRANT OPTION;命令行选项与参数解析在 cmd/tidb-server/main.go 中定义了三个相关的启动参数参数名默认值说明--initialize-securefalse以安全模式完成首次 bootstrap--initialize-insecuretrue以非安全模式完成首次 bootstrap当前默认--initialize-sql-file首次 bootstrap 后执行的一组 SQL 语句的文件路径参数解析与校验逻辑集中在 main.go互斥校验--initialize-secure与--initialize-insecure不能同时显式指定否则报错the options -initialize-insecure and -initialize-secure are mutually exclusive取值归一化--initialize-secure直接写入cfg.Security.SecureBootstrap--initialize-insecure则取其反值写入SecureBootstrap !*initializeInsecureWindows 限制由于auth_socket依赖 Unix 域套接字的对端凭据peer credentialsWindows 不支持因此当runtime.GOOS windows且SecureBootstrap为 true 时直接报错the option -initialize-secure is not supported on Windowssecure 模式仅在类 Unix 系统可用SQL 文件检查--initialize-sql-file指定的文件必须存在否则报错can not access -initialize-sql-file。配置项的最终形态是 pkg/config/config.go 中Security结构体的SecureBootstrap bool同时支持 TOML 与 JSON 两种配置格式toml:secure-bootstrap json:secure-bootstrap因此该开关也可以在配置文件或控制面 API 中设置而不仅仅局限于命令行。关于--initialize-sql-file的补充其设计用途是「在首次 bootstrap 后执行一组 SQL」典型场景是设置需要持久化到集群的 GLOBAL 系统变量因为这类变量不随配置文件读取。执行逻辑在 pkg/session/bootstrap.go读取文件、用 SQL 解析器解析出语句列表然后逐条ExecuteStmt执行解析或执行失败时在非测试环境会直接 Fatal 退出。auth_socket 认证插件原理与连接校验auth_socket是 secure bootstrap 的基石。它的核心思想是不校验密码而是校验发起连接的进程是否属于指定的操作系统用户从而把「谁能当管理员」的决定权交给操作系统权限体系。服务端监听TCP Unix Socket 双通道要让 auth_socket 有意义TiDB 必须同时监听 TCP 与 Unix Socket。默认 socket 路径由参数-socket控制默认值为/tmp/tidb-{Port}.sock见 main.go并被映射到cfg.Socket。在 pkg/server/server.go 中needUnixSocket : s.cfg.Socket ! 服务启动时会根据配置同时拉起 TCP 监听与 Unix Socket 监听。配套的还有一个对用户体验至关重要的细节——陈旧 socket 文件清理对应设计文档中的 supporting feature #2。cleanupStaleSocketserver.go在启动监听前执行如果目标 socket 文件存在会先校验其类型是否为 socket若是正常 socket则尝试net.Dial(unix, ...)探测它是否仍然可用——若可用则拒绝覆盖防止误杀正在运行的实例否则将其视为残留文件移除。这避免了「上次异常退出留下 socket 文件导致本次启动失败」的经典问题。握手阶段的强制约束auth_socket 的校验并不只发生在「验证密码」环节而是贯穿整个握手流程。关键约束在 pkg/server/conn.goif !cc.isUnixSocket authPlugin mysql.AuthSocket { return servererr.ErrAccessDeniedNoPassword.FastGenByArgs(cc.user, host) }即凡是使用 auth_socket 插件的用户通过 TCP 连接一律直接拒绝错误码 1045。只有来自 Unix Socket 的连接才被允许继续。在checkAuthPluginconn.go中当用户的认证插件为auth_socket时再次确认连接来自 Unix Socketcc.isUnixSocket否则拒绝通过user.LookupId(fmt.Sprint(cc.socketCredUID))获取 Unix Socket 对端进程的真实 UID 对应的 OS 用户名将 OS 用户名与 MySQL 用户名或IDENTIFIED WITH auth_socket AS ...指定的字符串比对一致才放行。连接对端 UID 的获取在 server.go 中完成Unix Socket 连接建立后调用linux.GetSockUID(*uc)拿到对端凭据并记录到clientConn.socketCredUID。其他相关行为的源码佐证在权限校验模块 pkg/privilege/privileges/privileges.go 中AuthSocket用户的哈希校验直接返回 true——因为 auth_socket 用户的认证串可以是任意占位实际安全性由 OS 层保证无需也无法做密码哈希检查。用户可以通过CREATE USER ... IDENTIFIED WITH auth_socket或ALTER USER将任意本地用户切换为该插件。单元测试TestAuthSocket 的行为验证TestAuthSocket 是验证该机制的关键集成测试其断言清晰地反映了设计意图创建u1%、u2%AS sockuser与sockuser%三个 auth_socket 用户TCP 登录一律拒绝无论 OS 用户被 mock 成什么名字通过网络127.0.0.1连接 auth_socket 用户都会得到Error 1045 (28000): Access deniedSocket 登录且用户名与 OS 用户一致时放行将 OS 用户 mock 为sockuser后通过config.Net unix以sockuser身份连接成功select current_user()返回sockuser%Socket 登录但用户名与 OS 用户不一致时拒绝OS 用户为sockuser时用u1连接被拒绝AS sockuser语法指定认证字符串覆盖用户名匹配也被覆盖验证无论 MySQL 用户名是u2还是 OS 用户u2只要任一与 OS 用户名匹配即可登录。测试中使用MockOSUserForAuthSocket/ClearOSUserForAuthSocketconn.go 中声明的mockOSUserForAuthSocketTest原子指针来模拟不同的 OS 用户身份说明该路径在真实环境中依赖操作系统用户查询接口。测试设计、影响与风险设计文档对测试与发布策略的说明同样值得保留集成测试要求--initialize-secure的行为需要与 TiDB Dashboard、TiDB Operator、DBaaS、TiUP 等周边组件联合验证兼容策略部分组件如 DBaaS可以选择显式使用--initialize-insecure完成 bootstrap随后将 root 密码改为强密码——这与 MySQL 各种安装方式中的 bootstrap 模式类似升级/降级无风险因为改动只落在 bootstrap 流程本身不影响已初始化集群的升级/降级路径版本策略该改动不打算 cherry-pick 到既有 GA 版本否则会与 TiDB Dashboard 产生兼容性问题已由 Dashboard 团队确认。影响与风险方面文档特别指出选择auth_socket的前提是 TiDB 只支持类 Unix 操作系统、未来也不计划支持 Windows源码中-initialize-secure is not supported on Windows的硬校验与此一致存在一些边界复杂度例如 socket 文件已存在的处理已由cleanupStaleSocket解决--initialize-secure选项本身不增加风险但将其设为默认值会增加风险——这是一次行为变更如果文档与沟通没有同步更新对新用户来说会成为支持问题。备选方案为什么不用随机密码设计文档记录了一个被讨论并拒绝的备选方案像 MySQL 那样在--initialize-secure时生成随机密码。文档给出的结论是「已讨论并拒绝」未采纳的原因可以结合auth_socket的取舍推断随机密码需要额外渠道安全地传递给管理员写日志、写文件都有泄露面而auth_socket直接把认证委托给操作系统更符合「本机管理、无口令化」的最小暴露原则。未决问题设计文档在发布时留下了两个开放问题社区在后续演进中可以继续讨论auth_socket 用户在 MySQL 侧的身份应该取OS 用户名还是固定为root两种做法各有代价保留两个「root」rootlocalhost与root%会带来身份混淆让默认管理员因机器而异则需要配套「创建 root 账户」的引导文档。从当前 bootstrap.go 的实现看secure 模式实际写入的是(localhost, root, OS 用户名, auth_socket, ...)——即 MySQL 用户名固定为 root、认证由 OS 用户名驱动相当于文档中「2 个 root」方案的一种落地形态root%仍然通过后续手工CREATE USER创建。实践建议小结生产环境首选--initialize-secure在类 Unix 系统上从第一次启动就杜绝「无密码 root 暴露全网」的窗口期启动后立即通过mysql -S /tmp/tidb-{Port}.sock登录创建带强密码的root%网络管理账户并保留 OS 用户的 socket 通道作为本地运维入口若沿用默认的--initialize-insecure务必在第一时间ALTER USER root% IDENTIFIED BY 强密码并配合防火墙限制 3306 端口来源善用--initialize-sql-file在首次 bootstrap 时固化需要的全局变量注意 secure 模式与 Windows 不兼容Windows 部署只能使用 insecure 模式后手动加固。延伸阅读设计文档原文docs/design/2021-09-29-secure-bootstrap.mdbootstrap 核心实现pkg/session/bootstrap.go命令行参数定义与校验cmd/tidb-server/main.go、cmd/tidb-server/main.go配置项定义pkg/config/config.goUnix Socket 监听与陈旧文件清理pkg/server/server.goauth_socket 握手校验pkg/server/conn.go、pkg/server/conn.go集成测试pkg/server/tests/commontest/tidb_test.go【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考