The below is part of a screenplay I’m working on for a sequel in the Speed franchise, tentatively called Speed 3: Push IT. It also nicely sets up a scenario for the later bit about viewing Jenkins credentials in plaintext.
If you’d rather not read it, just scroll on down. I won’t be too hurt.
Speed 3: Push IT
FADE IN
INT. SOME LARGE CORPORATION - NIGHT
JASMINE and BORIS are looking at Jasmine’s computer. Jasmine is in the process of accepting a secret file from Boris and saving it locally to her machine.
JASMINE
There, it’s done. I’ve saved it in my temp directory. Gonna upload it to the Jenkins credential store now.
Jasmine logs into the Jenkins UI, opens the credentials page, and uploads the secret.
JASMINE
Okay, it’s safely in Jenkins now. Deleting my local copy. Done.
BORIS
Great. Gonna delete my copy now too.
Both local copies have been deleted and the only remaining copy exists in Jenkins. They both go back to their desks and start getting their stuff ready to leave for the night.
Jasmine’s phone starts ringing. She picks it up.
Screaming is heard through the phone.
CALLER
Jasmine! We need that secret file for [insert really horrible yet plausible reason here]! Send us a copy now!
JASMINE
I can’t, we’ve deleted our local copies.
CALLER
Pop quiz, hotshot. That was an encryption key used for our database. If we don’t get it, ALL of our data is effectively gone and we’re all out of a job. What do you do? WHAT DO YOU DO?
This is just one scene in a potential movie’s worth of them where being able to read a Jenkins stored credential in plaintext would be beneficial.
I’m sorry, I’m afraid I can’t do that
Jenkins can store credentials. This is good.
It can hide them if someone tries to output them in a build log. This is also good.
It can stop any potentially ill-willed non-administrative user from lifting them and wreaking havoc. This is very good.
Jenkins is protecting us from ourselves. And our users.
Show me what ya got
Jenkins is not quite as altruistic as we’ve made it seem and if we ask it in just the right manner, it will divulge those sweet sweet secrets.
The explanation
There isn’t much to the logic of what we’re doing below. We’re retrieving the credentials from the store as we normally would for use but instead of using it immediately in a block of code, we’re writing it to a variable that we’ve defined in the beginning of the pipeline. Then we’re simply printing out this variable later on.
The important bits are:
- writing it to a variable declared outside the stage that retrieves the credential with the function
withCredentials
- printing it outside the
withCredentials
block above
Normally we’d use the credential in something like a sh
block within the withCredentials
block. Doing so would display a masked credential but saving it to a variable and printing it outside of withCredentials
gives us the actual value.
The magic
// Variables to hold the actual secret value and what type it is (user/pass, text, file)
def secretValue
def typeText
// These store the runtime user input for cred type and cred ID
def credId = params.credId
def credType = params.credType
node("node_label") {
stage ('Get Credentials') {
if(credType.equals("userPass")) {
withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: "${credId}",
usernameVariable: 'theUser', passwordVariable: 'thePassword']]) {
typeText = "Password for ${credId}"
secretValue = "${thePassword}"
}
} else if(credType.equals("secretText")) {
withCredentials([string(credentialsId: "${credId}", variable: 'secretText')]) {
typeText = "Secret text for ${credId}"
secretValue = "${secretText}"
}
} else if(credType.equals("secretFile")) {
withCredentials([file(credentialsId: "${credId}", variable: 'secretFile')]) {
typeText = "Secret file for ${credId}"
secretValue = sh(returnStdout: true, script: "cat ${secretFile}").trim()
}
}
}
stage('Print it out') {
println "<< ${typeText} >>\n${secretValue}"
}
}
We’re checking credType
(chosen by the user at runtime via a dropdown) to see whether the credential is a user/pass combo, a secret text, or a secret file.
The credId
is also provided by the user at runtime and passed to the withCredentials
block to retrieve and save the credential value to a variable.
The job DSL code below defines the two parameters credId
and credType
and creates the pipeline job that runs the script above.
pipelineJob("jenkins-print-creds") {
definition {
parameters {
choiceParam('credType', ['userPass', 'secretText', 'secretFile'], 'Type of credential')
stringParam('credId', '', 'Credential ID')
}
cpsScm {
scm {
git {
branch('YOUR_GIT_BRANCH')
remote {
url("YOUR_GIT_REPO_THAT_HOLDS_THE_ABOVE_JENKINSFILE")
credentials('YOUR_GIT_CREDENTIAL')
}
}
}
scriptPath("Jenkinsfile")
}
}
}
Caveat
Another way of viewing credentials is through the Jenkins Script Console, a Groovy console for running arbitrary Groovy code on the master or agents. You can use the hudson.util.Secret.decrypt
function to decrypt and then print the value. The process is fairly simple and outlined in this stackoverflow post.
However, this feature is usually and correctly limited to just administrators. This blog post assumes that the user doesn’t have access to this console.