Paimon 1.3.1 报 Proxy user is not supported:Kyuubi + Kerberos 集成排查与源码级定位

Paimon 1.3.1 报 Proxy user is not supported:Kyuubi + Kerberos 集成排查与源码级定位

副标题:一次 Spark 4.x 新版本栈的生产验证记录

关键词:Paimon Proxy user is not supported、Kyuubi Kerberos、Paimon 1.3.1、HadoopSecuredFileSystem、KerberosLoginProvider、Spark Catalog Kerberos、UnsupportedOperationException、Paimon 集成、impersonation、Spark 4.0 升级

目录

  • 一、问题现象
  • 二、先看清楚:这是哪套栈的问题
  • [三、第一反应:是不是 keytab 没配?](#三、第一反应:是不是 keytab 没配? "#%E4%B8%89%E7%AC%AC%E4%B8%80%E5%8F%8D%E5%BA%94%E6%98%AF%E4%B8%8D%E6%98%AF-keytab-%E6%B2%A1%E9%85%8D")
  • [四、核心变化:1.3.1 多了一层「安全加固」](#四、核心变化:1.3.1 多了一层「安全加固」 "#%E5%9B%9B%E6%A0%B8%E5%BF%83%E5%8F%98%E5%8C%96131-%E5%A4%9A%E4%BA%86%E4%B8%80%E5%B1%82%E5%AE%89%E5%85%A8%E5%8A%A0%E5%9B%BA")
  • [五、源码:KerberosLoginProvider 的三条分支](#五、源码:KerberosLoginProvider 的三条分支 "#%E4%BA%94%E6%BA%90%E7%A0%81kerberosloginprovider-%E7%9A%84%E4%B8%89%E6%9D%A1%E5%88%86%E6%94%AF")
  • 六、为什么必然走到第三条分支
  • [七、一个隐蔽的坑:isLegal() 永远返回 true](#七、一个隐蔽的坑:isLegal() 永远返回 true "#%E4%B8%83%E4%B8%80%E4%B8%AA%E9%9A%90%E8%94%BD%E7%9A%84%E5%9D%91islegal-%E6%B0%B8%E8%BF%9C%E8%BF%94%E5%9B%9E-true")
  • [八、解决方案:补上 principal 与 keytab](#八、解决方案:补上 principal 与 keytab "#%E5%85%AB%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E8%A1%A5%E4%B8%8A-principal-%E4%B8%8E-keytab")
  • 九、为什么有两套配置键名
  • 十、验证方法
  • 十一、升级视角:新版本栈还要检查什么
  • 十二、总结

一、问题现象

1.1 报错原文

在 Kyuubi + Spark 环境里使用 Paimon 时,任何 SQL 操作都会失败:

css 复制代码
java.lang.UnsupportedOperationException: Proxy user is not supported
    at org.apache.paimon.security.KerberosLoginProvider.throwProxyUserNotSupported(KerberosLoginProvider.java:101)
    at org.apache.paimon.security.KerberosLoginProvider.isLoginPossible(KerberosLoginProvider.java:76)
    at org.apache.paimon.security.HadoopModule.install(HadoopModule.java:55)
    at org.apache.paimon.fs.hadoop.HadoopSecuredFileSystem.trySecureFileSystem(HadoopSecuredFileSystem.java:205)
    at org.apache.paimon.fs.hadoop.HadoopFileIO.createFileSystem(HadoopFileIO.java:207)
    at org.apache.paimon.fs.hadoop.HadoopFileIO.getFileSystem(HadoopFileIO.java:175)
    at org.apache.paimon.fs.hadoop.HadoopFileIO.getFileStatus(HadoopFileIO.java:109)
    at org.apache.paimon.catalog.CatalogFactory.createUnwrappedCatalog(CatalogFactory.java:98)
    at org.apache.paimon.catalog.CatalogFactory.createCatalog(CatalogFactory.java:71)
    at org.apache.paimon.spark.SparkCatalog.initialize(SparkCatalog.java:137)
    at org.apache.spark.sql.connector.catalog.CatalogManager.loadCatalog(Catalogs.scala:65)

1.2 三个关键特征

  • 错误发生在 Paimon Catalog 初始化阶段,不是后续的表读写
  • 精确指向 KerberosLoginProvider.throwProxyUserNotSupported() 第 101 行
  • 调用链清晰:createFileSystem → trySecureFileSystem → HadoopModule.install → isLoginPossible

二、先看清楚:这是哪套栈的问题

这一点必须先讲清楚,否则很容易误判成"现网出故障了"。

2.1 两套栈对照

现网生产栈 本次验证栈
Kyuubi 1.10.2 1.11.x(新版本)
Spark 3.4.1 / 3.5.5 4.0.x
Paimon 旧版本 1.3.1
Hudi / Ranger 插件 有 有
状态 稳定运行 生产验证中,暴露此问题

2.2 所以这不是"线上事故"

现网跑得好好的,问题只出现在新版本栈的验证环境里。

这件事的价值反而更大:

如果你正在规划 Kyuubi / Spark 的版本升级,并且依赖 Paimon、Hudi、Ranger 这些插件,这个坑你大概率会踩到。

2.3 为什么升级后会冒出来

因为升级不是一件事,是四件事同时发生:

复制代码
Kyuubi 升版本    → 认证与 impersonation 实现变化
Spark 升 4.x     → Catalog 插件模型、依赖体系变化
Paimon 升 1.3.1  → 新增安全加固层(本文主角)
插件同步升级      → Hudi / Ranger 各自的行为也在变

任何一个环节的安全性"变严",都可能和另一个环节的"代理身份"撞上。

本文记录的,就是其中撞得最直接的一次。


三、第一反应:是不是 keytab 没配?

看到 Kerberos 相关报错,第一反应通常是去查 keytab。

但有个很关键的对照事实:

同样的代码,在 Paimon 1.0.0 上跑得好好的。现网那套栈也没这个问题。

如果 keytab 有问题,旧版本也该失败。所以问题不在"认证材料缺失",而在版本行为变了。

对比 HadoopFileIO.createFileSystem() 的实现:

Paimon 1.0.0:

arduino 复制代码
protected FileSystem createFileSystem(Path path) throws IOException {
    return path.getFileSystem(hadoopConf.get());   // 直接取,不做任何安全包装
}

Paimon 1.3.1:

ini 复制代码
protected FileSystem createFileSystem(Path path) throws IOException {
    Configuration conf = hadoopConf.get();
    FileSystem fileSystem = path.getFileSystem(conf);
    // ★ 新增:安全加固层
    fileSystem = HadoopSecuredFileSystem.trySecureFileSystem(fileSystem, options, conf);
    return fileSystem;
}

就是这一行 trySecureFileSystem,改变了整个行为。


四、核心变化:1.3.1 多了一层「安全加固」

trySecureFileSystem 的逻辑:

arduino 复制代码
public static FileSystem trySecureFileSystem(
        FileSystem fileSystem, Options options, Configuration configuration) throws IOException {
    SecurityConfiguration config = new SecurityConfiguration(options);
    if (config.isLegal()) {
        LOG.info("Hadoop security configuration is legal, use the secured FileSystem.");
        HadoopModule module = new HadoopModule(config, configuration);
        module.install();                      // ← 这里会走到 KerberosLoginProvider
        UserGroupInformation ugi = UserGroupInformation.getLoginUser();
        return new HadoopSecuredFileSystem(fileSystem, ugi);
    } else {
        LOG.info("Hadoop security configuration is illegal, use the original FileSystem.");
        return fileSystem;
    }
}

调用顺序:

markdown 复制代码
1. 从 options 读取 principal / keytab → SecurityConfiguration
2. 判断配置是否"合法" → isLegal()
3. 合法 → HadoopModule.install()
4. HadoopModule 创建 KerberosLoginProvider
5. isLoginPossible() 检查当前 UGI 的认证方式

第 5 步就是报错的地方。


五、源码:KerberosLoginProvider 的三条分支

java 复制代码
public boolean isLoginPossible() throws IOException {
    if (!UserGroupInformation.isSecurityEnabled()) {
        return false;                                  // 未开安全认证,直接跳过
    }
​
    UserGroupInformation currentUser = UserGroupInformation.getCurrentUser();
​
    if (principal != null) {                           // 分支 1
        return true;                                   // 有 principal → 允许 keytab 登录 ✅
    } else if (!isProxyUser(currentUser)) {            // 分支 2
        if (useTicketCache && currentUser.hasKerberosCredentials()) {
            return true;                               // 非 PROXY → 允许 ticket cache ✅
        }
    } else {                                           // 分支 3
        throwProxyUserNotSupported();                  // PROXY + 无 principal → ❌ 抛异常
    }
    return false;
}

画成决策树:

复制代码
是否有 principal?
├─ 有 → 走 keytab 登录 ✅
└─ 没有
    ├─ 当前 UGI 是 PROXY 用户? → 是 → 抛异常 ❌
    └─ 不是 → 尝试 ticket cache ✅

结论很明确:只要「没有 principal」且「当前是 PROXY 用户」,就一定抛异常。


六、为什么必然走到第三条分支

6.1 Kyuubi 天然产生 PROXY 用户

Kyuubi 做多租户的核心机制就是 impersonation ------服务进程以 hive/kyuubi 身份登录,然后代理真实客户端用户去访问 HDFS。

这种代理状态下,当前线程的 UserGroupInformation 的认证方式被标记为:

复制代码
UserGroupInformation.AuthenticationMethod.PROXY

这就是 isProxyUser(currentUser) 返回 true 的原因。

6.2 没有 principal → 必走分支 3

如果 spark.sql.catalog.paimon.security.kerberos.login.principal 没配,principal 就是 null。

于是:

ini 复制代码
principal == null
    +
currentUser 是 PROXY
    ↓
分支 3:throwProxyUserNotSupported()

6.3 完整因果链

vbnet 复制代码
升级到 Paimon 1.3.1(新增安全加固层)
        ↓
trySecureFileSystem → HadoopModule.install
        ↓
KerberosLoginProvider.isLoginPossible()
        ↓
principal 未配置 + UGI 是 PROXY
        ↓
UnsupportedOperationException: Proxy user is not supported

注意:这不是 Paimon 的 bug,也不是 Kyuubi 的 bug。

这是两个"更安全"的设计撞在了一起:

  • Kyuubi 的 impersonation 让多租户成为可能 → 产生 PROXY 身份
  • Paimon 1.3.1 的安全加固拒绝"无凭证的代理身份" → 直接抛异常

单独看,两边都没做错;放在一起,就都不工作了。


七、一个隐蔽的坑:isLegal() 永远返回 true

这是最容易让人困惑的地方。

既然 principal/keytab 都没配,isLegal() 为什么还返回 true?

看源码:

arduino 复制代码
public boolean isLegal() {
    // 1. 检查 keytab 和 principal 是否成对存在
    if (StringUtils.isNullOrWhitespaceOnly(keytab)
            != StringUtils.isNullOrWhitespaceOnly(principal)) {
        return false;              // 只有一个非空 → 不合法
    }
​
    // 2. 检查 keytab 文件是否存在
    if (!StringUtils.isNullOrWhitespaceOnly(keytab)) {
        File keytabFile = new File(keytab);
        return keytabFile.exists() && keytabFile.isFile() && keytabFile.canRead();
    }
​
    return true;                   // ★ 两个都为空,仍然返回 true
}

逻辑是「成对检查」,不是「必须存在」。

两个都是 null 时,第一个判断 null != null 为 false,不返回;第二个判断跳过;最后 return true。

所以:

scss 复制代码
两个都没配 → isLegal() = true → 走安全通道 → 然后炸在 isLoginPossible()

如果没有这一行 return true,没配的时候会直接跳过安全层,回到 1.0.0 的行为,就不会报错。

这是一个典型的「防御性代码只防了一半」:它假设「不配 = 不启用」,但没考虑「不配 + 代理身份」这种组合。

这一点对升级评估特别重要:如果你以为"不配就是走老路径",就会低估升级风险。


八、解决方案:补上 principal 与 keytab

既然分支 1 是「有 principal 就允许」,那就把 principal/keytab 显式提供给 Paimon Catalog。

8.1 完整配置

ini 复制代码
################################### Kyuubi 自身认证 ###################################
kyuubi.authentication=KERBEROS
kyuubi.kinit.principal=kyuubi/node01.example.com@EXAMPLE.COM
kyuubi.kinit.keytab=/opt/kyuubi/kyuubi.keytab
​
################################### Spark 会话扩展 ###################################
spark.sql.extensions=org.apache.paimon.spark.PaimonSparkSessionExtensions
​
################################### Paimon Catalog ###################################
spark.sql.catalog.paimon=org.apache.paimon.spark.SparkCatalog
spark.sql.catalog.paimon.warehouse=hdfs://nameservice1/tmp/paimon
​
################################### ★ 关键:Paimon 的 Kerberos 配置 ###################################
spark.sql.catalog.paimon.security.kerberos.login.principal=kyuubi/node01.example.com@EXAMPLE.COM
spark.sql.catalog.paimon.security.kerberos.login.keytab=/opt/kyuubi/kyuubi.keytab

8.2 三个必须注意的点

注意点 说明
principal 必须与 keytab 内一致 用 klist -kt <keytab> 核对,大小写、FQDN 都要一致
keytab 路径是「当前机器真实路径」 不是容器内路径,Kyuubi 进程必须有读权限
改完必须重启 Kyuubi kyuubi-defaults.conf 是启动时读取的

8.3 核验 keytab

bash 复制代码
# 1. 确认 keytab 里的 principal 列表
klist -kt /opt/kyuubi/kyuubi.keytab
​
# 2. 确认文件可读
ls -l /opt/kyuubi/kyuubi.keytab
​
# 3. 确认 FQDN
hostname -f
​
# 4. 用 keytab 试登录
kinit -kt /opt/kyuubi/kyuubi.keytab kyuubi/node01.example.com@EXAMPLE.COM
klist

九、为什么有两套配置键名

配置里你会看到两组键:

ini 复制代码
# 主键(优先级 1)
spark.sql.catalog.paimon.security.kerberos.login.principal=...
spark.sql.catalog.paimon.security.kerberos.login.keytab=...
​
# 回退键(优先级 2)
spark.sql.catalog.paimon.hadoop.security.principal=...
spark.sql.catalog.paimon.hadoop.security.keytab=...

原因在 SecurityConfiguration 的 ConfigOption 定义:

arduino 复制代码
public static final ConfigOption<String> KERBEROS_LOGIN_KEYTAB =
        key("security.kerberos.login.keytab")          // 优先级 1
                .noDefaultValue()
                .withFallbackKeys("security.keytab");  // 优先级 2
​
public static final ConfigOption<String> KERBEROS_LOGIN_PRINCIPAL =
        key("security.kerberos.login.principal")       // 优先级 1
                .noDefaultValue()
                .withFallbackKeys("security.principal"); // 优先级 2

注意:这里定义的是相对键 (security.kerberos.login.keytab),前面会被加上 Catalog 前缀 spark.sql.catalog.paimon.。

也就是说,实际生效的是:

复制代码
spark.sql.catalog.paimon.security.kerberos.login.keytab      ← 真正生效
spark.sql.catalog.paimon.security.kerberos.login.principal   ← 真正生效

而 spark.sql.catalog.paimon.hadoop.security.* 这组,在没有自定义 Options 覆盖的情况下,不一定会被读到。

这是一个实践中常见的误区:网上很多示例写的是 hadoop.security.*,照抄过来不生效。

保险做法:两组都配,主键优先。多写两行不会有副作用。


十、验证方法

10.1 看日志

启动后正常应该出现:

arduino 复制代码
Hadoop security configuration is legal, use the secured FileSystem.

如果出现这两类,说明配置没被识别:

csharp 复制代码
Proxy user is not supported                              ← principal 未生效
Keytab file not found / not readable                      ← 路径或权限问题

10.2 用 SET -v 确认配置真的加载了

ini 复制代码
SET -v;

应该能看到:

bash 复制代码
spark.sql.catalog.paimon                                       org.apache.paimon.spark.SparkCatalog
spark.sql.catalog.paimon.security.kerberos.login.principal     kyuubi/node01.example.com@EXAMPLE.COM
spark.sql.catalog.paimon.security.kerberos.login.keytab        /opt/kyuubi/kyuubi.keytab

这一条很关键:很多人改完配置重启了,但配置没进到 engine 里,靠这个能一眼看出来。

10.3 实际建表验证

sql 复制代码
CREATE DATABASE IF NOT EXISTS paimon_test;
CREATE TABLE paimon_test.t1 (id INT, name STRING);
INSERT INTO paimon_test.t1 VALUES (1, 'a');
SELECT * FROM paimon_test.t1;

十一、升级视角:新版本栈还要检查什么

这次验证的意义不止于修好一个配置。

如果你也在规划 Kyuubi / Spark 大版本升级 + 插件同步升级,建议按下面这张表逐项过:

检查项 为什么 怎么验证
Catalog 级 Kerberos 配置 新版插件可能新增安全层,要求显式提供 principal 各 Catalog 的 security.* 键是否配全
impersonation 是否仍被所有插件支持 有的插件开始拒绝 PROXY 身份 用代理用户跑一遍建表/写入
配置键名前缀变化 新版本可能改变相对键与前缀拼接规则 SET -v 核对实际生效值
Hudi / Ranger 插件的等价变化 同一批升级,行为可能同步收紧 各自的 Catalog 与权限路径逐一验证
keytab 分发与权限 新组件可能以不同用户启动 检查文件属主与读权限
回退方案 新栈验证不通过时能否快速切回 保留旧引擎池 / 双栈并行

一个判断原则

升级不是"换个 jar",是"换一套默认行为"。

旧版本能跑,往往不是因为配置对,而是因为你恰好没触发那条新路径。


十二、总结

12.1 五个判断

① 「认证失败」不等于「认证材料缺失」

这次的根因不是 keytab 没配,而是 Paimon 主动放弃了代理身份这条路径。

看到 Kerberos 报错,先分清是"凭证问题"还是"路径问题"。

② 两个「更安全」的设计,撞在一起就都不工作

Kyuubi 的 impersonation 是安全特性,Paimon 1.3.1 的安全加固也是安全特性。

但前者产生 PROXY 身份,后者拒绝 PROXY 身份。

③ 防御代码写了一半,比不写更难查

isLegal() 在「两个都没配」时返回 true,于是所有未配置的环境都会进入安全通道,然后炸在下一步。

如果它返回 false,就会退回旧行为,问题根本不会出现。

④ 遇到奇怪的报错,去看那一行的上下文

throwProxyUserNotSupported() 只有三行业务逻辑,但它背后是一个完整的三分支决策树。

报错信息往往只说了「否」,没说「为什么否」------为什么否,要自己从源码里读出来。

⑤ 新栈验证的价值,在于"提前暴露"

现网那套 Kyuubi 1.10.2 + Spark 3.4.1/3.5.5 是稳定的。

但这个稳定,有一部分是"版本恰好没触发"带来的。

升级前把这些坑跑一遍,比升级后被业务追着问要划算得多。

12.2 排查 Checklist

检查项 方法
Paimon 版本 SELECT version() / 查 jar 版本
当前 UGI 认证方式 日志中搜 AuthenticationMethod
principal 是否配置 SET -v
keytab 路径是否真实存在 ls -l <keytab>
keytab 内 principal 是否一致 klist -kt <keytab>
能否用 keytab 登录 kinit -kt ...
配置是否生效 日志中搜 security configuration is legal
Catalog 前缀是否正确 必须是 spark.sql.catalog.<name>.security.kerberos.login.*

参考

  • Apache Paimon:HadoopSecuredFileSystem、KerberosLoginProvider、SecurityConfiguration(1.3.1)
  • Apache Paimon:org.apache.paimon.options.ConfigOption#withFallbackKeys
  • Hadoop:UserGroupInformation.AuthenticationMethod.PROXY、impersonation 机制
  • Apache Kyuubi:Kerberos 认证与 impersonation 配置
相关推荐
财迅通Ai2 小时前
远信储能(9621.HK)招股开启,AI+储能跑出新范式
大数据·人工智能·远信储能
一只鹿鹿鹿2 小时前
智算算力、AI-芯片与产品解析(PPT文件)
大数据·运维·网络·人工智能·物联网·1024程序员节
Fluxproxy4 小时前
大模型批量数据采集:AI 自动化工作流搭建实战
大数据·人工智能·自动化
sun153690275724 小时前
超声波风速风向传感器:测量原理、通讯协议与应用场景详解
大数据
国科安芯4 小时前
商业光学遥感载荷焦平面供电链的低噪声稳压器研究综述——以国科安芯ASP7A84AS型3A低压差线性稳压器为器件级参照
大数据·网络·嵌入式硬件·平面·卫星·商业航天·抗辐射
米软科技4 小时前
数字化转型:多元化集团如何构建高效的AI数字化中台?
大数据·人工智能
Leo.yuan5 小时前
Dora企业级Data Agent应用指南:存量BI复用、私有化部署与权限管理
大数据·人工智能
千里念行客2406 小时前
南都电源在甘肃玉门的200MW/800MWh独立共享储能电站建成
大数据·科技·能源
程序员梅雨7 小时前
【从零开始学习数据仓库 | 第二篇】: 拉链表 & 数仓分层
大数据·数据仓库·学习