GitLab 诡异问题排查:“Fingerprint has already been taken” 与幽灵 SSH Key
当 GitLab 提示“Project not found”但你明明有权限,且添加 SSH Key 时报错“指纹已存在”,该如何通过 Rails Console 彻底定位并清除“幽灵”密钥?本文记录一次完整的排查过程。
在日常的 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.
初步排查:
- 检查权限: 确认该账号在项目中确实是
Maintainer角色(排除 Guest 角色导致的问题)。 - 检查 URL: 确认 Git URL 大小写无误。
- 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 同时存在于两个地方!
- 它绑定在一个旧的/异常的用户账号下。
- 它同时被注册为了一个 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 权限的“灵然”问题时:
- 不要只看 Web 界面,界面显示的仅仅是冰山一角。
Fingerprint has already been taken是最有价值的线索。- 作为管理员,GitLab Rails Console 是最强大的调试工具,可以解决 UI 上无法处理的数据冲突。
- 搜索数据库时,尽量使用特征字符串(如邮箱后缀)而非整段 Key,以避免格式干扰。
警告:
gitlab-rails console拥有直接操作数据库的权限,执行destroy操作前请务必确认对象 ID 无误,建议操作前对数据库进行备份。