TiDB 安全启动(Secure Bootstrap)设计解析:从 `--initialize-insecure` 到 `--initialize-secure`
2026/9/10 19:51:41 网站建设 项目流程

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 身份登录;
  • 无密码账户意味着攻击者不需要破解任何凭据即可获得SUPERGRANT 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 'ubuntu'@'localhost' IDENTIFIED WITH 'auth_socket' REQUIRE NONE PASSWORD EXPIRE DEFAULT ACCOUNT UNLOCK; GRANT ALL PRIVILEGES ON *.* TO 'ubuntu'@'localhost' WITH GRANT OPTION;

注意两个关键差异:

  1. 用户名不是固定的 root,而是当前 OS 用户名(如ubuntutidb等);
  2. Host 不是%而是localhost,认证插件是auth_socket,不依赖密码。

源码实现同样在doDMLWorks中(pkg/session/bootstrap.go):当SecureBootstrap为 true 时,调用osuser.Current()获取当前进程的操作系统用户名,然后插入一条("localhost", "root", ?, "auth_socket", ...)记录,其中?参数即u.Username

值得注意的实现细节:secure 模式插入的 User 字段固定为"root",Host 为"localhost",而 auth_socket 插件在登录时会用真实 OS 用户名去匹配。也就是说最终能登录的管理员身份是「root@localhost(本地 OS 用户)」——这与设计文档中「创建基于 OS 用户的账户」的描述在最终效果上一致:只有与 OS 用户名匹配的本机进程才能通过认证。

启动完成后,管理员可以立即通过 Unix Socket 以本机用户身份登录,并创建常规的网络管理账户:

mysql -S /tmp/tidb.sock
mysql> 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:

  1. 互斥校验--initialize-secure--initialize-insecure不能同时显式指定,否则报错the options -initialize-insecure and -initialize-secure are mutually exclusive
  2. 取值归一化--initialize-secure直接写入cfg.Security.SecureBootstrap--initialize-insecure则取其反值写入(SecureBootstrap = !*initializeInsecure);
  3. Windows 限制:由于auth_socket依赖 Unix 域套接字的对端凭据(peer credentials),Windows 不支持,因此当runtime.GOOS == "windows"SecureBootstrap为 true 时直接报错the option -initialize-secure is not supported on Windows,secure 模式仅在类 Unix 系统可用;
  4. 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)。cleanupStaleSocket(server.go)在启动监听前执行:如果目标 socket 文件存在,会先校验其类型是否为 socket;若是正常 socket,则尝试net.Dial("unix", ...)探测它是否仍然可用——若可用则拒绝覆盖(防止误杀正在运行的实例),否则将其视为残留文件移除。这避免了「上次异常退出留下 socket 文件导致本次启动失败」的经典问题。

握手阶段的强制约束

auth_socket 的校验并不只发生在「验证密码」环节,而是贯穿整个握手流程。关键约束在 pkg/server/conn.go:

if !cc.isUnixSocket && authPlugin == mysql.AuthSocket { return servererr.ErrAccessDeniedNoPassword.FastGenByArgs(cc.user, host) }

即:凡是使用 auth_socket 插件的用户,通过 TCP 连接一律直接拒绝(错误码 1045)。只有来自 Unix Socket 的连接才被允许继续。

checkAuthPlugin(conn.go)中,当用户的认证插件为auth_socket时:

  1. 再次确认连接来自 Unix Socket(cc.isUnixSocket),否则拒绝;
  2. 通过user.LookupId(fmt.Sprint(cc.socketCredUID))获取 Unix Socket 对端进程的真实 UID 对应的 OS 用户名;
  3. 将 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_socketALTER USER将任意本地用户切换为该插件。

单元测试:TestAuthSocket 的行为验证

TestAuthSocket 是验证该机制的关键集成测试,其断言清晰地反映了设计意图:

  1. 创建u1@'%'u2@'%'AS 'sockuser')与sockuser@'%'三个 auth_socket 用户;
  2. TCP 登录一律拒绝:无论 OS 用户被 mock 成什么名字,通过网络(127.0.0.1)连接 auth_socket 用户都会得到Error 1045 (28000): Access denied
  3. Socket 登录且用户名与 OS 用户一致时放行:将 OS 用户 mock 为sockuser后,通过config.Net = "unix"sockuser身份连接成功,select current_user()返回sockuser@%
  4. Socket 登录但用户名与 OS 用户不一致时拒绝:OS 用户为sockuser时,用u1连接被拒绝;
  5. AS 'sockuser'语法(指定认证字符串覆盖用户名匹配)也被覆盖验证:无论 MySQL 用户名是u2还是 OS 用户u2,只要任一与 OS 用户名匹配即可登录。

测试中使用MockOSUserForAuthSocket/ClearOSUserForAuthSocket(conn.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」(root@localhostroot@%)会带来身份混淆;
    • 让默认管理员因机器而异,则需要配套「创建 root 账户」的引导文档。
  • 从当前 bootstrap.go 的实现看,secure 模式实际写入的是("localhost", "root", <OS 用户名>, "auth_socket", ...)——即 MySQL 用户名固定为 root、认证由 OS 用户名驱动,相当于文档中「2 个 root」方案的一种落地形态;root@'%'仍然通过后续手工CREATE USER创建。

实践建议小结

  1. 生产环境首选--initialize-secure(在类 Unix 系统上):从第一次启动就杜绝「无密码 root 暴露全网」的窗口期;
  2. 启动后立即通过mysql -S /tmp/tidb-{Port}.sock登录,创建带强密码的root@'%'网络管理账户,并保留 OS 用户的 socket 通道作为本地运维入口;
  3. 若沿用默认的--initialize-insecure,务必在第一时间ALTER USER 'root'@'%' IDENTIFIED BY '<强密码>',并配合防火墙限制 3306 端口来源;
  4. 善用--initialize-sql-file在首次 bootstrap 时固化需要的全局变量;
  5. 注意 secure 模式与 Windows 不兼容,Windows 部署只能使用 insecure 模式后手动加固。

延伸阅读

  • 设计文档原文:docs/design/2021-09-29-secure-bootstrap.md
  • bootstrap 核心实现:pkg/session/bootstrap.go
  • 命令行参数定义与校验:cmd/tidb-server/main.go、cmd/tidb-server/main.go
  • 配置项定义:pkg/config/config.go
  • Unix Socket 监听与陈旧文件清理:pkg/server/server.go
  • auth_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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询