问题形容
近期将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, 其中少不了容错解决的局部。