Jenkins can already mask your sensitive secrets and passwords by storing them with Jenkins Credentials. However, there are times when you may be using an external secret store for your pipelines.
This article will demonstrate how to mask external secrets in Jenkins pipelines with the mask password plugin. We also review how to keep those secrets from leaking into Blue Ocean dashboards.
First install the Jenkins Mask Passwords plugin. Navigate to manage Jenkins and click Manage Plugins.
Search for Mask Passwords. Select the plugin then click “Download now and install after restart”.
Once Mask Passwords is setup you can click Restart Jenkins to safely reboot.
Now that the plugin is installed we can use the MaskPasswordsBuildWrapper
in Jenkins pipelines. We will start with a locally defined secret to illustrate how to use the wrapper.
Try it out by creating a new Jenkins Pipeline and pasting in the following pipeline script:
pipeline { agent any stages { stage('Secret-Masking') { steps { script{ MASKED_SECRET = 'I_SHOULD_BE_MASKED' wrap([$class: 'MaskPasswordsBuildWrapper', varPasswordPairs: [[password: MASKED_SECRET]]]) { echo 'Retrieve Secret: ' + MASKED_SECRET echo MASKED_SECRET } } } } } }
Running this pipeline results in the following Jenkins console output:
Console Output Started by user admin [Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/secret-pipeline [Pipeline] { [Pipeline] stage [Pipeline] { (Secret-Masking) [Pipeline] script [Pipeline] { [Pipeline] maskPasswords [Pipeline] { [Pipeline] echo Retrieve Secret: ******** [Pipeline] echo ******** [Pipeline] } [Pipeline] // maskPasswords [Pipeline] } [Pipeline] // script [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS
Awesome! We have successfully masked our secrets in the Jenkins Console output. But there is another place we need to inspect for secret masking.
Blue Ocean is an updated Jenkins UI experience. Opening the Blue ocean console from the Jenkins build output reveals open issue 59214. The Blue Ocean output exposes the secret!
The secret value is still exposed even when the secret is used without string interpolation.
There is a workaround noted in the comments that allows us to use withEnv
to mask the secrets in the Blue Ocean console output.
Configure the following script using withEnv:
pipeline { agent any stages { stage('Secret-Masking') { steps { script{ MASKED_SECRET = 'I_SHOULD_BE_MASKED' wrap([$class: 'MaskPasswordsBuildWrapper', varPasswordPairs: [[password: MASKED_SECRET]]]) { withEnv(["SECRET=${MASKED_SECRET}"]){ echo 'Whoops interpolated secret leaked in Blue Ocean: ' + SECRET sh 'echo Mask that secret without interpolation: $SECRET' sh 'printenv | grep SECRET' } } } } } } }
The Jenkins console output still masks the secret as well as the environment variable.
Console Output Started by user admin [Pipeline] Start of Pipeline [Pipeline] node Running on Jenkins in /var/jenkins_home/workspace/secret-pipeline [Pipeline] { [Pipeline] stage [Pipeline] { (Secret-Masking) [Pipeline] script [Pipeline] { [Pipeline] maskPasswords [Pipeline] { [Pipeline] withEnv [Pipeline] { [Pipeline] echo Whoops interpolated secret leaked in Blue Ocean: ******** [Pipeline] sh + echo Mask that secret without interpolation: ******** Mask that secret without interpolation: ******** [Pipeline] sh + printenv + grep SECRET SECRET=******** [Pipeline] } [Pipeline] // withEnv [Pipeline] } [Pipeline] // maskPasswords [Pipeline] } [Pipeline] // script [Pipeline] } [Pipeline] // stage [Pipeline] } [Pipeline] // node [Pipeline] End of Pipeline Finished: SUCCESS
However, now when we look at the Blue Ocean output the $SECRET is masked there as well in the step.
Take note, this technique still exposes secrets if used with string interpolation.
We will use CloudTruth as the external secrets manager for Jenkins. CloudTruth is a secrets manager that syncs your secrets across multiple projects and environment.
From our community edition you can setup your first secrets via the UI or the CLI. The following CLI command creates a CloudTruth secret called “secret” that we will pull into Jenkins:
cloudtruth --project MyFirstProject parameter set secret -v I_SHOULD_BE_MASKED --secret true
Configure the following script to set an external secret as an environment variable:
pipeline { environment { CLOUDTRUTH_API_KEY = credentials('CLOUDTRUTH_API_KEY') } agent any stages { stage('CloudTruth') { steps { script{ CLOUDTRUTH_SECRET = sh(script:'cloudtruth --project MyFirstProject --env default parameters get secret', returnStdout: true).trim() wrap([$class: 'MaskPasswordsBuildWrapper', varPasswordPairs: [[password: CLOUDTRUTH_SECRET]]]) { withEnv(["SECRET=${CLOUDTRUTH_SECRET}"]){ sh 'echo Mask that secret without interpolation: $SECRET' sh 'printenv | grep SECRET' } } } } } } }
The pipeline uses the following shell script to retrieve and set a secret via the CloudTruth CLI.
CLOUDTRUTH_SECRET = sh(script:'cloudtruth --project MyFirstProject --env default parameters get secret', returnStdout: true).trim()
The console output will mask the secret when called with the Mask Password wrapper.
pipeline { environment { CLOUDTRUTH_API_KEY = credentials('CLOUDTRUTH_API_KEY') } agent any stages { stage('CloudTruth') { steps { script{ CLOUDTRUTH_SECRET = sh(script:'cloudtruth --project MyFirstProject --env default parameters get secret', returnStdout: true).trim() wrap([$class: 'MaskPasswordsBuildWrapper', varPasswordPairs: [[password: CLOUDTRUTH_SECRET]]]) { withEnv(["SECRET=${CLOUDTRUTH_SECRET}"]){ sh 'echo Mask that secret without interpolation: $SECRET' sh 'printenv | grep SECRET' } } } } } } }
Using the techniques we described above we have also successfully masked the external secret in the Blue Ocean console output.