Jenkins Git 子模块拉取失败: UNPROTECTED PRIVATE KEY FILE / bad permissions

Jenkins Git 子模块拉取失败: UNPROTECTED PRIVATE KEY FILE / bad permissions

一、先看现象:子模块拉取阶段报 bad permissions

这是一类很容易被误判成 Git 仓库权限、Jenkins 凭证或网络问题的构建失败。

环境抽象如下:

项目 示例
CI Jenkins Freestyle Job
构建节点 Linux Jenkins agent
构建用户 <build-user>
主仓库协议 HTTPS
子模块协议 SSH
失败命令 git submodule update --init --recursive <submodule>

Jenkins 控制台里的关键报错是:

git submodule update --init --recursive <submodule>

Cloning into '<workspace>/<submodule>'...
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0664 for '/home/<build-user>/.ssh/id_rsa' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/home/<build-user>/.ssh/id_rsa": bad permissions
git@git.example.com: Permission denied (publickey).
fatal: Could not read from remote repository.

结论先说:这不是 Git 服务端拒绝了一个正确的 key,而是本机 SSH 客户端在发起认证前就把私钥忽略了。原因是私钥权限太宽。

Jenkins Git 子模块 SSH key 权限排查流程

上图按 ① 到 ⑥ 展示完整判断链路:主仓库 checkout 成功不代表 SSH 子模块认证也正常,真正的断点在 OpenSSH 对私钥文件权限的本地安全检查。

Jenkins 子模块拉取失败链路动图

二、拆认证链路:主仓库 HTTPS,子模块 SSH

这次排查里最容易绕进去的点是:同一个 Jenkins job 的其他构建能成功,主仓库也能正常拉取,为什么只有某一次失败?

原因是主仓库和子模块走的不是同一条认证链路:

主仓库: https://git.example.com/group/project.git
子模块: git@git.example.com:group/submodule.git

主仓库通过 Jenkins 配置的 HTTPS credential 拉取,不会使用节点上的 /home/<build-user>/.ssh/id_rsa

但子模块是 SSH 地址,执行 git submodule update 时会触发 SSH 认证,这时才会碰到节点本地的私钥文件权限。

三、从 Jenkins 日志确认失败阶段

先不要急着改 Jenkins 凭证,也不要先怀疑网络。第一步是看失败发生在哪个阶段。

如果日志里主仓库 checkout 已经完成,后面才出现:

git submodule update --init --recursive <submodule>

并且紧跟 UNPROTECTED PRIVATE KEY FILE / This private key will be ignored,排查方向就应该转到构建节点的 SSH key 文件权限。

这类报错的关键词是:

UNPROTECTED PRIVATE KEY FILE
bad permissions
This private key will be ignored
Permission denied (publickey)

其中最关键的是 This private key will be ignored。它说明 SSH 客户端还没真正拿这把 key 去服务端认证。

四、登录构建节点检查私钥权限

登录 Jenkins 实际执行构建的 agent 节点,用构建用户对应的路径检查 SSH 目录和私钥权限:

stat -c '%U:%G %a %n' \
  /home/<build-user> \
  /home/<build-user>/.ssh \
  /home/<build-user>/.ssh/id_rsa \
  /home/<build-user>/.ssh/id_rsa.pub 2>/dev/null || true

异常输出类似:

<build-user>:<build-user> 755 /home/<build-user>
<build-user>:<build-user> 700 /home/<build-user>/.ssh
<build-user>:<build-user> 664 /home/<build-user>/.ssh/id_rsa
<build-user>:<build-user> 664 /home/<build-user>/.ssh/id_rsa.pub

这里真正有问题的是私钥:

/home/<build-user>/.ssh/id_rsa  ->  664

SSH 私钥不能被 group/others 读写。常见安全权限是:

~/.ssh             700
~/.ssh/id_rsa      600
~/.ssh/id_rsa.pub  644 或 664 通常不影响认证

五、用 Jenkins 实际用户复现

不要只用 root 测试。Jenkins agent 实际以哪个用户跑,就用哪个用户复现。

示例:

