我想使用 Jenkins Remote API,我正在寻找安全的解决方案。我遇到了Prevent Cross Site Request Forgery exploits
,我想使用它,但我在某处读到你必须提出一个碎屑请求。
如何获取 crumb 请求以使 API 正常工作?
我找到了这个https://github.com/entagen/jenkins-build-per-branch/pull/20
,但我仍然不知道如何解决它。
我的 Jenkins 版本是 1.50.x。
我想使用 Jenkins Remote API,我正在寻找安全的解决方案。我遇到了Prevent Cross Site Request Forgery exploits
,我想使用它,但我在某处读到你必须提出一个碎屑请求。
如何获取 crumb 请求以使 API 正常工作?
我找到了这个https://github.com/entagen/jenkins-build-per-branch/pull/20
,但我仍然不知道如何解决它。
我的 Jenkins 版本是 1.50.x。
我也没有在文档中找到这个。此代码已针对较旧的 Jenkins (1.466) 进行了测试,但仍应有效。
要发出面包屑,请使用crumbIssuer
// left out: you need to authenticate with user & password -> sample below
HttpGet httpGet = new HttpGet(jenkinsUrl + "crumbIssuer/api/json");
String crumbResponse = toString(httpclient, httpGet);
CrumbJson crumbJson = new Gson().fromJson(crumbResponse, CrumbJson.class);
这会让你得到这样的回应
{"crumb":"fb171d526b9cc9e25afe80b356e12cb7","crumbRequestField":".crumb"}
这包含您需要的两条信息
如果您现在想从 Jenkins 获取某些内容,请将 crumb 添加为标题。在下面的示例中,我获取了最新的构建结果。
HttpPost httpost = new HttpPost(jenkinsUrl + "rssLatest");
httpost.addHeader(crumbJson.crumbRequestField, crumbJson.crumb);
这是整个示例代码。我使用gson 2.2.4来解析响应,其余部分使用Apache 的 httpclient 4.2.3。
import org.apache.http.auth.*;
import org.apache.http.client.*;
import org.apache.http.client.methods.*;
import org.apache.http.impl.client.*;
import com.google.gson.Gson;
public class JenkinsMonitor {
public static void main(String[] args) throws Exception {
String protocol = "http";
String host = "your-jenkins-host.com";
int port = 8080;
String usernName = "username";
String password = "passwort";
DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.getCredentialsProvider().setCredentials(
new AuthScope(host, port),
new UsernamePasswordCredentials(usernName, password));
String jenkinsUrl = protocol + "://" + host + ":" + port + "/jenkins/";
try {
// get the crumb from Jenkins
// do this only once per HTTP session
// keep the crumb for every coming request
System.out.println("... issue crumb");
HttpGet httpGet = new HttpGet(jenkinsUrl + "crumbIssuer/api/json");
String crumbResponse= toString(httpclient, httpGet);
CrumbJson crumbJson = new Gson()
.fromJson(crumbResponse, CrumbJson.class);
// add the issued crumb to each request header
// the header field name is also contained in the json response
System.out.println("... issue rss of latest builds");
HttpPost httpost = new HttpPost(jenkinsUrl + "rssLatest");
httpost.addHeader(crumbJson.crumbRequestField, crumbJson.crumb);
toString(httpclient, httpost);
} finally {
httpclient.getConnectionManager().shutdown();
}
}
// helper construct to deserialize crumb json into
public static class CrumbJson {
public String crumb;
public String crumbRequestField;
}
private static String toString(DefaultHttpClient client,
HttpRequestBase request) throws Exception {
ResponseHandler<String> responseHandler = new BasicResponseHandler();
String responseBody = client.execute(request, responseHandler);
System.out.println(responseBody + "\n");
return responseBody;
}
}
或者你可以使用 Python 而requests
不是
req = requests.get('http://JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)', auth=(username, password))
print(req.text)
会给你名字和面包屑:
Jenkins-Crumb:e2e41f670dc128f378b2a010b4fcb493
此 Python 函数获取 crumb,并另外使用 crumb 发布到 Jenkins 端点。这是使用 Jenkins 2.46.3 测试并开启CSRF 保护:
import urllib.parse
import requests
def build_jenkins_job(url, username, password):
"""Post to the specified Jenkins URL.
`username` is a valid user, and `password` is the user's password or
(preferably) hex API token.
"""
# Build the Jenkins crumb issuer URL
parsed_url = urllib.parse.urlparse(url)
crumb_issuer_url = urllib.parse.urlunparse((parsed_url.scheme,
parsed_url.netloc,
'crumbIssuer/api/json',
'', '', ''))
# Use the same session for all requests
session = requests.session()
# GET the Jenkins crumb
auth = requests.auth.HTTPBasicAuth(username, password)
r = session.get(crumb_issuer_url, auth=auth)
json = r.json()
crumb = {json['crumbRequestField']: json['crumb']}
# POST to the specified URL
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
headers.update(crumb)
r = session.post(url, headers=headers, auth=auth)
username = 'jenkins'
password = '3905697dd052ad99661d9e9f01d4c045'
url = 'http://jenkins.example.com/job/sample/build'
build_jenkins_job(url, username, password)
同时,您可以生成一个 API 令牌,以避免在上述解决方案提供的源代码中包含您的密码:
https://wiki.jenkins.io/display/JENKINS/Authenticating+scripted+clients
用户 cheffe 的回答帮助了 90%。谢谢你给了我们正确的方向。
缺少的 10% 围绕 HTTP 用户名和密码身份验证。
由于我使用的 Codenameone Java API 没有身份验证类,
new UsernamePasswordCredentials(usernName, password));
我用了:
String apiKey = "yourJenkinsUsername:yourJenkinsPassword";
httpConnection.addRequestHeader("Authorization", "Basic " + Base64.encode(apiKey.getBytes()));
参考 - https://support.cloudbees.com/hc/en-us/articles/219257077-CSRF-Protection-Explained
如果您使用用户名和用户 API 令牌进行身份验证,则 Jenkins 2.96 每周/2.107 LTS 不需要碎屑。有关更多信息,请参阅使用 API 令牌进行身份验证时不再需要 CSRF crumb 或 JENKINS-22474。
用户 cheffe 的 Java 代码片段在 Jenkins v2.89.3 (Eclipse.org) 和我使用的另一个 Jenkins 实例 v2.60.3 (一旦启用1)上非常适合我。
我已将此添加到 Maven mojo 2中,用于将本地编辑的config.xml
更改推送回服务器。
在任何这些答案中,我都没有找到使用Jenkins API token
. 我真的尝试了所有这些选项,但如果您启用保护,您应该使用而不是 normalCSRF
访问 Jenkins API 。此令牌可以由用户配置页面中的每个用户生成。该令牌可以按如下方式使用 -Jenkins API token
password
JenkinsApi::Client.new(server_url: jenkins_url, username: jenkins_user, password: jenkins_token)
PS - 此初始化是针对Ruby Jenkins API 客户端的