遇到问题
起因
深夜更新 Jenkins 插件的时候,遇到了一个问题:插件下载速度太慢了,并且有大概率失败。
因此我研究了一下 Jenkins 的插件升级机制,研究是否可以使用镜像站点加速。
初步研究
Jenkins 的 升级信息
(包含本体和插件)来自于 http://mirrors.jenkins-ci.org/updates/update-center.json
这个 URL。
这个 JSON 文件描述了所有可用插件的 版本信息
和 下载地址
,并且 Jenkins 在 插件中心
的 高级
选项卡里提供了修改这个地址的功能。
我找到了 jenkins-zh
社区提供的 升级信息
镜像地址 https://updates.jenkins-zh.cn/update-center.json
,他们所提供的 JSON 文件内部,将所有 下载地址
替换为等价的 清华 TUNA 镜像站 的地址。
遭遇困境
但是当我把镜像地址填入 Jenkins,点击 更新
时,界面突然弹出来巨长一串超出屏幕的报错,提示 签名验证失败
。
为什么
Jenkins 在这个 JSON 文件内做了签名认证。
也就是说,Jenkins 允许你从另外的 URL 获取到 update-center.json
这个文件,但是 JSON 文件内部描述的 下载地址
等信息是 无法修改
的,否则会破坏签名认证。
这也就意味着,插件下载的过程仍然无法使用镜像站点加速。
怎么办
两个方法:
- 重新签名
update-center.json
并替换 Jenkins 内的 CA 文件,让修改后的update-center.json
变得合法 - 修改代码重新编译 Jenkins,关掉这个签名认证 (既然允许自定义
升级信息
地址,又何必非要做签名认证
呢?)
方法 1 的问题
如果有一天我不使用这个镜像站了,我还得想办法恢复 CA 文件。
并且每次 update-center.json
有变动,我都得重新签名。尽管可以制作或者寻找现有的自动化工具,但是终究需要额外部署一套工具。
方法 2 的问题
每次 Jenkins 版本更新都需要重新编译 Jenkins,着实麻烦。
更好的办法
我想到了一个更好的办法,使用 Java Agent
机制,在运行时覆盖掉 Jenkins 签名验证方法,让它跳过实际的签名验证,永远返回正确。
这样即便是 Jenkins 版本更新,只要 升级信息
签名验证代码流程不变,这个方法就可以一直有效。
实施流程
什么是 Java Agent
Java Agent 通过在 Java 命令上添加参数 -javaagent:XXXXXX.jar
来启动,它允许你在主类之外,额外加载一个 Jar 包,并提供机制,允许你在类加载时,修改类的字节码。
通过 Java Agent 机制,可以在不修改源代码的情况下,在运行时修改 Java 程序。
定位 Jenkins 代码
简要查阅了 Jenkins 的源代码,定位到了 Jenkins 中,负责校验升级信息签名的方法来自 hudson.model.UpdateSite
类的 verifySignature
方法
/**
* Verifies the signature in the update center data file.
*/
private FormValidation verifySignature(JSONObject o) throws IOException {
return getJsonSignatureValidator().verifySignature(o);
}
我们的目标是,将这个方法的代码体,修改成如下内容
return FormValidation.ok();
这样,修改后的方法,永远会返回校验正常。
编写 Java Agent
知道了要修改什么,就可以开始编写 Java Agent 了。
我们选用 javassist
这个库,这是一个 IBM 推出的库,可以便捷地修改 Java 类字节码。
创建 Maven 工程
首先我们创建一个 Maven 工程,和常见的工程类似,但是 Pom 文件的设置有些许区别:
<!-- 略去了不重要的内容 -->
<dependencies>
<!-- 引入 javassist 包 -->
<dependency>
<groupId>org.javassist</groupId>