我想使用 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 获取某些内容,请将面包屑添加为标头。在下面的示例中,我获取了最新的构建结果。
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
参考 - https://support.cloudbees.com/hc/en-us/articles/219257077-CSRF-Protection-Explained
如果您使用用户名和用户 API 令牌进行身份验证,则 Jenkins 2.96 每周/2.107 LTS 不需要 crumb。有关更多信息,请参阅使用 API 令牌或 JENKINS-22474 进行身份验证时不再需要 CSRF crumb。
用户cheffe的回答帮助了90%。感谢您给我们正确的方向。
缺失的 10% 涉及 HTTP 用户名和密码身份验证。
由于我使用的 Codenameone Java API 没有身份验证类,
new UsernamePasswordCredentials(usernName, password));
我用过:
String apiKey = "yourJenkinsUsername:yourJenkinsPassword";
httpConnection.addRequestHeader("Authorization", "Basic " + Base64.encode(apiKey.getBytes()));
用户 Cheffe 的 Java 片段 在 Jenkins v2.89.3 (Eclipse.org) 和我使用的另一个 Jenkins 实例 v2.60.3 上非常适合我(一旦启用1)。
我已将其添加到 Maven mojo2,我用它来将本地编辑的
config.xml
更改推回到服务器。
在这些答案中,我没有找到使用
Jenkins API token
的选项。
我确实尝试了所有这些选项,但如果您启用 CSRF
保护,则应该使用 Jenkins API token
而不是普通的 password
来访问 Jenkins API。
该令牌可以由每个单独的用户在用户配置页面中生成。
代币可以如下使用-
JenkinsApi::Client.new(server_url: jenkins_url, username: jenkins_user, password: jenkins_token)
附注- 此初始化适用于 Ruby Jenkins API 客户端