【问题标题】:How to request for the crumb issuer for Jenkins如何为 Jenkins 请求 crumb issuer
【发布时间】:2013-05-24 15:22:38
【问题描述】:

我想使用 Jenkins Remote API,并且正在寻找安全的解决方案。我遇到了Prevent Cross Site Request Forgery exploits,我想使用它,但我在某处读到你必须提出 crumb 请求。

如何获取 crumb 请求以使 API 正常工作?

我找到了这个https://github.com/entagen/jenkins-build-per-branch/pull/20,但我仍然不知道如何修复它。

我的 Jenkins 版本是 1.50.x。

Authenticated remote API request responds with 403 when using POST request

【问题讨论】:

    标签: api security jenkins


    【解决方案1】:

    我也没有在文档中找到这个。此代码已针对较旧的 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"}
    

    这包含您需要的两条信息

    1. 您需要传递 crumb 的字段名称
    2. 面包屑本身

    如果您现在想从 Jenkins 获取某些内容,请将 crumb 添加为标题。在下面的示例中,我获取了最新的构建结果。

    HttpPost httpost = new HttpPost(jenkinsUrl + "rssLatest");
    httpost.addHeader(crumbJson.crumbRequestField, crumbJson.crumb);
    

    这里是整个示例代码。我正在使用 gson 2.2.4 解析响应,其余部分使用 Apache's 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;
        }
    
    }
    

    【讨论】:

    【解决方案2】:

    或者你可以使用 Python 和 requests 代替

    req = requests.get('http://JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)', auth=(username, password))
    print(req.text)
    

    会给你名字和面包屑:

    Jenkins-Crumb:e2e41f670dc128f378b2a010b4fcb493
    

    【讨论】:

      【解决方案3】:

      此 Python 函数获取 crumb,并另外使用 crumb 发布到 Jenkins 端点。这是在 CSRF protection 开启的情况下使用 Jenkins 2.46.3 测试的:

      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)
      

      【讨论】:

      • 这对我不起作用。它在邮递员工作。我必须调用端点来生成面包屑并将其添加到 POST 中。我对上面的 Python 代码做了类似的事情,但不知何故我得到了 403 错误。
      • 我必须在发出面包屑请求和发布请求之间使用requests.session()
      【解决方案4】:

      同时,您可以生成一个 API 令牌,以避免在上述解决方案提供的源代码中包含您的密码:

      https://wiki.jenkins.io/display/JENKINS/Authenticating+scripted+clients

      【讨论】:

        【解决方案5】:

        User cheffe's answer 帮助了 90%。感谢您给我们正确的方向。

        缺少的 10% 围绕 HTTP 用户名和密码身份验证。

        由于我使用的 Codenameone Java API 没有身份验证类,

        new UsernamePasswordCredentials(usernName, password));
        

        我用过:

        String apiKey = "yourJenkinsUsername:yourJenkinsPassword";
        httpConnection.addRequestHeader("Authorization", "Basic " + Base64.encode(apiKey.getBytes()));
        

        【讨论】:

          【解决方案6】:

          参考 - https://support.cloudbees.com/hc/en-us/articles/219257077-CSRF-Protection-Explained

          如果您使用用户名和用户 API 令牌进行身份验证,则 Jenkins 2.96 每周/2.107 LTS 不需要 crumb。有关更多信息,请参阅使用 API 令牌进行身份验证时不再需要 CSRF crumb 或 JENKINS-22474。

          【讨论】:

            【解决方案7】:

            User cheffe's Java snippet 在 Jenkins v2.89.3 (Eclipse.org) 和我使用的另一个 Jenkins 实例 v2.60.3 上对我非常有用(一旦启用1)。

            我已将此添加到 Maven mojo2 我用于将本地编辑的 config.xml 更改推回服务器。

            1 CSRF Protection
            2 Hudson job sync plugin

            【讨论】:

              【解决方案8】:

              在任何这些答案中,我都没有找到使用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 client

              【讨论】:

                猜你喜欢
                • 2017-09-05
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-02-02
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多