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 客户端在发起认证前就把私钥忽略了。原因是私钥权限太宽。

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

二、拆认证链路:主仓库 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) 不要只看最后一行。建议按这个顺序判断:
- 是否出现
UNPROTECTED PRIVATE KEY FILE。 - 是否出现
This private key will be ignored。 - 失败命令是不是
git submodule update或 SSH 方式的git clone。 - 主仓库和子模块是否使用了不同协议。
- Jenkins job 是插件 checkout 失败,还是 shell 构建脚本内部失败。
- 用 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、仓库权限或网络配置。

浙公网安备 33010602011771号