问题描述
近期将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, 其中少不了容错处理的部分。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。