记录Jenkins新版本API请求出现403的Crumb问题
问题描述
近期将jenkins从低版本升级到最新的2.312版本之后,对jenkins发起的API调用,均出现 403
错误。
curl -s -XPOST http://127.0.0.1:8080/credentials/store/system/domain/_/createCredentials \
> --user admin:pass@word1 --data-urlencode 'json={
quote> "": "0",
quote> "credentials": {
quote> "scope": "GLOBAL",
quote> "id": "credential_id_here",
quote> "username": "username_here",
quote> "password": "password_here",
quote> "description": "My new credentials",
quote> "$class": "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"
quote> }
quote> }'
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<title>Error 403 No valid crumb was included in the request</title>
</head>
<body><h2>HTTP ERROR 403 No valid crumb was included in the request</h2>
<table>
<tr><th>URI:</th><td>/credentials/store/system/domain/_/createCredentials</td></tr>
<tr><th>STATUS:</th><td>403</td></tr>
<tr><th>MESSAGE:</th><td>No valid crumb was included in the request</td></tr>
<tr><th>SERVLET:</th><td>Stapler</td></tr>
</table>
<hr><a href="https://eclipse.org/jetty">Powered by Jetty:// 9.4.43.v20210629</a><hr/>
</body>
</html>
根据网上查找的资料显示,是由于jenkins开启了 CSRF Protection
, 其实低版本也有这个功能,只是可能不完善,或者没有限制的这么死,如下图所示:
上图中左侧为升级前的旧版本 2.183
,右侧为新版本 2.312
,可以看到旧版本允许关闭跨域保护,新版本不允许。经过一轮测试和研究,发现确实无法关闭。
仔细阅读jenkins文档,发现CSRF有以下说明:
https://www.jenkins.io/doc/bo...
The Default Crumb Issuer encodes the following information in the hash used as crumb:
- The user name that the crumb was generated for
- The web session ID that the crumb was generated in
- The IP address of the user that the crumb was generated for
- A salt) unique to this Jenkins instance
All of this information needs to match when a crumb is sent back to Jenkins for that submission to be considered valid.
The only supported option Enable proxy compatibility removes information about the user IP address from the token. This can be useful when Jenkins is running behind a reverse proxy and a user’s IP address as seen from Jenkins would regularly change.
大概意思是说,默认启用的The *Default Crumb Issuer
, 这个拦截器会计算传递的crumb值的hash是否是可用hash, 这个hash的来源是通过用户名、sessionID,请求IP,以及访问的jenkins的唯一标识生成的。
其中唯一可以用的配置项 Enable proxy compatibility
, 只开放IP的校验,这可以使用在使用网络代理的场景中。
如何解决
根据这份文档,看起来对jenkins的调用,必须要先请求一个crumb的hash值,在每次请求的时候传递给它,例如:
JENKINS_CRUMB=$(curl -s 'http://127.0.0.1:8080/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)' --user admin:pass@word1)
curl -s -XPOST -H "Jenkins-Crumb:8dbf1060a4fd7a0120da0dd1a678cf82149dcd45d868c7799b760e79132fb774" http://127.0.0.1:8080/credentials/store/system/domain/_/createCredentials \
--user admin:pass@word1 --data-urlencode 'json={
"": "0",
"credentials": {
"scope": "GLOBAL",
"id": "credential_id_here",
"username": "username_here",
"password": "password_here",
"description": "My new credentials",
"$class": "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"
}
}'
根据上面这个网上找到的案例,发现运行之后还是会产生403,其中JENKINS_CRUMB
确定是有值的,见鬼了。
文档欠缺的时候就是这样无语,耗费了时间查找网上可能的案例时,终于发现一个更多的信息:
看起来还需要多传递一个cokkie的信息,不管有没有用,先测试再说,没想到真的成功了,jenkins官方为什么不给一个例子呢,吐槽....
有效测试如下:
curl -verbose -s 'http://127.0.0.1:8080/crumbIssuer/api/json' --user admin:pass@word1
* About to connect() to 127.0.0.1 port 8080 (#0)
* Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
* Server auth using Basic with user 'admin'
> GET /crumbIssuer/api/json HTTP/1.1
> Authorization: Basic YWRtaW46cGFzc0B3b3JkMQ==
> User-Agent: curl/7.29.0
> Host: 127.0.0.1:8080
> Accept: */*
> Referer: rbose
>
< HTTP/1.1 200 OK
< Date: Sat, 18 Sep 2021 06:48:15 GMT
< X-Content-Type-Options: nosniff
< X-Jenkins: 2.312
< X-Jenkins-Session: 4af5e654
< X-Frame-Options: deny
< Content-Type: application/json;charset=utf-8
< Set-Cookie: JSESSIONID.443fa9b8=node08lm1pknhy9zr1nr1gd79rdpd725.node0; Path=/; HttpOnly # 注意JSESSIONID
< Expires: Thu, 01 Jan 1970 00:00:00 GMT
< Content-Length: 163
< Server: Jetty(9.4.43.v20210629)
<
* Connection #0 to host 127.0.0.1 left intact
{"_class":"hudson.security.csrf.DefaultCrumbIssuer","crumb":"7f0ad0185a3a25c101ef654af337de01b822f2c4220a819ce1daaae7c61da059","crumbRequestField":"Jenkins-Crumb"}#
~ curl -s -XPOST --cookie "JSESSIONID.443fa9b8=node08lm1pknhy9zr1nr1gd79rdpd725.node0" -H "Jenkins-Crumb:7f0ad0185a3a25c101ef654af337de01b822f2c4220a819ce1daaae7c61da059" \
http://127.0.0.1:8080/credentials/store/system/domain/_/createCredentials \
--user admin:pass@word1 --data-urlencode 'json={
"": "0",
"credentials": {
"scope": "GLOBAL",
"id": "credential_id_here",
"username": "测试crumb",
"password": "password_here",
"description": "My new credentials",
"$class": "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"
}
}'
如上例子所示,需要再传递的时候增加cokkie和header,传递JSESSIONID和Jenkins-Crumb,以及账号密码或者token.那么程序发起调用之前,还需要先请求session和crumb,并保持session,如果session过期,还会出现403, 其中少不了容错处理的部分。
QUIC 学习入门概念及资料整理
行愚阅读 339
使用Jenkins优雅部署Java项目【超级详细的实战教程】
李博帅赞 1阅读 1.2k评论 1
不背锅运维:云原生下的CICD-3件套快速搭建合集:jenkins+harbor+gitlab
不背锅运维阅读 660
快速创建Jenkins Job
用户bPtuda阅读 629
Jenkins
阿南阅读 627
Jenkins 添加 Slave Agent 节点时报类文件不匹配错误
觅食的蛇阅读 446
jenkins+docker进行微服务部署
startshineye阅读 400
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。