Kerberos 认证体系引起的产品系统免密问题
2025-03-24 05:00:00

问题概述

客户对产品系统进行大版本升级,使用升级后的运维工具启动产品系统正常,随后的某项业务产生了数据文件,但是通过 scp 将数据文件拷贝到远程服务器时失败。客户改用升级前的运维工具启动产品系统,业务正常执行,文件成功拷贝到远程服务器。

问题排查

升级改动

在产品系统升级改造前,运维工具使用 Linux 管理类用户——通常是 root 用户——ssh 到服务器,执行启停脚本,该启动脚本切换到产品系统不同组件使用的不同 Linux 用户,执行各自的启停脚本。

为了控制权限,保障产品系统和服务器的安全,这次大版本改造,运维工具使用产品系统不同组件所需的不同 Linux 用户,分别 ssh 到服务器,直接执行各自的启停脚本。

picture1

执行失败的业务不在此次升级改造范围中。

首次排查

与客户沟通,除了测试过已经提到的业务,还测试了另一个与 scp 相关的业务,也遭遇了失败。

在产品系统的标准部署方案中,发起相关业务的组件程序所属的 Linux 用户,与远程服务器接收数据文件的用户,通过 ssh 密钥的形式配置了免密登录,以实现 scp 的无障碍使用。一般在这两个业务产生相关报错时,标准流程是建议客户检查组件程序的 Linux 用户与远程服务器用户的免密关系是否正常。本次排查一开始也按照同样的思路进行处理,客户使用终端 ssh 到相关用户进行免密测试时发现一切正常,但是执行业务还是存在问题。

客户自行使用组件所属 Linux 用户,手动执行了相关组件的启停脚本,重新启动了组件程序。然后再次测试业务时正常。此时意识到,通过运维工具启动的产品系统组件进程中,可能有某些环境变量存在问题。客户进一步使用改造前的运维工具重新启停了产品系统,最终测试业务正常执行。

在排查过程中,最需要确认的是,业务执行过程中具体的报错内容是什么?从产品系统的报错中,只能获知 scp 这一步产生问题,但是 scp 的具体报错内容并没有输出。scp 在一个业务脚本中执行,组件直接执行该业务脚本。

先拿到具体的日志。在不改动程序的代码的情况下,通过调整启动脚本和业务脚本,将 scp 产生的报错日志重定向到本地日志文件中。查看日志文件,看到了明确的认证失败的报错。

picture2

使用组件用户测试免密关系、直接运行业务脚本,这两项测试行为都是正常执行的,而组件进程调用时有问题。二者之间存在什么差异?在不信邪的情况下,直接查看 ssh 免密相关的密钥,发现根本没有配置,这是才得知客户的免密方式是 Kerberos。

更进一步,直接修改业务脚本,在其中插入 ssh -vvv,来仔细查看究竟碰到了什么认证问题。仔细排查 ssh 的 debug 日志发现,本次 ssh 连接产生了三次认证请求,分别是 Kerberos 认证、密钥认证和密码认证。密钥认证和密码认证都没有配置,重点在于 Kerberos 认证,认证失败报错信息中提示找不到一个位于 /tmp 目录下的缓存文件,文件名形如 /tmp/krb5cc_<uid>_%randomString。客户的技术专家告知,这个缓存文件是 Kerberos 的认证票据。

Kerberos

Kerberos 是一种计算机网络授权协议,用于通信各方进行身份认证。简单来说,就是客户端与服务端在可信的第三方 Kerberos 认证服务器上注册,当客户端与服务端通信时,依据各自的身份认证票据和第三方 Kerberos 认证服务器,来证明各自的身份,完成身份认证。

客户端和服务器都可以视作 Kerberos 服务的客户端,第三方 Kerberos 认证服务器简称为 KDC。当客户端向 KDC 认证时,KDC 会为该会话发送一组特定于该会话的票据返回到客户端。这个票据在整个认证流程中非常重要。

二次排查

基于 Kerberos 的票据概念,怀疑运维工具启动的组件进程与客户手动启动的组件进程之间存在差异。比如客户是通过正常流程登录,拥有相关票据,并在启动组件时继承给相关进程;而运维工具通过 ssh 登录时无法获取票据。

此时突然意识到,程序组件在启动时,也会通过 scp 获取远程服务器的数据文件,如果这一步失败,则产品系统的启动是失败的,运维工具会产生启动失败提示。而客户的产品系统启动是正常的,意味着启动时的 scp 是没有问题的。这一步是如何完成认证的呢?

既然怀疑到了进程的环境变量,那不妨看一看。使用升级前和升级后的运维工具分别启动产品系统,通过 strings /proc/<pid>/environ 查看进程持有哪些环境变量并对比。发现有问题的进程的环境变量中,出现了一个 KRB5CCNAME=FILE:/tmp/krb5cc_<uid>_%randomString