sudo -n -u <build-user> \
  GIT_SSH_COMMAND='ssh -i /home/<build-user>/.ssh/id_rsa -o BatchMode=yes -o StrictHostKeyChecking=no' \
  git ls-remote git@git.example.com:group/submodule.git HEAD

如果这里复现出同样的 bad permissions,就能把问题从“Jenkins 可能有问题”收敛成“构建节点本地 SSH 私钥权限不符合 OpenSSH 要求”。

六、修复:只收敛私钥权限

只改私钥权限:

chmod 600 /home/<build-user>/.ssh/id_rsa

如果属主也不对,再修正属主:

chown <build-user>:<build-user> /home/<build-user>/.ssh/id_rsa

通常不要顺手扩大修改范围。.ssh 目录、known_hosts、Jenkins job 配置、Git 仓库权限都不是这次错误的第一修复对象。

修复后再次验证:

stat -c '%U:%G %a %n' /home/<build-user>/.ssh/id_rsa

sudo -n -u <build-user> \
  GIT_SSH_COMMAND='ssh -i /home/<build-user>/.ssh/id_rsa -o BatchMode=yes -o StrictHostKeyChecking=no' \
  git ls-remote git@git.example.com:group/submodule.git HEAD

预期结果:

<build-user>:<build-user> 600 /home/<build-user>/.ssh/id_rsa
<commit-sha>  HEAD

只要 git ls-remote 能返回 HEAD,就说明子模块 SSH 认证链路已经恢复。

七、解释疑点:为什么其他构建可能没问题

同一个 Jenkins job 里,其他构建成功并不一定能反证这个根因。

先看这张对比图:

成功构建和失败构建的关键差异

关键点只有一个:成功构建没有在 Jenkins checkout 阶段直接使用原始私钥拉 SSH 子模块,而失败构建在 shell 脚本启动前就触发了这一步。

成功构建里可能先进入构建脚本:

[setup-vendor] using sanitized copy of /home/<build-user>/.ssh/id_rsa (perm normalized to 0600)

这种脚本会复制一份权限正确的 key,因此绕过了原始私钥 0664 的问题。也就是说,“后面的构建脚本能自我修正 key 权限”,不等于“Jenkins checkout 阶段也能自我修正”。

失败构建则是在 Jenkins Git 插件 checkout 阶段直接执行:

git submodule update --init --recursive <submodule>

这一步发生在构建脚本前,还没来得及做 key 权限标准化,所以直接失败。

八、排查判断顺序

遇到 Permission denied (publickey) 不要只看最后一行。建议按这个顺序判断:

  1. 是否出现 UNPROTECTED PRIVATE KEY FILE
  2. 是否出现 This private key will be ignored
  3. 失败命令是不是 git submodule update 或 SSH 方式的 git clone
  4. 主仓库和子模块是否使用了不同协议。
  5. Jenkins job 是插件 checkout 失败,还是 shell 构建脚本内部失败。
  6. 用 Jenkins 实际运行用户手动执行 git ls-remote 复现。

如果第 1 和第 2 条同时出现,优先修本地私钥权限。

九、快速参考

检查权限:

stat -c '%U:%G %a %n' \
  /home/<build-user>/.ssh \
  /home/<build-user>/.ssh/id_rsa

修复权限:

chmod 600 /home/<build-user>/.ssh/id_rsa
chown <build-user>:<build-user> /home/<build-user>/.ssh/id_rsa

用构建用户验证 Git SSH:

sudo -n -u <build-user> \
  GIT_SSH_COMMAND='ssh -i /home/<build-user>/.ssh/id_rsa -o BatchMode=yes -o StrictHostKeyChecking=no' \
  git ls-remote git@git.example.com:group/submodule.git HEAD

看到下面这种输出,说明认证已恢复:

<commit-sha>  HEAD

核心判断:

This private key will be ignored

这句话出现时,先修 key 权限,不要先改 Jenkins credential、仓库权限或网络配置。

posted @ 2026-07-22 12:14  Hello_worlds  阅读(5)  评论(0)    收藏  举报