Jenkins: Working with Credentials in your pipeline

Security is mandatory, and should always have been, so eventually, you’ll run into the requirement of having to pass credentials to a system in your pipelines.

It could be you trying to authenticate with your Binary Repository Manager (e.g. Nexus, Artifactory), your Source Code Management system (e.g. SVN, git, TFS) or an internal system like JIRA or a ChatOps-like application (Mattermost, Slack), but credentials are most likely necessary and mandatory.
Luckily, Jenkins knows the concept of a secret and calls it a credential. For more information on this topic, please see the documentation.

These credentials can also be used in your pipeline, and they are implemented as the helper function credentials() in the environment block.

This post is part of a Series in which I elaborate on my best practices for running Jenkins at scale which might benefit Agile teams and CI/CD efforts.

In this post, I’ll try to show the usage of the helper function in your Jenkins Pipeline, and how to write tests to validate that.

As our example pipeline so far did not include any credentials, we’ll have to modify it.

Our pipeline

This pipeline has roughly the same setup as the one I have used before: it builds the code with Maven and publishes it to Nexus. This pipeline also deploys the code to a JBoss instance called ‘DEV’ through a Custom Pipeline Step. For brevity, this Pipeline has been stripped of the post steps and the stage conditionals.

pipeline {
    agent none

    stages {
        stage('Build and Unit test') {
            agent { label 'maven' }
            steps {
                script {
                    module_Maven('clean verify')
                }
            }
        }
        stage('Deploy to JBoss DEV') {
            agent { label 'maven' }
            environment {
                JBOSS_CREDS = credentials('jenkins-jboss-dev-creds')
            }
            steps {
                script {
                    module_Deploy('DEV', JBOSS_CREDS_USR, JBOSS_CREDS_PSW)
                }
            }
        }
        stage('Publish to Nexus') {
            agent { label 'maven' }
            steps {
                script {
                    echo "This is where we publish to Nexus"
                    module_Artifact.publish()
                }
            }
        }
    }
}

The helper function credentials() needs to be inside an environment block. This block can live either on a global (Pipeline) level or as part of a stage. The Pipeline shown here has the block as part of a stage, to avoid injecting the credentials into places where it is not required making it just a little more secure.

This Pipeline assumes the availablility of a credential in your Jenkins instance by the name of jenkins-jboss-dev-creds.

There is a little bit of magic in Jenkins itself, which creates not one, but three environment variables when you’re using the credentials() helper function:

  • <VARIABLE_NAME>
  • <VARIABLE_NAME>_USR
  • <VARIABLE_NAME>_PSW

Not only does Jenkins make the variable you referred to in your pipeline, it also creates two new ones with appendices _USR and _PSW which contain the separate username and password respectively. The variable itself contains both of these values with a colon (:) as separator (username:password).

The close reader will have found the code by now, but for completeness I’ll also post it here:

environment {
    JBOSS_CREDS = credentials('jenkins-jboss-dev-creds')
}
steps {
    script {
        module_Deploy('DEV', JBOSS_CREDS_USR, JBOSS_CREDS_PSW)
    }
}

The pipeline code above passes the two separate variables for the user and password, together with the name of the environment we’re trying to deploy to, to a Custom Pipeline Step called module_Deploy.

The Test

Without much further ado, I hereby present to you the Happy flow testcase:

void 'Happy flow'() {
    given:
    binding.setVariable("JBOSS_CREDS_USR", "username")
    binding.setVariable("JBOSS_CREDS_PSW", "password")

    when:
    script.call([:])

    then:
    1 * mavenMock.call('clean verify')
    1 * deployMock.call('DEV', _, _)
    1 * artifactMock.publish()
    assertJobStatusSuccess()
}

This code requires some setup in the other parts of the pipeline test though: you need to define a new global called deployMock, and initialise it correctly in the helper method registerMocks(). The test itself defines two global variables for the Jenkins Pipeline, and sets the value of those variables to something meaningful.
The test then assumes a single call to the deployMock, with a fixed value of <em>DEV</em> for the environment and wildcardvalues for the username/password.

If all goes well, it should give you a green test run, and cheers all around!
Wel, maybe not the latter part, but you get the gist of where I’m going with that.

Future developments

Currently, I’m looking into expanding the test frameworks to ‘magically’ add the required Variables to the mocked Jenkins runtime during the test runs instead of you (as the author of the test) having to manually assign the correct values to the correct variables when needed. Just like the actual plugin would do.
I will update this post when I’ve gotten around to writing the required code!

Conclusion

The use of credentials in your Jenkins Pipelines can very easily be mocked and validated in your Pipeline Tests, by simply adding the correct Variables to your mocked Jenkins runtime with code like this:

binding.setVariable("JBOSS_CREDS_USR", "username")

When you’re running into issues here, please let me know by contacting me on this blog or through @maartentijhof.
Happy testing!