Jenkins Workflow - Creating a Class to Wrap Access to a Secured HTTP Endpoint

This blog post will demonstrate how to access an external system using HTTP with form-based security. This will show how you can use the following features:
  • Jenkins Workflow plugin
  • Groovy built-in libraries such as HttpClient
  • Git-based Workflow Global Library repository

Getting Started

Requirements

  1. JDK 1.7+
  2. Jenkins 1.609+
  3. An external system secured with form-based authentication - e.g., a Jenkins server with security enabled and anonymous access rescinded

Installation

  1. Download and install JDK 1.7 or higher
  2. Download and install Jenkins

Setup Jenkins

  • Update plugins - Make sure you have the latest Workflow plugins by going to Manage Jenkins –> Manage Plugins -> Updates and selecting any Workflow-related updates. Restart Jenkins after the updates are complete. As of this writing the latest version is 1.8.
  • Global libraries repo - Jenkins exposes a Git repository for hosting global libraries meant to be reused across multiple CD pipelines managed on the Master. We will setup this repository so you can build on it to create your own custom libraries. If this is a fresh Jenkins install and you haven’t setup this Git repository, follow these instructions to setup.

 


Important - Before proceeding to the next steps, make sure your Jenkins instance is running

 

See Workflow Global Library for details on how to set up the shared library - note that if security is enabled, the ssh format works best.

If using ssh ensure that your public key has been configured in Jenkins.

To initialise the git repository:

git clone ssh://@:/workflowLibs.git

Where

  • USER is a user a valid user that can authenticate
  • JENKINS_HOST is the DNS name of the Jenkins server you will be running the workflow on. If running on the CloudBees Jenkins platform, this is the relevant client master not the Jenkins Operations Center node.
  • SSH_PORT is the ssh port defined in Jenkins configuration:

Note the repository is initially empty.
To set things up after cloning, start with:

git checkout -b master

Now you may add and commit files normally. For your first push to Jenkins you will need to set up a tracking branch:

git push --set-upstream origin master

Thereafter it should suffice to run:
git push

Creating a Shared Class to Access Jenkins

cd workflowLibs 
mkdir -p src/net/harniman/workflow/jenkins 
curl -O \ 
   https://gist.githubusercontent.com/harniman/36a004ddd5e1c0635edd/raw/3997ddab0ab571c902068afad60cbc56eeda07cb/Server.groovy 
git add * 
git commit 
git push

This will make the following class

net.harniman.workflow.jenkins.Server

available for use by workflow scripts using this syntax:

def jenkins=new net.harniman.workflow.jenkins.Server()

and methods accessed using:

jenkins.logon()
jenkins.getURL()

Note:

  • Classes follow the usual Groovy package naming formats and thus need to be in the appropriate directory structure
  • It is unnecessary to go out of process to access this Jenkins instance - it can be accessed from Groovy via the Jenkins model, however, this is to show how a client for form-based access could be built
  • The below script is built to access Jenkins as an example for demonstration

This is the actual contents of Server.groovy:

package net.harniman.workflow.jenkins

import org.apache.commons.httpclient.Header 
import org.apache.commons.httpclient.HostConfiguration
import org.apache.commons.httpclient.HttpClient
import org.apache.commons.httpclient.NameValuePair
import org.apache.commons.httpclient.methods.GetMethod
import org.apache.commons.httpclient.methods.PostMethod
import org.apache.commons.httpclient.cookie.CookiePolicy
import org.apache.commons.httpclient.params.HttpClientParams

class Server  {
    String user
    String password
    String host
    Integer port
    String proto
    HttpClient client = new HttpClient()
    
Server(String user, String password, String host, Integer port, String proto) {
    this.user = user
    this.password=password
    this.host=host
    this.port=port
    this.proto=proto
    client.getHostConfiguration().setHost(host, port, proto);
    client.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
}
HttpClient getHttpClient() {
    return client
}
Integer logon() {
    String logonInitiationURL = proto + "://" + host + ":" + port + "/login?from=%2F"
    String logonSubmitURL = proto + "://" + host + ":" + port + "/j_acegi_security_check"

    // We need to make a call first to set up the session
    // HttpClient will automatically handle cookies based on the 
    // CookiePolicy set above
    GetMethod get = new GetMethod(logonInitiationURL)
    client.executeMethod(get);
    get.releaseConnection()
    PostMethod authpost = new PostMethod(logonSubmitURL)

    def json='{"j_username": "'+user+'", "j_password": "'+password+'", "remember_me": false, "from": "/"}'

    def param1=new NameValuePair("j_username", user)
    def param2=new NameValuePair("j_password", password)
    def param3=new NameValuePair("from", "/")
    def param4=new NameValuePair("json", json)
    def param5=new NameValuePair("Submit", "log in")

    authpost.addParameters(param1)
    authpost.addParameters(param2)
    authpost.addParameters(param3)
    authpost.addParameters(param4)
    authpost.addParameters(param5)

    client.executeMethod(authpost)
    
    // We need to follow the redirect to understand whether authentication 
    // was successful
    // 200 = Success
    // 401 = Credentials failure
    
    Header header = authpost.getResponseHeader("location");
    def response
    if (header != null) {
        String newuri = header.getValue();
        if ((newuri == null) || (newuri.equals(""))) {
            newuri = "/";
        }
        GetMethod redirect = new GetMethod(newuri);
        client.executeMethod(redirect);
        response=redirect.getStatusCode()
        redirect.releaseConnection()
    }
    authpost.releaseConnection()
    return response 
}


String getURL(String URL) {
    GetMethod get = new GetMethod(URL)
    client.executeMethod(get)
    def body = get.getResponseBodyAsString()
    get.releaseConnection()
    return body
}
        
}    

 

Creating the workflow

Create a new workflow job with the Workflow Definition as follows:

stage "Initialise"
def jenkins = new net.harniman.workflow.jenkins.Server("annie.admin","password",  "jenkins.beedemo.local",  80,  "http" )

def response

stage "Logon"
response = jenkins.logon()
echo "Response = $response"

stage "Query"
response = jenkins.getURL("http://jenkins.beedemo.local/roles/whoAmI")
echo "Response = $response"

Ensure you substitute in real values for:

You can substitute in the required URL for the query - for instance, it could be /config.xml to retrieve a job’s config.

Run the Job

If it works successfully you should see the raw body of the response printed in the console log.
A successful authentication results in

Response = 200

being printed in the console log. Anything else indicates a failure with the Logon step.

ConclusionDRY.

In this instance, we have also shown how the built-in Groovy libraries can be used to simplify making calls to external systems. These could be configuration databases, ticketing platforms or custom deployment tools. The underlying Groovy libraries can be leveraged to implement complex capabilities such as handling authentication, following redirects, etc. The advantage of using groovy rather than using command line tools from say an `sh` step, is that you have the option perform the logic outside of a `node` block. When executing inside a `node` block, an executor is allocated/consumed, whereas code outside the node block runs as a flyweight task and does not consume an executor.

References

HttpClient Examples
Jenkins Workflow - Getting Started by Udaypal Aarkoti

 

Blog Categories: 

Add new comment