2

Problem statement :

I have Jenkins Sever V:2.190.2 running on cloud. Permission "Logged In User can do any thing" is selected on Jenkins Config Security. So, it means user having valid user name and password can log in to the jenkin server and perform the authorised job. Basically I need to create job on Jenkin server by passing job name and jobXml.

Tried Below Option :

So far I have used "jenkinsci/java-client-api" api available on Github. This api is really good api for Jenkins related operation and I followed instructions as given on the READ.md. I created Jenkins Server instance and tried to call getJenkinVersion() and getJobs() method and both works well and returns results as expected. However, when I am going to call createJob(jobName, jobXml), this call returns 403 forbidden error from the server.

By digging little deeper in the issue, I found following :- 1. When I change the Jenkin security configuration to "Any User can do any thing" then this createJob() method works and I am able to create job. However, this option is not recommended due to security restriction. 2. When I keep Jenkin security configuration to "Logged in user can do any thing", then createJob() method does not work and returns 403 forbidden error. Here I also noticed that though I am providing correct user name and password/token, that is used to sign in, in to Jenkins server from UI to create Jenkin server instance as defined in user doc, when it hit to the method, it is logged in as "ANONYMOUS USER" in to jenkin. and I presume that this is the root cause for returning 403 error.

Below Code snippet :

**Sample 1**:
        HttpClientBuilder builder = HttpClientBuilder.create();
        JenkinsHttpClient client = new JenkinsHttpClient(uri, builder, "XXX", "XXX");
        JenkinsServer jenkins = new JenkinsServer(client);
        String sourceXML = readFile("src/main/resources/config.xml");
        System.out.println(String.format("Installed Jenkins Version >> %s", jenkins.getVersion().getLiteralVersion()));//works and gives correct result
        jenkins.createJob("test-nov1", sourceXML);

**Sample 2**:
        HttpClientBuilder builder = HttpClientBuilder.create();
        JenkinsHttpClient client = new JenkinsHttpClient(uri, addAuthentication(builder, uri, userName, passwordOrToken));
        JenkinsServer jenkins = new JenkinsServer(client);
        String sourceXML = readFile("src/main/resources/config.xml");
        System.out.println(String.format("Installed Jenkins Version >> %s", jenkins.getVersion().getLiteralVersion()));
        jenkins.createJob(null,"test-nov1", sourceXML,true);

**Sample Exception**:
    Exception in thread "main" org.apache.http.client.HttpResponseException: status code: 403, reason phrase: Forbidden
    at com.offbytwo.jenkins.client.validator.HttpResponseValidator.validateResponse(HttpResponseValidator.java:11)
    at com.offbytwo.jenkins.client.JenkinsHttpClient.post_xml(JenkinsHttpClient.java:375)
    at com.offbytwo.jenkins.JenkinsServer.createJob(JenkinsServer.java:389)
    at com.offbytwo.jenkins.JenkinsServer.createJob(JenkinsServer.java:359)
    at com.xx.OffByTwoJenkins.main(OffByTwoJenkins.java:31)

Option 2: I also tried with other option by directly calling Jenkin REST API directly using HttpUrl connection.

