Scripts for Docker integration with Jenkins using PKCS11 library
Get DigiCert® Software Trust Manager client tools set up and integrated with Jenkins and Docker Content Trust so that it can be automated into a CI/CD pipeline.
Prerequisites
Linux operating system
Jenkins build system
JDK installed on the Linux agent
Install Docker (version is 20.10.7.)
DigiCert ONE Client authentication certificate
Docker Content Trust setup
Docker Content Trust only works with DigiCert® Software Trust Manager on Linux.
Important
Make sure you configure the Jenkins agent to run on the same machine and user where the Docker Content trust is set up. The repository and generated keys remain on the machine where it is setup and cannot be ported or managed by PKCS11.
Download and set up PKCS11 library
The following process guides you through the process to download and register the DigiCert® Software Trust Manager PKCS11 library so that Docker can pick it up.
Sign in to DigiCert ONE.
Navigate to DigiCert® Software Trust Manager > Resources > Client tool repository.
Select Linux as the operating system.
Click the download icon next to DigiCert® Software Trust Manager PKCS#11 Library.
Rename smpkcs11.so to libykcs11.so.
Copy libykcs11.so to
/usr/local/lib
.
Set up Docker root key
You need to generate the Docker root key. The root key needs to have a certificate with “CN=root” in it. The following example generates it on DigiCert® Software Trust Manager with keytool provided with Java.
Create a PKCS11 configuration file with the following parameters:
name=signingmanager library="<Path to PKCS11 shared library file(smpkcs11.so)>" slotListIndex=0
Name the file pkcs11properties.cfg.
Run the keytool command to generate the root key.
keytool -keystore NONE -storetype PKCS11 -storepass NONE -providerClass sun.security.pkcs11.SunPKCS11 -providerArg <Path to pkcs11properties.cfg> -genkeypair -keyalg EC -keysize 256 -dname “CN=root” -alias docker_root_key
Important
The delegation key must be an ECDSA 256-bit key to work with Docker. Docker keys do not work with RSA keys.
Set up Docker delegation key
A delegation key and certificate is required to sign images in Docker. You can do this in Software Trust Manager or using SMCTL:
Add delegation key as Docker signer
Initialize the Docker repository.
Generate the repository keys internally.
Add the delegation key as a delegation for the repository.
Download the certificate you generated for the Docker delegation key in Software Trust Manager or SMCTL.
Reference the downloaded certificate in this command:
docker trust signer add --key <Docker Delegation Key.crt from previous step> "<Name of the delegation>" <Registry URL>/<Repository Name>
Command sample
docker trust signer add --key digicert_delegation.crt "Digicert" digicert/hello-digicert
To inspect the delegation, run:
docker trust inspect –pretty <Registry URL>/<Repository Name>
When an image push is done with content trust enabled, Docker signs the tag using the delegation key and pushes the trust data to the remote notary server. When an image pull/run is done with content trust enabled, the image is verified.
Integration with Jenkins
Agent setup for pipeline
You can only use a Linux node as an agent for the Docker Signing pipeline.
Make sure the node is setup on Jenkins to connect to the Linux machine and the user where the Docker Content Trust was setup.
In the example below, a node is defined connecting to the Linux machine where the Docker Content Trust is setup and labelled ‘Linux’.
pipeline { agent{ label 'linux' } }
Best practices for secure Jenkins use (Recommended)
Use secret text and files to ensure security and accountability among your Jenkins users when they use DigiCert® Software Trust Manager to sign code. The code examples later assume that you are using secret text and files.
Jenkins secrets
Secrets are variables in Jenkins encrypted so users can input information without knowing what the value of that information is. For example, you do not want all of your Jenkins collaborators knowing what your unique API key is, but they may need to use it to access signing tools through DigiCert® Software Trust Manager. You can set up a variable where "(api_key)" is the name and the value is the API key itself.
Configure Jenkins secrets
Note
Only Jenkins user's with the Credentials > Create permission can add new global credentials.
To add new global credentials:
Log in to Jenkins.
Navigate to: Jenkins homepage or dashboard > Manage Jenkins > Manage Credentials > Store > Jenkins > System > Global credentials (unrestricted).
Select Add credentials.
Select the Scope you want to use:
Scope
Description
Global
Apply the scope of the credential/s to the Pipeline project/item "object" and all its descendant objects.
System
Apply the scope of the credential/s to a single object only.
Add the following types of credentials.
ID
Credential type
Description
SM_API_KEY
Secret text
Copy and paste your API token in the Secret field.
SM_CLIENT_CERT_FILE
Secret file
Select choose file and upload your client authentication certificate.
SM_CLIENT_CERT_PASSWORD
Secret text
Copy and paste your client certificate password in the Secret field.
SM_HOST
Secret text
Copy and paste your host environment in the Secret field.
Integration with Jenkins
Environment variables setup for Jenkins plugin in pipeline script
The client tools need these environment variables to connect with DigiCert® Software Trust Manager to provide its service.
To integrate as environment variables that are part of the pipeline:
Docker build without content trust
Sometimes no trust data is associated with images built on other base images. When DOCKER_CONTENT_TRUST is enabled for the entire pipeline as an environment variable, the build stage or step will fail due to missing trust data in one of the base images.
To disable content trust for the specific build operation, use:
stage('Docker Build') { steps { sh 'docker build -t <Registry URL>/<Repository Name>:<Tag> --disable-content-trust=true .' } }
Docker signed push tags
To push signed tags when DOCKER_CONTENT_TRUST is set for the entire pipeline as an environment variable:
stage('Docker Push Signed Tags') { steps { sh 'DOCKER_CONTENT_TRUST=1 docker push <Registry URL>/<Repository Name>:<Tag>' } }
The signing is automatically done with the delegation key from DigiCert® Software Trust Manager using the PKCS11 library because we have set the DOCKER_CONTENT_TRUST environment variable for the entire pipeline.
To push signed tags with DOCKER_CONTENT_TRUST enabled only for the specific operation:
stage('Docker Push Signed Tags') { steps { sh 'DOCKER_CONTENT_TRUST=1 docker push <Registry URL>/<Repository Name>:<Tag>' } }
Note
If a tag is not specified in the push operation then the image is not signed and trust metadata will not be pushed to the notary server.
Pulling and verifying signed images
Tip
This operation is not restricted to the machine where the Docker Content Trust is set up. It can be done on any machine.
When DOCKER_CONTENT_TRUST is enabled for the entire pipeline as an environment variable, a docker pull/run of any signed image will automatically verify the image:
stage('Docker Verify') { steps { sh 'docker pull <Registry URL>/<Repository Name>:<Tag>' } }
When DOCKER_CONTENT_TRUST is not enabled for the entire pipeline as an environment variable, it can be enabled only for a specific operation:
stage('Docker Verify') { steps { sh 'DOCKER_CONTENT_TRUST=1 docker pull <Registry URL>/<Repository Name>:<Tag>' } }
Sample pipeline
pipeline { agent{ label 'linux' } tools { gradle 'Gradle' } environment { SM_CLIENT_CERT_PASSWORD="gxmiK9Oe72Pn" SM_CLIENT_CERT_FILE="/home/siddharth/smtools/local_pkcs12.p12" SM_HOST="https://clientauth.one.digicert.com" SM_API_KEY="01ff018928e385329550b6e7a7_9b38fc5fbb4ef99ceeb7d61e6984107efa4283583cb7a64f5e292582cd3ed848" } stages { stage('Checkout') { steps { checkout([$class: 'GitSCM', branches: [[name: '*/main']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'Siddharth-Srinivas', url: 'https://github.com/Siddharth-Srinivas/jenkins-test-repo.git']]]) } } stage('Clean') { steps { sh "./gradlew clean" } } stage('Test') { steps { withGradle { sh "./gradlew test" } } post { success { junit "**/build/test-results/test/TEST-*.xml" } } } stage('Assemble') { steps { withGradle { sh "./gradlew build" } } post { success { archiveArtifacts "app/build/libs/app.jar" } } } stage('Docker Build') { steps { sh "docker build -t siddharthsrinivas/hello-world:${env.BUILD_NUMBER} ." } } stage('Docker Push Signed Tags') { steps { sh "docker tag siddharthsrinivas/hello-world:${env.BUILD_NUMBER} siddharthsrinivas/hello-world:latest" sh "DOCKER_CONTENT_TRUST=1 docker push siddharthsrinivas/hello-world:latest" sh "DOCKER_CONTENT_TRUST=1 docker push siddharthsrinivas/hello-world:${env.BUILD_NUMBER}" } } stage('Docker Verify') { steps { sh "DOCKER_CONTENT_TRUST=1 docker pull siddharthsrinivas/hello-world:${env.BUILD_NUMBER}" sh "DOCKER_CONTENT_TRUST=1 docker pull siddharthsrinivas/hello-world " } } } }