ITPub博客

首页 > 数据库 > 国内数据库 > OceanBase 租户与用户访问控制疑问:ob_tcp_invited_nodes 和 user@host 以谁为准?

OceanBase 租户与用户访问控制疑问:ob_tcp_invited_nodes 和 user@host 以谁为准?

原创 国内数据库 作者:瑞蓝创_RLC_888 时间:2026-11-11 17:16:00 0 删除 编辑

1. 问题背景

OceanBase 中,"谁能连上数据库"由两层机制共同限制:

  1. 租户级:系统变量 ob_tcp_invited_nodes(创建租户时设置,或事后修改),约束整个租户可接受连接的来源 IP;
  2. 账号级:用户 'user'@'host' 中的主机名部分(MySQL 模式创建用户时指定),约束该用户可登录的来源 IP,例如 'app_reader'@'10.0.0.21' 只允许来自 10.0.0.21 的连接。

两层各自独立配置,由此引出两个问题:

  • 两者不一致(互不覆盖)时,会发生什么?
  • 两者是包含关系(一个大一个小)时,以谁为准?

2. 机制原理

2.1 租户级检查:ob_tcp_invited_nodes

参数性质与默认值

  • ob_tcp_invited_nodes 是 租户级系统变量(GLOBAL_SCOPE),不是配置项(config parameter):只能用系统变量语法 SET GLOBAL 修改(配置项语法 ALTER SYSTEM SET 会报 5099 (42000): System config unknown),修改对后续新连接立即生效。
  • 底层源码默认值为 '127.0.0.1,::1',即新租户在未显式设置时,除本机回环地址外一切来源都会被拒绝——这是建租户时容易忽略的默认行为。

租户白名单支持多种灵活形式



-- 精确 IP

SET  GLOBAL ob_tcp_invited_nodes= '192.168.5.87';

-- 通配符
SET  GLOBAL ob_tcp_invited_nodes= '192.168.5.%';

-- CIDR
SET  GLOBAL ob_tcp_invited_nodes= '192.168.1.0/24';

-- 混合逗号列表
SET  GLOBAL ob_tcp_invited_nodes= '192.168.5.%,127.0.0.1';

底层的匹配规则

  • 白名单字符串按逗号拆分为多个条目,逐条与客户端 IP 做匹配,任一命中即放行;
  • 单条匹配有两条路径:字符串通配( % 匹配任意长度、 _ 匹配单个字符)与 IP 地址匹配(精确 IP 或 CIDR 网段,如 192.168.1.0/24);
  • 因此白名单支持:精确 IP、通配符( 192.168.5.%)、CIDR、混合逗号列表。

在什么时候起作用?

连接处理的主流程为:解析握手包并确定客户端 IP → 连接校验(租户白名单在此步骤执行)→ 创建 session → 身份认证(账号检查在此步骤执行)。

  • 白名单检查读取租户系统变量 ob_tcp_invited_nodes,与客户端 IP 做匹配;不匹配即拒绝连接,返回 OB_ERR_NO_PRIVILEGE,并在日志中记录 client is not invited into this tenant;
  • 该检查发生在 session 创建之前,且仅在连接建立时执行一次,已有连接不会再被检查;
  • 特例: root@sys 从 127.0.0.1/ ::1 登录跳过检查(防止误设变量后无法登录);UNIX socket 连接跳过;
  • 经 OBProxy 连接时,检查的是 OBProxy 上报的 真实客户端 IP,而非 OBProxy 自身地址。

匹配不上的典型错误表现: ER_SPECIFIC_ACCESS_DENIED_ERROR(1227)、SQLSTATE 42501。

2.2 账号级检查:'user'@'host'

MySQL 租户中,一个用户由"用户名 + 主机名"二元组唯一确定,主机名是用户身份的一部分(存储于 mysql.user 视图,底层为 __all_user 等表的 USER_NAME/HOST_NAME 字段)。

匹配规则:与租户白名单共用同一套匹配逻辑,即同样支持 %、 _ 通配与精确 IP、CIDR。

检查时机( src/share/schema/ob_schema_getter_guard_user.cpp)

账号检查的处理逻辑:

  1. 按用户名取出 所有同名用户记录(不同主机名各为一条);
  2. 遍历每条记录:先将登录来源 IP 与该记录的主机名做匹配,不匹配则跳过;
  3. 匹配到的记录再校验密码(含双密码场景);
  4. 结果:用户不存在、主机名全不匹配或密码错误,均返回 OB_PASSWORD_WRONG;同名多条记录时, 任一主机名匹配且该记录密码校验通过即登录成功。

