How to mask secrets in Jenkins with the Mask Password plugin

Darryl Diosomito

Darryl Diosomito

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.

manage plugins
Add a caption

Search for Mask Passwords. Select the plugin then clickDownload now and install after restart”.

maskpasswordsplugin
Add a caption

Once Mask Passwords is setup you can click Restart Jenkins to safely reboot.

restart jenkins
Add a caption

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:

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.

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!

The secret value is still exposed even when the secret is used without string interpolation.

exposed secret blue ocean
Add a caption

There is a workaround noted in the comments that allows us to use withEnv to mask the secrets in the Blue Ocean console output.

Mask secrets in Jenkins Blue Ocean

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.

blue ocean secret masked
Add a caption

Take note, this technique still exposes secrets if used with string interpolation.

interpolated secret leak
Add a caption

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.

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.

masked external secret
Add a caption