本文来源于在一次Jenkins培训当中,有用户提问:我怎么知道自己的授信失效了。于是通过Jenkins提供的Groovy Script Console能力,编写脚本找出Jenkins中所有授信失效的项目。
背景
公司自建的CI平台
基于Jenkins
的,为了方便解答用户使用时碰到的问题,团队直接建立内部沟通群直接对接到用户。时间一长,发现用户经常遇到无法clone代码
的问题,日志类似如下。
仔细看会发现是由Authentication failed
导致的,往往是由于用户密码修改后,没有同步在CI平台
及时更新从而授信失效导致的。
现象
由于公司网络安全管控,每个员工均要在90天
周期更换一次密码。而更新密码之后,又没有手动同步到CI平台。最直接的现象就是,点进项目编辑页,会明显看到stderr: remote: HTTP Basic: Access denied
的error信息
。
能想到的几种解决方案
当沟通群一旦有人丢出来拉不到代码的问题,绝大部分都是由这个问题导致的,而我们也群里也不厌其烦帮用户解释了一次又一次,只要更新一下对应的授信密码就行。回过头来想,如何尽可能减少重复的事情发生呢?
90天
周期更新密码时,自动同步更新平台授信:可以彻底解决,但目前无法实现- 主动找出平台中哪些授信是已经失效了的,主动通知用户去更新:由被动用户找变成了主动告知用户,当前最切实的方案
- 建议用户使用
SSH方式
的授信,密码过期了也没事:对于新项目可以主动引导用户使用SSH类型
授信,由于历史问题,平台上已有项目绝大部分是UserPassword类型
授信
以上看来,如果可以每天定时轮询平台上所有的项目的授信,找出已失效的授信的项目,发送提醒邮件给用户及时更新密码,并建议用户换成SSH授信
,是目前最切实际的做法。那么问题第一步就是,如何找到失效授信的任务?
个人觉得有两种思路:
- 遍历所有上一次运行失败的项目,查看日志中是否包含
Access denied
关键字 - 遍历所有项目,模拟调用
check url
请求,判断当前项目是否授信失效
对于第2点,需要补充说明:当在项目编辑页面,所看到的error提示
,是Jenkins
调用了check url
请求返回的校验结果。
Request如下:
1 | Request URL: http://{host}:8081/job/test-fail-job-cased-by-credential-failed/descriptorByName/hudson.plugins.git.UserRemoteConfig/checkUrl |
Response如下:
1 | <div class=error><img src='/static/18ef55c1/images/none.gif' height=16 width=1>Failed to connect to repository : Command "/usr/local/bin/git ls-remote -h http://git.midea.com/paas/customize-xxl-job.git HEAD" returned status code 128:<br>stdout: <br>stderr: remote: HTTP Basic: Access denied<br>fatal: Authentication failed for 'http://git.midea.com/paas/customize-xxl-job.git/'<br></div> |
接下来,我们将用Script Console
去实现以上两种思路。提前说明两点:
- 本次实验环境:Jenkins version
2.89.3
- 实验脚本不建议直接在生产环境上使用
Groovy Script Console 介绍
Jenkins
提供了一个 Groovy 脚本控制台
,允许用户在 Jenkins
主运行时或代理上的运行时中运行任意的 Groovy
脚本。它提供了可以做很多事情的能力:
- 创建子进程,并在
Jenkins master
和agents
上执行任意命令; - 它甚至可以读取
Jenkins master
上拥有访问权限的文件(比如/etc/passwd
); - 甚至可以解密
Jenkins配置凭据
; - 普通用户若拥有使用
Groovy Script Console
权限等同于拥有管理员权限; Groovy Script Console
之所以如此强大,是因为它最初是为Jenkins 开发人员
设计的一个调试界面,但后来发展成为Jenkins Admin
用来配置 Jenkins
和调试 Jenkins runtime问题
的一个界面;- 由于
Groovy Script Console
提供了强大的功能,Jenkins
及其Agents
不应该以root
用户身份在Linux
上运行,在任何操作系统上也不应该以root
用户身份运行; - 确保您的
Jenkins instance
的安全;
在Agents上运行Groovy Script
可以在节点管理侧边栏菜单,点击脚本控制台
即可。
除此之外,也可以在Master
的Groovy Script Console
运行脚本执行到Agents
上
1 | import hudson.util.RemotingDiagnostics |
支持远程访问
通过Bash
提交Groovy
文件
1 | curl --user 'username:api-token' --data-urlencode \ |
示例案例
以下仓库中有许多可以作为参考的Groovy Script
,基本上涉及大部分用户需要的场景:
- Cloudbees jenkins-脚本库
- Sam Gleske 的 jenkins-script-console-scripts 存储库
由于Groovy
脚本直接访问Jenkins
的接口,通常已有脚本很容易因为Jenkins 版本
升级后过时,从而导致在运行脚本时返回异常,因为Jenkins core
或Jenkins 插件
中的公共方法和接口已经更改。 在试用例子的时候要记住这一点。
举个栗子:一行代码,禁用所有Jobs
1 | Jenkins.instance.getAllItems(hudson.model.AbstractProject.class).each {i -> i.setDisabled(true); i.save() } |
参照Jenkins 2.89 接口文档,这行代码做的事情是获取所有的项目,一一设置为Disabled
保存。
遍历所有上一次运行失败的项目,且日志中包含Access denied关键字
1 | jobs = Jenkins.instance.getAllItems() |
- 遍历所有的
Jobs
,这里忽略了folder
- 统计每个
Job
的运行次数 - 对于运行过的
Job
获取最后一次Build
,并判断对应的log中是否存在Access denied
关键字 - 打印符合条件的
JOB
信息
遍历所有项目,模拟调用check url请求,判断当前项目是否授信失效
对于这个思路,要解决的问题是如何模拟调用check url
请求,我一开始的思路是,获取到每个任务对应的仓库信息和授信信息,从授信信息中获取明文信息后,然后直接使用HTTP
请求gitlab
判断,太麻烦了,还不如直接请求Jenkins
的check url
请求。仔细观察请求URL``http://{host}:8081/job/test-fail-job-cased-by-credential-failed/descriptorByName/hudson.plugins.git.UserRemoteConfig/checkUrl
猜测,Jenkins
应该也是直接调用的git
插件的checkUrl
方法,于是找到git
插件源码的UserRemoteConfig
类,果然找到对应的doCheckUrl
方法。
1 |
|
所以只要调用UserRemoteConfig.DescriptorImpl
内部类的doCheckUrl
方法即可,最后实现的脚本如下:
1 | import hudson.plugins.git.* |
- 需要特别
import
插件的包hudson.plugins.git.*
- 通过
Jenkins.instance.getDescriptorByType()
获取UserRemoteConfig.DescriptorImpl
的实例 - 遍历所有项目,每个项目获取对应的
SCM
信息,其中包含了credentialsId
和url
信息 - 调用
descriptor.doCheckUrl(job,urc.credentialsId,urc.url)
方法验证授信是否失效 - 打印所有失效的项目信息
彩蛋:解密Jenkins授信
1 | import com.cloudbees.plugins.credentials.Credentials |
总结
感想如下:
- 脚本调试成本很高:将逻辑拆分为多个步骤,按照步骤一步一步完善代码,上一步跑通后再实现下一步;
- 对照
Jenkins
文档:建议参照对应版本Jenkins
的Javadoc
,减少出错; - 经常出现“无该方法签名”的错误:通常是脚本中的字段或方法在当前版本的
Jenkins
中不存在,请检查Javadoc
的版本和Jenkins
版本是否一致; - 观察
Jenkins
是如何实现的:别着急实现,可以看看Jenkins
自己是如何实现的,不然可能会走弯路; - 之前实现过一个功能,实时获取
Jenkins
正在执行和正在队列中的Job
信息,当时还不知道有Script Console这种
东西,实现方案是调用Jenkins
的HTTP API
,获取XML
的内容,再用特定的解析语法获取到Job
信息。能获取到的Job字段内容很少,且还要处理XML
解析逻辑。现在看来使用Script Console
实现会更加简单快捷。