**Sample Code** :
public int createJob(final String username, final String password, final String jenkinsUrl, final String jobName) {
        // http://JENKINSURL//createItem?name=JOBNAME
        String jobUrl = jenkinsUrl + "/createItem?name=" + jobName;

        int responseCode = 00;
        try {
            String basicAuth = Base64.getEncoder().encodeToString((username+":"+password).getBytes(StandardCharsets.UTF_8));
            //String encoding = Base64.getEncoder().encodeToString((username+":"+password).getBytes("utf-8"));
            System.out.println(String.format("User Auth >> %s", basicAuth));
            String sourceXML = readFile("src/main/resources/config.xml");
            URL url = new URL(jobUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            //connection.setReadTimeout(10000);
            //connection.setConnectTimeout(15000);
            connection.setRequestProperty("Authorization", "Basic " + basicAuth);
            connection.setRequestProperty("Content-Type", "application/xml");
            connection.setRequestProperty("Content-Language", "en-US");
            connection.setRequestMethod("POST");
            connection.setUseCaches(false);
            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setInstanceFollowRedirects(false);
            OutputStream os = connection.getOutputStream();
            os.write(sourceXML.getBytes());
            os.flush();
            responseCode = connection.getResponseCode();
            BufferedReader br = new BufferedReader(new InputStreamReader((connection.getInputStream())));
            String output;
            System.out.println("Output from Server .... \n");
            while ((output = br.readLine()) != null) {
                System.out.println(output);
            }
            connection.disconnect();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return responseCode;

    }

This also returns with same error 403 forbidden.

**Exception** :
Caused by: java.io.IOException: Server returned HTTP response code: 403 for URL: <<JenkinsURL>>
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(Unknown Source)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
    at java.net.HttpURLConnection.getResponseCode(Unknown Source)
    at com.xx.JenkinsJobExecutor.createJob(JenkinsJobExecutor.java:109)

I really not able to understand where I need to tweak for getting the job created. Thanks

Kumar
  • 955
  • 5
  • 20
  • 50

2 Answers2

1

The following solution worked for me even after CSRF was enabled.

public class JenkinsJobCreate {
public static void main(String[] args) {

    System.out.println("JenkinsJobsTrigger has started ###############################");

    String ipAddress = "http://localhost:8080/";
    String jobName = "Hello-world";

    String username = "admin";
    String password = "admin";

    System.out.println("ipAddress: " + ipAddress);
    System.out.println("jobName: " + jobName);

    System.out.println("username: " + username);
    System.out.println("password: " + password);


    try (JenkinsServer jenkinsServer = new JenkinsServer(new URI(ipAddress), username, password)) {

        // our XML file for this example
        File xmlFile = new File("src/main/resources/config.xml");

        // Let's get XML file as String using BufferedReader
        // FileReader uses platform's default character encoding
        // if you need to specify a different encoding, use InputStreamReader
        Reader fileReader = new FileReader(xmlFile);
        BufferedReader bufReader = new BufferedReader(fileReader);

        StringBuilder sb = new StringBuilder();
        String line = bufReader.readLine();
        while( line != null){
            sb.append(line).append("\n");
            line = bufReader.readLine();
        }
        String jobXml = sb.toString();
        System.out.println("XML to String using BufferedReader : ");
        System.out.println(jobXml);

        bufReader.close();

        jenkinsServer.createJob(jobName, jobXml, true);

    } catch (Exception e) {
        System.out.println("Exception Occured!!!");
        e.printStackTrace();
    }

    System.out.println("JenkinsJobsTrigger has Finished ###############################");

}

}

config.xml

<?xml version='1.1' encoding='UTF-8'?>
<project>
  <description></description>
  <keepDependencies>false</keepDependencies>
  <properties/>
  <scm class="hudson.scm.NullSCM"/>
  <canRoam>true</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers/>
  <concurrentBuild>false</concurrentBuild>
  <builders>
    <hudson.tasks.Shell>
      <command>echo &quot;Jenkins Testing Hello World!&quot;</command>
    </hudson.tasks.Shell>
  </builders>
  <publishers/>
  <buildWrappers/>
</project>
Dinesh Shekhawat
  • 504
  • 6
  • 13
  • Thank you very much for your answer. Do U have some sample code. I am not much on cookies etc. – Kumar Nov 05 '19 at 06:52
  • Another point I want to make here is that we don't have CSRF enabled. So, I assume, there is no need to crumb with the request. request you to please post sample code if any available with you. Thanks – Kumar Nov 05 '19 at 06:56
  • I tried using the API using CURL only and not Java as you have done. However, I will try to post the sample code in Java if I can find a solution using the same. Have you disabled CSRF in the Jenkins Configuration? If yes, please try to avoid doing that, it is recommended to keep those enabled. – Dinesh Shekhawat Nov 05 '19 at 07:42
  • Yes, it is disabled as of now. But I will try to make it enabled. But as of now, biggest issue is creating a job using Java call. That's why I posted code sample also ( as per your suggestion, it is Option 2 in the above code sample using HttpUrlConnection.) – Kumar Nov 05 '19 at 08:05
  • I tried to hit REST API GET method to get Crumb from my jenkins server but again 403 error here too. – Kumar Nov 05 '19 at 08:53
  • How did you Hit the API? Using CURL? – Dinesh Shekhawat Nov 05 '19 at 09:06
  • Thanks for your solution. but this is again - org.apache.http.client.HttpResponseException: status code: 403, reason phrase: Forbidden – Kumar Nov 05 '19 at 09:26
  • I have disabled csrf otherwise I was getting not valid crumb. This work for you because you might have selected "any user can do any thing" in Jenkins server configuration security. – Kumar Nov 05 '19 at 09:28
  • This issue doesn't occur in older version of Jenkins. I will try to do this in the latest version without disabling the CSRF. – Dinesh Shekhawat Nov 05 '19 at 09:31
  • which version you are using ? the error is coming in to 2.164.x and 2.190.2 version. – Kumar Nov 05 '19 at 09:45
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/201881/discussion-between-dinesh-shekhawat-and-kumar). – Dinesh Shekhawat Nov 05 '19 at 13:05
  • Hello Dinesh, Thanks for your solution. I set up local Jenkins and tried my code and your code as well with my config.xml and your config.xml. Both works as well. The problem was having extra "/" in url I was putting in local jenkins url. However, I am still getting same error in Jenkin server available in amazon cloud server with url and port 80. – Kumar Nov 06 '19 at 06:35
  • Do you have any thought related to this peculiar scenario? – Kumar Nov 06 '19 at 06:36
  • You did not mention anything related to Amazon cloud server in your question. As far as I can guess the basic concept remains the same. You need to use crumb for accessing Jenkins API across different hosts. If you are getting the same error on Amazon Cloud server than most probably, the amazon server must be blocking your request. I have not worked with amazon cloud server. – Dinesh Shekhawat Nov 06 '19 at 08:38
  • hmm...But I guess, if it is so, then why I am getting Job details and Jenkins version from the same API. If it is working and returns with correct response, then it means it is going through the server. The same thing should be applicable to createJob() method also. I suppose. – Kumar Nov 06 '19 at 08:59
0

My trick was simply to include the username and password in the URL with the standard syntax.

Here's the CURL I used:

curl -I http://user:gNouIkl2ca1t@54.226.181.123/job/RemoteTriggerExample/build?token=abc-123

So without a username and password it gives a 403. With the username and passsword in the URL it works fine.

Tip? Might want to use https instead of http.

Cameron McKenzie
  • 3,684
  • 32
  • 28