Jenkins Checking Credentials
⏱ 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.
- It’ll show up in the log as if we’re attempting to use the credentials. That’s noise nobody is served with in pipelines that are already bordering on noisy.
- It’ll be tracked as a use of the credentials, which is unfortunate when we’re not using them but merely checking for its existence. That’ll be a disservice to us if we wish to see where credentials have been and continue to be used.
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
}