#+BEGIN_COMMENT .. title: Configuring secrets in Jenkins master from shell .. slug: configuring-secrets-in-jenkins-master-from-shell .. date: 2018-01-08 18:27:14 UTC+01:00 .. tags: .. category: .. link: .. description: .. type: text #+END_COMMENT * To automatically configure secrets on Jenkins ** first, a disclaimer Just to make this upfront, I have tested this on Jenkins 2.89.3, with [[https://plugins.jenkins.io/credentials-binding][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: #+NAME: credentials #+BEGIN_SRC groovy :tangle ../files/scripts/credentials.groovy :exports none import jenkins.model.* import com.cloudbees.plugins.credentials.* import com.cloudbees.plugins.credentials.common.* import com.cloudbees.plugins.credentials.domains.* import com.cloudbees.plugins.credentials.impl.* def credId = args[0] def description = args[1] def username = args[2] def password = args[3] def store = Jenkins.instance.getExtensionList('com.cloudbees.plugins.credentials.SystemCredentialsProvider')[0].getStore() def secret = new UsernamePasswordCredentialsImpl( CredentialsScope.GLOBAL, credId, description, username, password ) store.addCredentials(Domain.global(), secret) #+END_SRC There are only two lines really specific to plain text credentials. You can replace i.e.: #+BEGIN_SRC groovy secret = new UsernamePasswordCredentialsImpl( //... #+END_SRC with other credential definitions. For example for plain-text credential (that has no username): #+BEGIN_SRC groovy import org.jenkinsci.plugins.plaincredentials.impl.* import hudson.util.Secret def secret = new StringCredentialsImpl(CredentialsScope.GLOBAL,credId,description, Secret.fromString(password)) #+END_SRC For ssh credentials: #+BEGIN_SRC groovy 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 ) #+END_SRC 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: #+BEGIN_SRC groovy import com.cloudbees.jenkins.plugins.awscredentials.* def secret = new AWSCredentialsImpl( CredentialsScope.GLOBAL, credId, username, password, description ) #+END_SRC ** 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: #+NAME: jenkins_cli #+BEGIN_SRC bash :tangle ../files/scripts/credentials_example.sh :exports none jenkins-cli -remoting -s $url groovy credentials.groovy "example_secret" "What are the magic words?" "adamsaleh" "squeamishossifrage" #+END_SRC ** 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/~ [[../../images/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. #+BEGIN_SRC groovy node { withCredentials([string(credentialsId: 'example_secret, variable: 'TOKEN')]) { print "The secret is ${env.TOKEN}" } } #+END_SRC 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!