匹配不上的典型错误表现: ER_ACCESS_DENIED_ERROR(1045)、SQLSTATE 42000,报错文案为 Access denied for user 'u'@'ip' (using password: ...)。

2.3 两道检查的顺序与组合逻辑

  • 顺序:租户白名单在连接建立早期(session 创建前)执行;账号检查在认证阶段执行。若白名单不通过, 根本不会走到账号检查;
  • 组合:AND 关系。对某个具体用户而言,实际可连接来源 = {client_ip ∈ 租户白名单} ∩ {client_ip ∈ 该用户名下所有主机名记录的并集};
  • 实测佐证:本次实验中,白名单不含当前客户端 IP 时,连接失败错误为 1227 (42501) 而不是 1045——说明请求在到达账号检查之前已被拦下,与源码顺序一致。

3. 实测验证

3.1 实验环境

项 值(实测)
数据库版本 5.7.25-OceanBase-v4.4.2.3( VERSION())
以下语句手动执行,共三次连接,分别覆盖账号层拦截、租户层拦截、检查顺序三种情况,可以快速验证上面的原理。

窗口 1:以 root@msq 登录,全程在该会话执行 SQL。



SELECT 
NOW(
6) 
AS 开始;

SELECT @@GLOBAL.ob_tcp_invited_nodes;                    
-- 主机名为保留地址,必不匹配来源 IP
CREATE  USER  'u_t'@ '192.0.2.2'  IDENTIFIED  BY  'Test_123';  

验证 1 账号层拦截:白名单保持原值(放行全部来源),仅账号主机名不匹配。窗口 2 另开新连接执行:


date 
'+%F %T 验证 1 连接(白名单原值)'

obclient -h192.168.5.86 -P2883 -u 'u_t@msq#ob435' -p 'Test_123' -e  'SELECT 1;'
# 预期 ERROR 1045 (42000): Access denied for user 'u_t'@'192.168.5.87' (using password: YES)
# 判定:白名单 % 放行,账号主机名不匹配 —— 账号层拦截

验证 2 租户层拦截:先在窗口 1 将白名单限制为不含实际来源的保留地址:



SELECT 
NOW(
6) 
AS 限制白名单时刻;

SET  GLOBAL ob_tcp_invited_nodes= '192.0.2.1';    -- 保留地址,不含任何真实来源

再在窗口 2 另开新连接,连 root 自己(root 主机名放行任意来源,账号层不构成拦截):


date 
'+%F %T 验证 2 连接(白名单已受限)'

obclient -h192.168.5.86 -P2883 -u 'root@msq#ob435' -p -e  'SELECT 1;'
# 预期 ERROR 1227 (42501): Access denied —— 拦截发生在账号层之前

验证 3 检查顺序:白名单保持受限,连接 u_t,两层均不匹配。窗口 2 执行:


date 
'+%F %T 验证 3 连接(白名单仍受限)'

obclient -h192.168.5.86 -P2883 -u 'u_t@msq#ob435' -p 'Test_123' -e  'SELECT 1;'
# 预期仍为 ERROR 1227 (42501) 而非 1045 —— 若账号检查在前应报 1045,报 1227 即白名单先查

窗口 1 收尾(本会话不受白名单影响,改完随时可还原):



SELECT 
NOW(
6) 
AS 还原白名单时刻;

SET  GLOBAL ob_tcp_invited_nodes= '%';    -- 还原为最初记下的原值
DROP  USER  IF  EXISTS  'u_t'@ '192.0.2.2';

以上语句完整执行一遍,输出如下

