37

I want to use the Jenkins Remote API, and I am looking for safe solution. I came across Prevent Cross Site Request Forgery exploits and I want to use it, but I read somewhere that you have to make a crumb request.

How do I get a crumb request in order to get the API working?

I found this https://github.com/entagen/jenkins-build-per-branch/pull/20, but still I don't know how to fix it.

My Jenkins version is 1.50.x.

Authenticated remote API request responds with 403 when using POST request

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Satish
  • 16,544
  • 29
  • 93
  • 149

8 Answers8

47

I haven't found this in the documentation either. This code is tested against an older Jenkins (1.466), but should still work.

To issue the crumb use the 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);

This will get you a response like this

{"crumb":"fb171d526b9cc9e25afe80b356e12cb7","crumbRequestField":".crumb"}

This contains two pieces of information you need

  1. the field name with which you need to pass the crumb
  2. the crumb itself

If you now want to fetch something from Jenkins, add the crumb as header. In the sample below I fetch the latest build results.

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

Here is the sample code as a whole. I am using gson 2.2.4 to parse the response and Apache's httpclient 4.2.3 for the rest.

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;
    }

}
cheffe
  • 9,345
  • 2
  • 46
  • 57
  • 1
    Confirmed on version 2.19 of Jenkins. Still working. – teodron Jan 17 '18 at 10:26
  • Also, you could simply go to: `https://YOUR_JENKINS_URL/crumbIssuer/api/json` to get your crumb as explained in [https://stackoverflow.com/a/44714793/12821043](https://stackoverflow.com/a/44714793/12821043) – Luis Chaves Rodriguez Oct 15 '20 at 09:28
10

Or you can use Python and requests instead

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

will give you the name and the crumb:

Jenkins-Crumb:e2e41f670dc128f378b2a010b4fcb493
JamesD
  • 2,466
  • 24
  • 40
8

This Python function gets the crumb, and additionally uses the crumb to post to a Jenkins endpoint. This is tested with Jenkins 2.46.3 with CSRF protection turned on:

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)
gaiazov
  • 1,908
  • 14
  • 26
John McGehee
  • 9,117
  • 9
  • 42
  • 50
  • 2
    This did not work for me. It was working in postman. I have to call endpoint to generate the crumb and added it header in the POST. I did similar with the above Python code but somehow I am getting a 403 error. – alltej Nov 04 '20 at 15:16
  • 1
    I had to use a `requests.session()` between making the crumbs request, and the post request – gaiazov Mar 31 '21 at 03:53
4

Meanwhile you can generate an API token in order to prevent having to include your password in the source code provided by the solutions above:

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

Florian Straub
  • 826
  • 9
  • 18
4

enter image description here

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

If you authenticate with a username and a user API token then a crumb is not needed from Jenkins 2.96 weekly/2.107 LTS. For more information please refer to CSRF crumb no longer required when authenticating using API token or JENKINS-22474.

3

User cheffe's answer helped 90%. Thanks for giving us the right direction.

The missing 10% revolved around HTTP username and password authentication.

Since the Codenameone Java API I was using did not have the Authentication Class,

new UsernamePasswordCredentials(usernName, password));

I used:

String apiKey = "yourJenkinsUsername:yourJenkinsPassword";
httpConnection.addRequestHeader("Authorization", "Basic " + Base64.encode(apiKey.getBytes()));
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1

User cheffe's Java snippet worked great for me on Jenkins v2.89.3 (Eclipse.org) and another Jenkins instance I use, at v2.60.3 (once enabled1).

I've added this to a Maven mojo2 I use for pushing locally-edited config.xml changes back to the server.

1 CSRF Protection
2 Hudson job sync plugin

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
nickboldt
  • 732
  • 1
  • 7
  • 13
0

In any of these answers I didn't find an option to use Jenkins API token. I really tried all of these options but if you're enabling CSRF protection, you should access Jenkins APIs with Jenkins API token instead of normal password. This token can be generated by each individual user in the user config page. The token can be used as follows-

JenkinsApi::Client.new(server_url: jenkins_url, username: jenkins_user, password: jenkins_token)

P.S. - This initialization is for a Ruby Jenkins API client

enter image description here

Swaps
  • 1,450
  • 24
  • 31