Scripts for Docker container signing using PKCS11 library on Azure DevOps
Prerequisites
Linux operating system
Azure DevOps build system
JDK installed on the Linux agent
DigiCert® Software Trust Manager Access setup (see below)
Docker installation
DigiCert® Software Trust Manager PKCS11 Library (Client Tools)
Docker version is 20.10.7.
Nota
You can download client tools from DigiCert ONE® portal at DigiCert® Software Trust Manager > Resources > Client tool repository.
DigiCert® Software Trust Manager setup
To use client tools and connect to DigiCert® Software Trust Manager for operations, you must have access to DigiCert® Software Trust Manager on DigiCert One. If you do not have this, contact an administrator for DigiCert ONE and request a login.
Permissions
You must select the correct permissions for the user to be able to use the client tools to perform operations with DigiCert® Software Trust Manager. It is also important for the administrator to enable multi-factor authentication for the user, as it is required for compliance.
In Account Manager, enable at least View user and View organization for the user. In DigiCert® Software Trust Manager, enable all permissions.
Client authentication certificate setup
The client tools and PKCS11 library need to connect and authenticate with DigiCert® Software Trust Manager to perform its operations. A client authentication certificate helps with connection and authentication.
To generate a certificate for the user:
Sign in to DigiCert ONE.
Navigate to the Profile icon > Admin Profile > Authentication certificates > Create authentication certificate.
Importante
The information shown after generating the certificate cannot be accessed again, so you must to record all the information specified on the screen to use it later.
API token setup
You need an API token for the client tools and PKCS11 library to authorize a login with DigiCert® Software Trust Manager to perform its operations.
To generate an API token:
Sign in to DigiCert ONE.
Navigate to the Profile icon > Admin Profile > API tokens > Create API token.
Importante
You can only access the API token once after you have created it, and you must save the details so you can use it later.
Docker content trust setup
Docker content trust works with only on a Linux system. Also, the repository and target keys generated stay on the machine where it is setup (it cannot be managed by PKCS11).
Importante
Make sure you configure the Azure DevOps agent to run on the same machine and user where the Docker Content Trust is setup. The repository and target keys stay on the machine where it is setup and cannot be ported.
PKCS11 library setup
To register the Secure Software Manager provider with PKCS11:
Sign in to DigiCert ONE.
Navigate to DigiCert® Software Trust Manager > Resources > Client tool repository.
Downloading the shared library for the Linux OS.
Rename the library file and copy to a specific location so that Docker can pick it up.
Linux users: Rename smpkcs11.so to libykcs11.so and copy to /usr/local/lib
Docker root key setup
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 that is provided with java.
First, you need to create a PKCS11 configuration file with these parameters:
name=signingmanager library="<Path to PKCS11 shared library file(smpkcs11.so)>" slotListIndex=0
For examples shown below, this configuration will be created in a file called pkcs11properties.cfg.
Then, the keytool command can be issued 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
Importante
Docker keys work only when they are ECDSA 256 keys and not RSA keys.
Docker delegation key setup
Generate a delegation key and certificate for docker, which you can use to sign images. You can do this on the DigiCert ONE portal in DigiCert® Software Trust Manager or using the DigiCert® Software Trust Manager CLI:
smctl keypair generate ecdsa <Delegation Key Alias> --cert-alias=<Delegation Cert Alias> --cert-profile-id=<Certificate Profile ID> --generate-cert=true --key-type=PRODUCTION
Nota
The DigiCert® Software Trust Manager CLI (smctl) tool will not work until you have set up the SM environment variables for the pipeline (outlined later). The delegation key must be an ECDSA 256-bit key to work with docker.
The keypair name and certificate name must be unique inputs (cannot exist on the portal already).
You can retrieve the certificate profile ID from the DigiCert ONE portal at DigiCert® Software Trust Manager > Certificates > Certificate profiles. Choose a profile (Profile Category should be Test
or Production
based on the key type generated) that you want to use to generate the certificate.
Add delegation key as docker signer
You must initialize the docker repository, generate the repository keys internally, and add the delegation key previously generated as a delegation for the repository. To do this, download the certificate generated for the docker delegation key from the DigiCert ONE portal so you can add it as a delegation on Docker.
Go to DigiCert® Software Trust Manager > Certificates and select the certificate with the alias used in the previous step. Download the certificate from the details page, and use the information in the command:
docker trust signer add --key <Docker Delegation Key.crt from previous step> "<Name of the delegation>" <Registry URL>/<Repository Name>
Example:
docker trust signer add --key digicert_delegation.crt "Digicert" digicert/hello-digicert
The delegation can then be inspected using:
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 Azure DevOps pipeline
Agent setup for pipeline
You can only use a Linux node as an agent for the Docker Signing pipeline. This must use a self-hosted agent connecting to the same Linux machine on which the Docker content trust was setup. Make sure the node is setup on Azure DevOps to connect to the Linux machine and the user where the docker content trust was setup. In the example, an agent pool named default is setup with a single Linux node on it connecting to the same machine and user where the Docker content trust was setup.
pool: name: 'default' demands: agent.os -equals Linux
Environment variables setup for pipeline
The client tools need these environment variables to connect with DigiCert® Software Trust Manager to provide its service. They can be integrated as environment variables that are part of the pipeline as shown in the example below or they can be configured at an OS environment level.
Variables:
- name: SM_CLIENT_CERT_PASSWORD value: ********* - name: SM_CLIENT_CERT_FILE value: <Path to Client Auth Cert File> - name: SM_HOST value: https://clientauth.one.digicert.com - name: SM_API_KEY value: <API Token> - name: PKCS11_CONFIG value: <Path to pkcs11properties.cfg> #Remove the following variable if you want to enable trust for specific actions - name: DOCKER_CONTENT_TRUST value: 1
The values for these environment variables are explained below: SM_CLIENT_CERT_PASSWORD is the password from the client authorization setup. SM_CLIENT_CERT_FILE is the certificate downloaded from the client authorization setup. SM_HOST is the path to the DigiCert ONE portal with client authorization.
Nota
In most cases, this path stays as it is unless you are connecting to a self-hosted instance of the DigiCert ONE product. SM_API_KEY is the API token generated during API token setup. DOCKER_CONTENT_TRUST is the environment variable that enforces image signing and verification for operations in the pipeline. If it is defined here, then it applies to all operations in the pipeline unless you specifically disable it for a docker operation. This environment variable can also be set for a specific operation only.
Docker build without content trust
For images that are built on other base images, sometimes there is no trust data associated with the base image. If DOCKER_CONTENT_TRUST environment variable is setup for the entire pipeline, then a build step will fail due to missing trust data in one of the base images. It can be disabled for the specific build operation as shown:
- script: docker build -t <Registry URL>/<Repository name>:<Tag> --disable-content-trust=true <Path to Directory containing Dockerfile> displayName: 'Docker build'
Docker push signed tags
An example of an Azure DevOps pipeline step to push signed tags when DOCKER_CONTENT_TRUST is set for the entire pipeline as an environment variable:
- script: docker push <Registry URL>/<Repository name>:<Tag> displayName: 'Docker Sign and Push'
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.
An example of an Azure DevOps pipeline step to push signed tags with DOCKER_CONTENT_TRUST enabled only for the specific operation:
- script: DOCKER_CONTENT_TRUST=1 docker push <Registry URL>/<Repository name>:<Tag> displayName: 'Docker Sign and Push’
Nota
If a tag is not specified in the push operation, the image is not signed and trust metadata is not pushed to the notary server.
Pulling and verifying signed images
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. This operation is not restricted to the machine where the Docker Content Trust is setup. It can be done on any machine.
- script: docker pull <Registry URL>/<Repository name>:<Tag> displayName: 'Docker Pull & Verify'
When DOCKER_CONTENT_TRUST is not enabled for the entire pipeline as an environment variable, it can be enabled only for a specific operation as shown:
- script: DOCKER_CONTENT_TRUST=1 docker pull <Registry URL>/<Repository name>:<Tag> displayName: 'Docker Pull & Verify'
Sample pipeline
trigger: - main pool: name: 'default' demands: agent.os -equals Linux variables: - name: SM_CLIENT_CERT_PASSWORD value: gxmiK9Oe72Pn - name: SM_CLIENT_CERT_FILE value: "/home/siddharth/smtools/local_pkcs12.p12" - name: SM_HOST value: https://clientauth.one.digicert.com - name: SM_API_KEY value: 01ff018928e385329550b6e7a7_9b38fc5fbb4ef99ceeb7d61e6984107efa4283583cb7a64f5e292582cd3ed848 - name: PKCS11_CONFIG value: "/home/siddharth/smtools/pkcs11properties.cfg" - name: DOCKER_CONTENT_TRUST value: 1 steps: - task: Gradle@2 displayName: 'Gradle Build' inputs: workingDirectory: '' gradleWrapperFile: 'gradlew' gradleOptions: '-Xmx3072m' javaHomeOption: 'path' jdkDirectory: '/usr/lib/jvm/java-11-openjdk-amd64' jdkArchitectureOption: 'x64' publishJUnitResults: true testResultsFiles: '**/TEST-*.xml' tasks: 'build' - script: docker build -t siddharthsrinivas/hello-world:$(Build.BuildId) --disable-content-trust=true $(System.DefaultWorkingDirectory)/ displayName: 'Docker build' - script: | docker tag siddharthsrinivas/hello-world:$(Build.BuildId) siddharthsrinivas/hello-world:latest docker push siddharthsrinivas/hello-world:$(Build.BuildId) docker push siddharthsrinivas/hello-world:latest displayName: 'Docker Sign and Push' - script: | docker image rm siddharthsrinivas/hello-world:$(Build.BuildId) docker image rm siddharthsrinivas/hello-world:latest docker pull siddharthsrinivas/hello-world:$(Build.BuildId) docker pull siddharthsrinivas/hello-world displayName: 'Docker Verify'