
1. 为什么Git凭证值得单独占一篇笔记先说一个我自己遇到过的真实场景。某天早上同事发消息说构建挂了报错只有两行fatal: Authentication failed for https://git.example.com/...。他的第一反应是打开 Jenkinsfile把 checkout 的 branch 从main改成master又改回来重启构建三次问题当然还在。真正的原因是他三天前在代码托管平台上把自己的个人访问令牌Personal Access Token重置了一次而 Jenkins 里存的那份还是旧的。这件事说明一个很朴素的道理在 Jenkins 里Git 凭证不是 Job 的一个配置项而是一个独立存在的、有自己生命周期的对象。它有自己的存储位置、自己的作用域、自己的权限模型还有自己的过期时间。Job 只是引用它引用关系的断裂往往发生在 Job 看不到的地方。所以理解凭证的模型比背几条命令更值钱。这篇笔记想解决的问题很具体把 Git 凭证从加进去能跑就行变成知道为什么这么加、出问题从哪查。我会覆盖三类最常见的 Git 凭证形态HTTPS 令牌、SSH 私钥、用户名密码讲清楚它们的存储模型和作用域规则然后给出一条从下拉框里看不到凭证到Permission denied (publickey)的完整排查链路最后把凭证串进自动部署 Java Web 应用的流水线里。适合谁看如果你已经能跑通一个最简单的 Jenkins Job但对系统管理 → 凭据那个页面里一堆概念全局、系统、域、ID、Provider一直没搞明白或者你在 Windows 上装 Jenkins 时被验证 credentials这一步搞得怀疑人生那这篇大概率能帮你省几个小时。如果你已经在用 Pipeline 和共享库中间关于domain限制和sshagent的段落也值得扫一眼。2. Jenkins 的凭证模型Store、Scope、Domain 三层关系2.1 凭证存在哪儿怎么加密的Jenkins 把所有凭证序列化到$JENKINS_HOME/credentials.xml。这个文件里你看到的是密文而不是明文密码或私钥内容真正干加密活的是$JENKINS_HOME/secrets/目录下的密钥材料master.key和hudson.util.Secret。这个设计带来两个必须记住的实操结论第一迁移或恢复 Jenkins 时如果只搬了credentials.xml没搬secrets/所有凭证都会解不开。表现是页面还在、条目还在但一用就报错或者干脆显示成一串乱码。我踩过一次把整个$JENKINS_HOME用tar打包才算干净利落不要试图单独拷某个文件。第二credentials.xml本身是可以被有 Shell 权限的人读到的虽然内容是密文所以给 Jenkins 主机上的普通用户开放 Shell等价于给了他们离线爆破或窃取密钥材料的机会。生产环境的建议是Jenkins 主机的登录权限收紧到极少数人业务代码用agent节点跑不要在 master 上开一堆账号。注意凭证一旦保存Jenkins 不会再把它显示出来。页面上只给你一个 ID 和描述。这不是 bug是刻意设计。想改内容只能覆盖重写。2.2 Store系统级和个人级是两个入口添加凭证有两个完全不同的入口很多人第一次会点错系统级系统管理 → 凭据Credentials→ 系统 → 全局凭据Global credentials。这里的凭证属于 Jenkins 实例本身所有有权限的 Job 都能引用普通用户看不到添加按钮只有管理员能加。这就是热词里那句jenkins在window上安装时验证credentials经常被混淆的地方——安装向导里让你填的管理员账号密码校验跟 Git 凭证一点关系都没有它只是创建初始管理员。个人级你的用户名右上角→ 凭据。这里的凭证只属于你个人别人看不到、也用不了。适合临时调试不适合放进流水线因为一旦这个用户被删除凭据跟着消失Job 集体挂掉。结论很直接只要是团队共用的流水线凭证一律加在系统级。个人级只在我本地临时验证一个私钥能不能通这种场景下用一下。2.3 Scope 与 Domain控制谁能看见和对谁生效在全局凭据页面点添加会看到 Scope 选项。这里有个容易误解的地方Scope 不是限制 Job而是限制凭证被使用的层级。Scope含义什么时候选Global凭证可以被 Jenkins 内部任何需要它的地方取用包括作为 SCM 拉代码的凭据出现在 Job 配置下拉框里绝大多数 Git 凭证System只用于 Jenkins 与外部系统之间的系统级交互通常不会出现在 Job 的 SCM 凭据下拉框里少数需要特殊限制的场景我见过最典型的翻车是加了一个 SSH 私钥Scope 顺手选了 System然后回到 Job 配置里死活找不到这条凭证。排查这类问题的第一步就是回来看 Scope。另一层是 Domain。默认情况下所有凭证都落在_这个全局域里这个域不限定任何 URL。你可以新建一个域比如叫code.example.com并在域里配置 URL 模式那么只有在访问该 URL 时这个域下的凭证才会出现在下拉框。这个功能的实际价值在于同一台 Jenkins 上给多个代码仓库分发不同账号时避免选错。团队多了之后一个下拉框里躺着二十条名字都叫 git-readonly 的凭证Domain 能救命。3. 三类 Git 凭证的添加过程与选择依据3.1 HTTPS 个人访问令牌现在最推荐的默认方案我现在的默认选择是 HTTPS 令牌理由有三个不需要在 Jenkins 主机上管理私钥文件、权限粒度可以做到只读单仓库、撤销成本极低平台上点一下就失效。操作路径是在代码托管平台的账号设置里生成一个访问令牌权限只勾选仓库读取相关的范围拿到字符串后回到 Jenkins系统管理 → 凭据 → 系统 → 全局凭据 → 添加凭据类型选Username with passwordUsername 填你的账号名有些平台要求填固定的占位字符串比如把令牌当成密码、用户名随便填具体看你用的平台文档Password 粘贴令牌ID 手工填一个有意义的名字比如git-https-readonly不要留空让 Jenkins 生成 UUID描述写清楚哪个平台、哪个账号、什么权限、什么时候过期ID 一定要手工命名这是我在 Pipeline 场景下最看重的一条经验。Pipeline 里的credentialsId是写死在 Jenkinsfile 里的如果当初留空生成了a1b2c3d4-...这种 UUID后面换凭证时你就得去改代码。手工命名 命名规范用途-协议-权限能让你的 Jenkinsfile 长期稳定。令牌本身有两个现实问题会过期以及权限可能不够。前者表现为某天突然全员构建失败后者表现为 push 类操作报 403 而 clone 正常。建议在描述里写上过期时间并给它单独配一个日历提醒如果平台支持用机器人账号而不是某个人的账号避免人一离职全组瘫痪。3.2 SSH 私钥跨机器、免交互场景下的稳定选择当你的流水线需要从 Jenkins 主动连到多台机器比如构建完把产物推到一个 Git 仓库做归档或者走 SSH 触发远端脚本私钥是更顺手的方案。生成一对专用密钥ssh-keygen -t ed25519 -C jenkins-cibuild-01 -f ~/.ssh/id_ed25519_jenkins -N chmod 600 ~/.ssh/id_ed25519_jenkins cat ~/.ssh/id_ed25519_jenkins.pub几个参数值得解释-t ed25519是当前推荐的算法密钥短、性能好-C里的注释只是给人看的方便日后在服务器的authorized_keys里认出这把钥匙是谁的-N 表示不设口令因为 Jenkins 自动构建没法交互输入口令——但你也可以用带口令的私钥然后把口令单独存成一条 Secret text 凭证配合sshagent使用安全性更好但配置更绕。生成后把公钥内容贴到代码托管平台的部署密钥Deploy Key里注意只给读权限。然后在 Jenkins 里类型选SSH Username with private keyID 填git-ssh-deployUsername 填git大多数托管平台对 SSH 访问都要求这个用户名具体以平台文档为准Private Key 选 Enter directly把私钥全文粘贴进去提示粘贴私钥时务必包含首尾的-----BEGIN ... KEY-----和-----END ... KEY-----两行。少一行是最常见的低级错误报错却只会告诉你认证失败非常难查。关于场景选择我给一个粗略的判断场景建议单个仓库拉代码HTTPS 令牌需要从构建机反向推送、多仓库聚合SSH 私钥老旧自建仓库只支持账号密码Username with passwordWebhook 校验、API 回调密钥Secret text需要把证书/配置文件给构建脚本用Secret file3.3 用户名密码与 Secret text老仓库和 Webhook 分别用在哪有些自建仓库还在用纯账号密码这时就用 Username with password但一定要单独建一个只读账号不要用某个同事的账号。原因不复杂一旦这个人改了密码构建就断了而排查的人根本不知道这条凭证背后是谁。Secret text 则常被忽略。它的典型用途是代码托管平台推送事件到 Jenkins 时的校验令牌、调外部 API 的 Bearer Token。它和 Git 凭证不同不需要用户名只有一串密文。配置 Webhook 触发时用的就是它。这里插一句关于测试连接的经验。很多版本的 Git 插件在 Job 配置里给了一个 Test Connection 按钮但它经常给不出有诊断价值的信息——令牌权限不对、仓库路径写错、网络不通都只回你一句失败。真正有效的验证方式不是点按钮而是在 Jenkins 主机上手工跑一次命令git ls-remote https://git.example.com/group/app.git能列出 ref 说明凭据和网络都没问题剩下的锅就在 Jenkins 侧配置、Scope、Domain。这个动作能省掉大量在 UI 上反复点的时间。4. 凭证在 Freestyle 与 Pipeline 中的四种引用姿势4.1 Freestyle Job下拉框为空时的两种正常原因Freestyle 最简单源码管理 → Git → Credentials选一条。下拉框为空通常只有两种正常原因。第一种是权限。Jenkins 的授权策略如果是项目矩阵或基于角色的策略你只有 Job 的配置权限但没有Credentials/View权限就看不到凭证列表。这时候不是凭证没加是你没资格看。让管理员在全局安全配置里给你补上Credentials → View和Credentials → Use。第二种是Domain 限制。前面说过如果你把凭证放进了一个限定了 URL 的域而下拉框对应的仓库 URL 不匹配这个模式它就不会出现。判断方法很直接去凭证页面看这条凭证挂在哪个域下面和 Job 里的 URL 对一下前缀。还有一种非正常情况凭证类型不对。Git 插件的凭据下拉框只接受特定类型Secret text 是不会出现在这里的。加了半天找不到往往就是类型选错了。4.2 Pipeline 的 git 步骤最省心的写法Pipeline 里如果只是拉代码直接用git步骤带credentialsIdJenkins 会自动完成凭据注入你不需要在任何地方出现明文pipeline { agent any stages { stage(Checkout) { steps { git branch: main, credentialsId: git-https-readonly, url: https://git.example.com/group/app.git } } } }branch这一项可以填*/main这种通配形式具体支持哪种写法取决于你装的插件版本。我个人的习惯是明确写分支名不写通配因为通配在 tag 构建的流水线里会带来意外的匹配结果。4.3 sshagentSSH 私钥在 Pipeline 里的正确打开方式SSH 私钥不能像 HTTPS 那样直接塞给git步骤。Pipeline 里要用sshagent把私钥临时装载到 SSH 代理中任务结束后自动卸载stage(Checkout via SSH) { steps { sshagent(credentials: [git-ssh-deploy]) { sh mkdir -p ~/.ssh git clone gitgit.example.com:group/app.git . } } }这里有个必须提前处理的细节首次连接的主机指纹确认。自动构建没法交互回答yes/no所以要么在 Jenkins 主机上提前把目标主机指纹写进known_hosts要么在流水线里显式调用ssh-keyscanssh-keyscan -t rsa,ecdsa,ed25519 git.example.com ~/.ssh/known_hosts不处理它你会收到Host key verification failed而这条报错和凭证本身没关系很多人会在这里误判成私钥配错了。4.4 withCredentials手动拼命令时的兜底方案少数情况下你需要把凭据当环境变量交给自定义脚本比如用 curl 调平台的 API。这时用withCredentialswithCredentials([usernamePassword( credentialsId: git-https-readonly, usernameVariable: GIT_USER, passwordVariable: GIT_PASS)]) { sh echo user is $GIT_USER # 不要把 $GIT_PASS 拼进 URL也不要 echo 它 }关于这段代码有两条硬性经验值得强调。第一绝对不要把密码拼进 Git 远程 URL。像https://user:passhost/repo.git这种写法会把凭据写进.git/config后续任何能读这个文件的人都能拿到它。第二Jenkins 会对输出做遮蔽处理$GIT_PASS出现在日志里会被替换成****。但这不等于安全——如果脚本把密码做了 Base64 编码再打印遮蔽就失效了。验证凭据是否正确不要靠打印要靠用它去执行一个真实操作并观察退出码。5. 从下拉框看不到到Permission denied的排查链路5.1 第一步永远是分层是 Jenkins 的问题还是 Git 的问题我的排查习惯是先把问题切两半避免在一堆可能性里乱撞。分层的方法就是上一条提到的在 Jenkins 主机上以 Jenkins 运行用户的身份手工执行一次 Git 命令。能通问题在 Jenkins 的配置层不能通问题在凭据内容或网络层。用哪个用户执行非常关键。Linux 上 Jenkins 通常以jenkins用户运行sudo -u jenkins git ls-remote ...才是等价复现。用 root 或自己的账号跑能通不代表 Jenkins 能通——私钥文件权限、known_hosts位置、~/.gitconfig都是按用户隔离的。5.2 六种最常见报错与对应动作下面这张表是我这些年积累下来的对照表基本覆盖了九成以上的现场问题现象最可能的原因动作下拉框里没有凭证权限不足 / Domain 不匹配 / 类型不对查授权策略、查域 URL 模式、查凭证类型Authentication failed令牌过期、被重置、权限范围不够重新生成令牌并覆盖凭证内容403 Forbidden账号对目标仓库无权限或触发了平台风控用同一账号在浏览器验证检查是否被限制Permission denied (publickey)公钥没加到对端、私钥粘错、用户名填错核对 Deploy Key、核对 Username、核对首尾行Host key verification failedknown_hosts缺目标主机指纹ssh-keyscan预置指纹改了凭证但报旧错Agent 上残留缓存/~/.ssh旧文件清理对应 agent 的用户目录后重跑特别注意最后一行。Jenkins 的凭据更新是即时的但 agent 节点上可能残留上一次的 SSH 代理或缓存的 known_hosts 条目。如果你在分布式环境里发现改了没用先在一个干净的 agent 上跑一次排除缓存因素。5.3 一个容易被忽略的坑中文路径与文件名转义这条和凭证不是直接相关但它经常和凭证问题混在一起让人误判。Git 默认会对非 ASCII 的文件名做八进制转义日志里就会出现一堆\344\270\255这种鬼东西看起来像编码崩了。一行配置解决git config --global core.quotepath false顺带把换行符处理也定死避免 Windows 上跑 Jenkins、构建 Linux 产物时出现满屏的文件被修改假象git config --global core.autocrlf input这两条配置建议在 Jenkins 运行用户的~/.gitconfig里做一次而不是写在每个 Job 的脚本里。全局配置属于环境不属于某个项目。5.4 验证顺序不要一次改三样最后给一条偏方法论的经验。排查凭证问题时一次只改一个变量。我见过太多人一着急就同时重新生成令牌、换私钥、改用户名、清缓存然后问题好了但他永远不知道是哪一步救回来的——下次再遇到还是从零开始。我的习惯是固定验证顺序先在 Jenkins 主机上以jenkins用户手工跑git ls-remote通了再去 Job 里点构建还不通就看 Jenkins 系统日志系统管理 → 系统日志或$JENKINS_HOME/logs。系统日志里通常有插件抛出的完整堆栈比 UI 上那句认证失败信息量大得多。6. 凭据的安全边界与长期维护6.1 最小权限不是口号是省事给流水线用的 Git 凭据权限就两条原则只读优先单仓库优先。如果你用的是部署密钥就绑到具体仓库而不是整个账号如果用的是令牌就只勾该仓库的读取范围。这样做的好处不只是安全更是故障半径小——万一这份凭据泄露损失可控万一它过期影响范围也只有那几个 Job容易定位。反过来那种给个管理员令牌全公司共用的做法短期省事长期一定会变成没人敢动的地雷。我接手过一套这样的 Jenkins一条令牌横跨三十多个仓库谁都不敢撤销因为一撤全停。这种局面基本没法优雅收场。6.2 轮换把过期这件事变成例行工作凭据过期导致的构建失败本质上是运维流程问题不是技术问题。我现在的做法是把所有对外凭据登记在一张表里包含四列凭证 ID、用途、创建时间、过期时间。到期前一周在团队里发个提醒提前换掉Job 就永远不会因为过期而红。轮换时有个小技巧不要覆盖先新增再切换。具体说就是新建一条 ID 类似git-https-readonly-2025的凭据把 Job 的credentialsId改过去确认构建全绿最后再删除旧的。这样任何时刻都有可回退的路径不用在改了之后全挂和不敢改之间二选一。6.3 备份与恢复master key 才是命门前面提过一次这里再强调一次因为它太容易出事。备份 Jenkins 时credentials.xml和secrets/目录必须成对备份且建议用文件系统级别的快照而不是手工挑文件。理由很实在credentials.xml里的密文需要secrets/里的密钥材料才能解开只拿一个文件恢复出来就是一堆死数据。另外不要把备份放在 Jenkins 主机自己能读到的普通目录里。备份文件里虽然有密文但配合密钥材料就等于明文。放到独立的、权限收紧的存储上。6.4 Jenkinsfile 里不该出现什么最后一条纪律任何形式的明文密钥都不应该出现在 Jenkinsfile 里包括注释里。Jenkinsfile 通常会进版本库进版本库就意味着所有能读代码的人都能看到而且历史提交里删不掉。所有敏感值一律走credentialsId让 Jenkins 在运行时注入。这不是洁癖是唯一可行的做法。7. 把凭证串进自动部署从拉代码到环境变量7.1 凭证生效后Jenkins 会给你哪些可用变量凭证配好、拉代码成功后Jenkins 的 Git 插件会自动注入一批环境变量这些在做版本标记、通知、回滚时非常有用变量含义典型用途GIT_COMMIT当前构建对应的提交 SHA打镜像 tag、写构建记录GIT_BRANCH分支名区分环境部署策略GIT_URL仓库地址拼通知里的链接GIT_PREVIOUS_COMMIT上一次成功构建的提交计算本次变更范围想确认当前构建里到底有哪些变量别猜直接在流水线里加一步打印出来看sh env | sort | grep -E GIT_|JENKINS_|BUILD_这个动作我强烈建议在流水线刚搭起来的时候做一次把实际可用的变量列表记在项目的 README 里。不同插件版本注入的变量集合是有差异的网上的教程和你的实际环境经常对不上。7.2 一个完整的 Java Web 部署流水线示例把前面的东西拼起来一个从拉代码到部署的骨架大概长这样pipeline { agent any options { timestamps() } stages { stage(拉取代码) { steps { git branch: main, credentialsId: git-https-readonly, url: https://git.example.com/group/app.git } } stage(构建) { steps { sh mvn -B -DskipTestsfalse clean package } } stage(记录版本) { steps { echo 本次构建提交: ${env.GIT_COMMIT} echo 分支: ${env.GIT_BRANCH} } } stage(部署) { when { branch main } steps { sshagent(credentials: [deploy-target-ssh]) { sh scp target/app.war deploytarget-host:/opt/tomcat/webapps/ ssh deploytarget-host systemctl restart tomcat } } } } }这段骨架里有几个刻意的设计。部署阶段用了独立的 SSH 凭证deploy-target-ssh和拉代码的凭证分开这样即使某一组凭证出问题影响面也被限制在一个阶段内。部署只在main分支执行用when { branch main }控制避免开发分支的提交直接打到服务器上——这条规则救过我不止一次。构建阶段保留测试执行不要为了快而长期挂-DskipTests那等于把测试套件废掉。如果部署内容涉及外部接口的密钥、通知渠道的凭据继续用 Secret text 类型加凭证在流水线里用withCredentials包住对应步骤。整个 Jenkinsfile 里应该零明文密钥。7.3 Webhook 触发与插件依赖的边角问题自动部署的另一半是触发。代码推上去要自动构建通常靠 Webhook 回调 Jenkins。这里的凭证就是前面说的Secret text配置在 Job 的触发器和平台的 Webhook 两端两边字符串必须一致。不一致的表现是推送代码后 Jenkins 毫无反应而 Jenkins 日志里可能只有一条被拒绝的记录——所以推送后没反应时先去看平台的 Webhook 投递记录那里会告诉你 HTTP 状态码。最后一个偏环境的问题。如果你在隔离网络里安装 Jenkins 或者插件市场连不上可以配置插件的更新站点为可访问的镜像地址或者干脆用离线方式安装.hpi文件系统管理 → 插件管理 → 高级 → 上传插件。这条和 Git 凭证没有直接关系但它决定你能不能装上 Git 插件、能不能用上sshagent这类步骤。先确认插件在再怀疑凭证顺序反了会浪费很多时间。我自己的习惯是每搭一套新 Jenkins第一件事是装好 Git、Pipeline、Credentials Binding、SSH Agent 这几个插件第二件事才是加凭证。插件没齐的时候去调凭证等于在一辆没装轮胎的车上找发动机的毛病。