# ===== 窗口 1:准备(root@msq 登录会话内执行) =====
obclient [(none)]> SELECT NOW(6) AS 开始;
+----------------------------+
| 开始                       |
+----------------------------+
| 2026-09-30 18:30:15.120456 |
+----------------------------+
1 row in set (0.00 sec)
obclient [(none)]> SELECT @@GLOBAL.ob_tcp_invited_nodes;
+-------------------------------+
| @@GLOBAL.ob_tcp_invited_nodes |
+-------------------------------+
| %                             |
+-------------------------------+
1 row in set (0.00 sec)
obclient [(none)]> CREATE USER 'u_t'@'192.0.2.2' IDENTIFIED BY 'Test_123';
Query OK, 0 rows affected (0.05 sec)
# ===== 窗口 2:验证 1 =====
[root@192 ~]# date '+%F %T 验证 1 连接(白名单原值)'
2026-09-30 18:31:02 验证 1 连接(白名单原值)
[root@192 ~]# obclient -h192.168.5.86 -P2883 -u'u_t@msq#ob435' -p'Test_123' -e 'SELECT 1;'
ERROR 1045 (42000): Access denied for user 'u_t'@'192.168.5.87' (using password: YES)
# ===== 窗口 1:限制白名单 =====
obclient [(none)]> SELECT NOW(6) AS 限制白名单时刻;
+----------------------------+
| 限制白名单时刻             |
+----------------------------+
| 2026-09-30 18:31:38.663120 |
+----------------------------+
1 row in set (0.00 sec)
obclient [(none)]> SET GLOBAL ob_tcp_invited_nodes='192.0.2.1';
Query OK, 0 rows affected (0.03 sec)
# ===== 窗口 2:验证 2 =====
[root@192 ~]# date '+%F %T 验证 2 连接(白名单已受限)'
2026-09-30 18:31:52 验证 2 连接(白名单已受限)
[root@192 ~]# obclient -h192.168.5.86 -P2883 -u'root@msq#ob435' -p -e 'SELECT 1;'
Enter password: 
ERROR 1227 (42501): Access denied
# ===== 窗口 2:验证 3 =====
[root@192 ~]# date '+%F %T 验证 3 连接(白名单仍受限)'
2026-09-30 18:32:07 验证 3 连接(白名单仍受限)
[root@192 ~]# obclient -h192.168.5.86 -P2883 -u'u_t@msq#ob435' -p'Test_123' -e 'SELECT 1;'
ERROR 1227 (42501): Access denied
# ===== 窗口 1:收尾还原 =====
obclient [(none)]> SELECT NOW(6) AS 还原白名单时刻;
+----------------------------+
| 还原白名单时刻             |
+----------------------------+
| 2026-09-30 18:32:20.451073 |
+----------------------------+
1 row in set (0.00 sec)
obclient [(none)]> SET GLOBAL ob_tcp_invited_nodes='%';
Query OK, 0 rows affected (0.03 sec)
obclient [(none)]> DROP USER IF EXISTS 'u_t'@'192.0.2.2';
Query OK, 0 rows affected (0.04 sec)

若窗口 1 会话中断且白名单未还原,按上文白名单误锁后的人工恢复处理。

结果分析:实际报错与预期一致时,三次连接对应如下结论。

  • 验证 1 报 1045:白名单保持原值,连接已通过租户层检查,拦截发生在账号匹配——账号主机名 192.0.2.2 与实际来源 192.168.5.87 不一致。账号层检查与密码认证共用错误码 1045,本实验以密码正确、仅主机名不匹配的方式排除了密码因素。

  • 验证 2 报 1227:root 主机名为 %,放行任意来源,若仅执行账号匹配不会被拒绝;实际在进入账号检查之前即被拦截,且被拦截的是设置白名单的 root 自己——租户白名单是先于账号检查的独立检查,对租户内全部账号生效,与账号配置无关。

  • 验证 3 报 1227 而非 1045:两层同时不匹配时返回的是白名单的错误码。若账号检查在前此处应报 1045,实际报 1227,证明租户白名单先于用户主机名执行。

  • 验证 1 与验证 2 合看,即开篇第二个问题(包含关系以谁为准)的答案:

    验证 白名单 账号主机名 包含关系 结果 由谁决定
    验证 1 %(大) 192.0.2.2(小) 白名单 ⊇ 主机名 1045 更小的账号层
    验证 2 192.0.2.1(小) %(大) 白名单 ⊆ 主机名 1227 更小的白名单层

    更严的一层决定结果,报错码直接指认该层——实际可连接范围恒为两层允许范围的交集。

总结:三项验证结果与前面的原理一致——MySQL 模式租户的建连检查按“租户白名单 → 用户主机名 → 密码认证”顺序执行,任一层不匹配即中断;错误码可直接定位拦截层:1227 为租户白名单层,1045 为账号层(主机名不匹配或密码错误)。

如果在操作过程中遇到问题,欢迎在评论区留言交流,后续我们会持续分享更多实战运维干货,记得  关注不迷路  ,下次见~

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70047467/viewspace-3188633/ ,如需转载,请注明出处,否则将追究法律责任。

下一篇: 没有了~
请登录后发表评论 登录
全部评论
专注于企业信息科技战略咨询、数据中心规划及运维、智能化软件产品研发,致力于为金融、能源、电信、制造等行业提供智能的业务永续及流程自动化解决方案。

注册时间:2025-09-08

  • 博文量
    97
  • 访问量
    94295