GitLab 诡异问题排查:“Fingerprint has already been taken” 与幽灵 SSH Key

当 GitLab 提示“Project not found”但你明明有权限,且添加 SSH Key 时报错“指纹已存在”,该如何通过 Rails Console 彻底定位并清除“幽灵”密钥?本文记录一次完整的排查过程。

GitLab 诡异问题排查:“Fingerprint has already been taken” 与幽灵 SSH Key

在日常的 DevOps 维护中,GitLab 的权限问题通常很好解决,但偶尔会遇到一些“甚至在欺骗你”的诡异现象。

最近处理了一个非常典型的案例:一位同事拥有项目的 Maintainer 权限,网页端浏览代码一切正常,但在终端执行 git clone 时却死活下载不下来,甚至在尝试重新添加 SSH Key 时报错 Key 已存在。

这篇文章记录了从现象分析到使用 GitLab Rails Console 进行“上帝视角”修复的全过程。

1. 诡异的现象

同事在终端尝试拉取代码时,GitLab 返回了经典的“无权限”错误:

remote: The project you were looking for could not be found or you don't have permission to view it.
fatal: Could not read from remote repository.

初步排查:

  1. 检查权限: 确认该账号在项目中确实是 Maintainer 角色(排除 Guest 角色导致的问题)。
  2. 检查 URL: 确认 Git URL 大小写无误。
  3. SSH 测试: 使用 ssh -T git@gitlab.example.com 测试,返回 Welcome to GitLab, @User!

矛盾点出现了: SSH 认证通过了(说明 Key 是对的),GitLab 账号也有权限,但服务器却拒绝提供代码。

2. 深入挖掘:SSH 详细日志

为了搞清楚 SSH 交互过程中到底发生了什么,我们开启了详细调试模式:

GIT_SSH_COMMAND="ssh -vvv" git clone git@gitlab.example.com:group/project.git

在冗长的日志中,我们发现了端倪:

  • SSH 握手成功。
  • 服务器接受了客户端提供的 Public Key。
  • 但在最后一步,GitLab 内部逻辑返回了“找不到项目”。

这通常暗示了一个隐蔽的问题:GitLab 识别到的“这个 Key 的主人”,并不是我们认为的“这个账号”。

3. 关键线索:指纹已存在

为了验证猜想,我们尝试将同事电脑上的公钥(id_rsa.pub)手动添加到他的 GitLab 个人设置中。结果弹出了决定性的错误提示:

The form contains the following error:
Fingerprint sha256 has already been taken

真相大白: 这把 SSH Key 已经存在于 GitLab 数据库中了,而且被绑定在了其他地方(可能是另一个旧账号,或者是作为一个全局的 Deploy Key)。

因为 SSH 协议只认 Key 不认人,GitLab 看到这把 Key,将其识别为“旧身份”或“部署密钥”,而这个“旧身份”并没有当前新项目的权限,所以报错“找不到项目”。

4. 终极大招:GitLab Rails Console

由于是私有化部署的 GitLab,作为超级管理员,我们无法在网页端搜索“这把 Key 到底在哪”(UI 上无法通过指纹反查 Key 的归属)。

我们需要登录 GitLab 服务器,使用 Rails Console 直接查询数据库。

步骤一:进入控制台

在服务器终端执行:

sudo gitlab-rails console

步骤二:定位“幽灵” Key

起初,我们尝试直接用 Key 的字符串进行模糊搜索,但因为 SSH Key 字符串过长,复制粘贴容易出现格式问题(例如换行符或编码错误),导致搜索失败。

更稳健的搜索策略:
观察公钥的结尾,通常会有 username@device.local 形式的注释。我们可以只搜索这个独特的后缀。

在 Console 中执行以下 Ruby 脚本:

# 定义搜索关键词(Key 结尾的注释邮箱)
search_term = "username@device.local"

puts "🔍 正在搜索包含 '#{search_term}' 的密钥..."

# 1. 在用户密钥 (User Key) 中查找
user_key = Key.where("key LIKE ?", "%#{search_term}%").first

if user_key
  puts "🔴 [找到了!] 这是一个用户 Key"
  puts "   所属用户: #{user_key.user.username}"
  puts "   Key ID:  #{user_key.id}"
else
  puts "⚪️ 在用户 Key 中没找到..."
end

# 2. 在部署密钥 (Deploy Key) 中查找
deploy_key = DeployKey.where("key LIKE ?", "%#{search_term}%").first

if deploy_key
  puts "🟠 [找到了!] 这是一个部署 Key"
  puts "   Key ID:    #{deploy_key.id}"
else
  puts "⚪️ 在部署 Key 中没找到..."
end

步骤三:发现问题与清理

脚本运行结果令人惊讶:这把 Key 同时存在于两个地方!

  1. 它绑定在一个旧的/异常的用户账号下。
  2. 它同时被注册为了一个 Deploy Key。

这就是导致权限混乱的根源。我们需要将其从数据库中彻底移除。

执行删除命令:

# 假设查到的 ID 为 99
# 删除被占用的用户 Key 记录
Key.find(99).destroy

# 删除被占用的 Deploy Key 记录
# 注意:即使 ID 相同,它们属于不同的表,需要分别删除
DeployKey.find(99).destroy

5. 结果

在 Rails Console 中看到 destroy 返回成功信息后,退出控制台。

回到 GitLab 网页端,再次为同事添加这把 SSH Key,立刻成功通过。再次执行 git clone,代码顺利下载。

总结

当遇到 GitLab SSH 权限的“灵然”问题时:

  1. 不要只看 Web 界面,界面显示的仅仅是冰山一角。
  2. Fingerprint has already been taken 是最有价值的线索。
  3. 作为管理员,GitLab Rails Console 是最强大的调试工具,可以解决 UI 上无法处理的数据冲突。
  4. 搜索数据库时,尽量使用特征字符串(如邮箱后缀)而非整段 Key,以避免格式干扰。

警告: gitlab-rails console 拥有直接操作数据库的权限,执行 destroy 操作前请务必确认对象 ID 无误,建议操作前对数据库进行备份。