How to mask secrets in Jenkins with the Mask Password plugin
November 16, 2021
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.
Installing the Mask Password Plugin
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.
Using the Mask Passwords Build Wrapper
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:
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.
Trouble in paradise
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!
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.
Mask external secrets in Jenkins
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.