roht.no: on.thor

Projects, articles, notes and other technical musings from a DevOps focused developer

Jenkins Checking Credentials

Thor K. Høgås

⏱ 4 minutes read. 📅 Published . ✎ Last updated .

In dealing with complex pipelines in Jenkins you may have come across how to deal with credentials. We're using Jenkins to automatically build virtual machine templates, and eventually technical debt in the form of a credentials constraint caught up with us. We needed different credentials for different artifacts or build targets, and because we suddenly needed so many, we needed a sensible default or fallback.

Quick Searching

Some precursory research yielded other users with the same issue. On Github there was an approach to find credentials based on a username, and while this is useful, it's not useful for us. We have a range of credential identifiers to lookup.

import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.common.*

def credentials_for_username(String username) {
  def username_matcher = CredentialsMatchers.withUsername(username)
  def available_credentials =
      CredentialsProvider.lookupCredentials(
        StandardUsernameCredentials.class,
        Jenkins.getInstance(),
        hudson.security.ACL.SYSTEM
      )

  return CredentialsMatchers.firstOrNull(available_credentials, username_matcher)
}

Then, there was an issue on Jenkins’ tracker “Provide some mechanism for looking up whether credentials exist in Pipeline”. A CloudBees employee suggests the following to check if credentials with a given ID exist:

boolean stringCredentialsExist(String id) {
  try {
    withCredentials([string(credentialsId: id, variable: 'irrelevant')]) {
      true
    }
  } catch (_) {
    false
  }
}

This is nice, but there are some drawbacks when we check for them.

Our Approach

What's the other alternative? Alternative C? It's quite close to alternative A, but slightly different. Here's a slightly different variant of solution A.

import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.common.*

/**
 * Check if usernamePassword credentials with given identifier exists.
 * @param id credentialsId to check
 */
boolean usernamePasswordExists(String id) {
  def idMatcher = CredentialsMatchers.withId(id)
  def availableCredentials = CredentialsProvider.lookupCredentials(
    UsernamePasswordCredentials.class,
	Jenkins.getInstance(),
    hudson.security.ACL.SYSTEM
  )
  return CredentialsMatchers.firstOrNull(availableCredentials, idMatcher) != null
}

However, this can be improved. If you use this in a Jenkinsfile, you will be stopped when trying to use CredentialsMatchers and CredentialsProvider. If you then proceed to approve the function calls you may be exposing more than you're really comfortable with, but that's your call. More importantly your run may not get access to the credentials due to passing the Jenkins instance itself to lookupCredentials.

That being said, an even better reason to not use CredentialsMatchers is that CredentialsProvider has – perhaps unsurprisingly – the method findCredentialById, because usually the credential id is well-known.

It has a different function signature, but only requires some minor alterations. First pass the id, then the class of the type of credentials we are interested in. Next, use the currentBuild variable and use the non-whitelisted getRawBuild() to get the Run object for the current run. You may switch the last parameter out with an empty list seeing as this is Groovy, but for the sake of readability we'll specify that we are sending in an empty list of requirements.

// vars/usernamePasswordExists.groovy
import com.cloudbees.plugins.credentials.CredentialsProvider
import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials
import com.cloudbees.plugins.credentials.domains.DomainRequirement

/**
 * Check if usernamePassword credentials with given identifier exists.
 * @param id credentialsId to check
 * @return True if a usernamePassword credentialId exists and is accessible.
 */
boolean call(String id) {
  def available_credentials = CredentialsProvider.findCredentialById(
    id,
    UsernamePasswordCredentials.class,
    currentBuild.getRawBuild(),
    Collections.<DomainRequirement>emptyList()
  )
  return available_credentials != null
}

Note that our function name is call, and not usernamePasswordExists. This is because the function should be used as part of a shared library, and thus placed in vars/usernamePasswordExists.groovy. The filename defines the function name when you use it in your pipeline, declarative or not.

With that, you have a quick way to check if credentials exist that doesn't look messy.

// ...
if (usernamePasswordExists(generatedCredentialsId)) {
  credentialsId = generatedCredentialsId
} else {
  credentialsId = fallbackCredentialsId
  }
}
withCredentials([usernamePassword(credentialId: credentialsId...)]) {
  // do stuff
}