通过 man Kerberos,了解到这个环境变量指向票据的缓存文件。服务器中确实没有这个缓存文件,但是存在 /tmp/krb5cc_<uid> 形式的文件,这应该是票据缓存文件。客户的技术专家明确了这个就是票据缓存,那带有随机字符串后缀的票据缓存的生存周期,是不是与 ssh 会话关联呢?

运维工具通过 ssh 密码登录到服务器,运行对应的启动脚本后退出。一个猜测是,当运维工具 ssh 到服务器时,这种随机字符串的票据缓存是生成了的,成功启动了组件后 ssh 会话结束,对应的票据缓存也被清理,导致启动的组件进程找不到这些票据缓存,致使 ssh 认证失败。之前提到组件启动时也会进行 scp 且成功,观察启动脚本中,在拉起组件后 sleep 了一段时间,这段时间 ssh 会话依旧存活,票据缓存文件存在,组件进程完全可以在这段时间内完成 scp 操作;sleep 结束后 ssh 会话退出,票据缓存文件失效,组件进程执行后续的业务失败。

那么为什么运维工具发起的 ssh 会话会产生这种随机字符串的票据缓存?终端登录的用户的环境变量中没有指定 KRBCCNAME,所以按照默认逻辑找到了有效的 /tmp/krb5cc_<uid> 票据缓存文件。票据的处理逻辑是怎样的?为什么终端登录的用户没有指定环境变量呢?

运维工具发起的 ssh 会话是通过密码认证的,客户的技术专家告知,终端登录的 ssh 会话是通过了跳板机的认证后,由跳板机与服务器之间通过密钥完成 ssh 认证的。那么可以提出一个假设:Kerberos 认为密码认证的 ssh 会话不是完全可信的,于是给了基于 ssh 会话生存周期的票据缓存;而通过密钥认证的 ssh 会话可信,完全可以使用默认的 /tmp/krb5cc_<uid> 的票据缓存。

查找资料

基于 Kerberos、ssh、密码认证、KRB5CCNAME 等关键词,找到了 pam_krb5 这个模块。这个模块基于 Linux 的 PAM 机制,允许用户使用密码登录时,产生基于会话的票据缓存文件。

这下见到胜利的曙光了。了解这个模块的相关信息和配置方式,在客户环境上查看是否启用了这个模块,确定上述提到的问题是由这个模块产生的。

如何解决

既然一切问题来源于 KRB5CCNAME 指向特殊的票据缓存文件,那应该怎么处理呢?

  1. 用户登录时不产生这个环境变量;
  2. 用户登录后清理掉这个环境变量。

对于第一种方案,既然通过密钥认证的 ssh 会话不会产生 KRB5CCNAME,那么客户可以使用密钥来登录。运维工具的配置中提供了 ssh 密码认证和密钥认证,客户只需要调整运维工具的使用方式即可。这个方法暂时没有论证,但是可行度很高。

对于第二种方案,在用户的 profile 中配置 unset KRB5CCNAME,使得用户登录后清理掉这个环境变量,来通过默认逻辑使用 /tmp/krb5cc_<uid>,找到正常的票据缓存文件。通过现场测试,这个方案可行。

然而并不是说提出了两个方案,就可以随便选择一个的。方案之间也需要进行评审。

对于第一种方案,部署一套产品系统会使用多台服务器,这些服务器都需要配置密钥,并且在运维工具上配置。而对于同一套产品系统,可能有多份运维工具要连接,那么这些运维工具之间不应该使用同一套密钥,而是分别配置,才能保障安全。多份运维工具对应一套产品系统环境就要配置很多密钥,而客户部署有多套产品系统环境,这对于客户的运维来说压力很大,客户不一定会接受这个方案。

那采用第二种方案可以吗?笔者在测试中发现,一般环境中不会配置 pam_krb5 模块;客户作为集团业务部门,服务器环境都是集团统一的,由 IT 部门和安全部门进行配置的。由此可以推测,这种安全相关的模块应该是集团安全部门的要求。如果直接 unset 掉环境变量,那么这个模块的安全意义也消失了,集团安全部门不一定会接受。这就是客户作为集团业务部门和安全部门之间的交涉了。

所以提出了各种解决方案,也需要产品系统提供方和客户进行会议讨论,找到双方乃至第三方——集团安全部门——都能接受的方案。

7 WHY

  1. 为什么业务执行失败?因为业务中 scp 认证失败。
  2. 为什么 scp 认证失败?因为 scp 使用 Kerberos 认证失败。
  3. 为什么 Kerberos 认证失败?因为所需的票据缓存文件不存在。
  4. 为什么票据缓存文件不存在?因为进程所需的票据缓存文件的生存周期与 ssh 会话挂钩。
  5. 为什么会挂钩?因为 ssh 会话指定了 KRB5CCNAME 环境变量。
  6. 为什么会产生这个环境变量?因为 ssh 认证体系中引入了 pam_krb5 模块。
  7. 为什么要引入这个模块?因为客户的安全部门对于 ssh 安全的要求。