To automatically configure secrets on Jenkins

first, a disclaimer

Just to make this upfront, I have tested this on Jenkins 2.89.3, with Credentials Binding plugin 1.14. I think I should make myself a CI/CD to test these snippets one day, because nobody likes a an out of date how-to, especially if that person will be me, in a year, trying to quickly solve a Jenkins configuration problem.

I assume that you have the account setup for cli usage, with alias for jenkins-cli in your shell. Second, there might be a better way. For example, if you are using Jenkins just to run your ansible-scripts, you could use ansible-valut, if you are relying on Kubernetes, you could configure secrets through kubectl.

In my experience, storing secrets in Jenkins is completely acceptable. The secrets are encrypted on rest. When you log them to console, Jenkins will transparently display ** instead of them. There is support for various types of them:

  • Plain text
  • Username/Password
  • SSH private key
  • AWS credentials

and more. Of course you could configure these through the management UI in the credentials section, but we didn't want to do that. We want to be able to have our credentials configured from code.

Second disclaimer, you might want to investigate if you want to have your credential scopes in Jenkins. We wave all of our credentials in the global scope, available of all jobs to use. We don't mind, because with our setup we would often have several distinct Jenkins instances, housing various secrets. For example Jenkins that is publicly accessible only has AWS credentials to push build artifacts to S3 bucket, but other instance, behind VPN, might have AWS credentials for EC2 as well, and we might run pipelines that spawn/update our staging infrastructure from there.

create a groovy script with the payload

For example with this groovy file, I can add credentials containing username and password:

There are only two lines really specific to plain text credentials. You can replace i.e.:

secret = new UsernamePasswordCredentialsImpl( //...

with other credential definitions. For example for plain-text credential (that has no username):

import org.jenkinsci.plugins.plaincredentials.impl.*
import hudson.util.Secret
def secret = new StringCredentialsImpl(CredentialsScope.GLOBAL,credId,description, Secret.fromString(password))

For ssh credentials:

import com.cloudbees.jenkins.plugins.sshcredentials.impl.*
import hudson.plugins.sshslaves.*;
def username = "HARDCODED"
def privateKeyStr = "-----BEGIN RSA PRIVATE KEY-----\nMII..."
def privateKeySource = new BasicSSHUserPrivateKey.DirectEntryPrivateKeySource(privateKeyStr)
def secret = new BasicSSHUserPrivateKey(
	CredentialsScope.GLOBAL,
	credId,
	username,
	privateKeySource,
	"",
	description
)

In case of ssh credentials, we usually hardcode the private key string in the groovy-file itself, because we encountered some problems where escaping mechanisms of shell seem to have collided with escaping mechanisms of groovy script transported over http. This would get even trickier if you'd needed to create a credential that is binary.

For AWS credentials, if we supply the access key as username and secret key as password:

import com.cloudbees.jenkins.plugins.awscredentials.*
def secret = new AWSCredentialsImpl(
	CredentialsScope.GLOBAL,
	credId,
	username,
	password,
	description
)

execute the script with jenkins-cli

To create the credentials (in this case, I am assuming username password), just run the jenkins-cli groovy command. I have alias jenkins-cli=java -jar /opt/jenkins-cli.jar, and ssh key configured on my Jenkins master which means that I would run:

use from a Jenkinsfile

When I use these in my scripts, pipelines and Jenkinsfiles, I have to admit, that I usually generate the actual code with snippet-generator. This makes my work much easier especially if I need to work with credential I haven't worked with, or with several credentials at once. You should be able to find the generator on https://$JENKINSURL/pipeline-syntax/

generate_credentials.png

If you'd then tried out a pipeline script such as this, you should be able to use the password injected in the environment variable.

node {
  withCredentials([string(credentialsId: 'example_secret, variable: 'TOKEN')]) {
    print "The secret is ${env.TOKEN}"
  }
}

Beware, you shouldn't see the password itself if you print it, and this should give you some level of protection, i.e if you'd want to have your job-logs public (and we did want that for our opensource projects).

If you liked this, and want to ask anything, (or didn't and want to tell me how to do something better), feel free to write me an email to adam at asaleh.net!