Docker Content Trust

Azure DevOps Services

Docker Content Trust (DCT) lets you use digital signatures for data sent to and received from remote Docker registries. These signatures allow client-side or runtime verification of the integrity and publisher of specific image tags.

Note

A prerequisite for signing an image is a Docker Registry with a Notary server attached (examples include Docker Hub or Azure Container Registry)

Signing images in Azure Pipelines

Prerequisites on development machine

  1. Use Docker trust's built-in generator or manually generate delegation key pair. If the built-in generator is used, the delegation private key is imported into the local Docker trust store. Else, the private key will need to be manually imported into the local Docker trust store. See Manually Generating Keys for details.
  2. Using the delegation key generated from the step above, upload the first key to a delegation and initiate the repository

Tip

To view the list of local Delegation keys, use the Notary CLI to run the following command: $ notary key list.

Set up pipeline for signing images

  1. Grab the delegation private key, which is in the local Docker trust store of your development machine used earlier, and add the same as a secure file in Pipelines.

  2. Authorize this secure file for use in all pipelines.

  3. The service principal associated with containerRegistryServiceConnection must have the AcrImageSigner role in the target container registry.

  4. Create a pipeline based on the following YAML snippet:

    pool:
      vmImage: 'Ubuntu 16.04'
    
    variables:
      system.debug: true
      containerRegistryServiceConnection: serviceConnectionName
      imageRepository: foobar/content-trust
      tag: test
    
    steps:
    - task: Docker@2
      inputs:
        command: login
        containerRegistry: $(containerRegistryServiceConnection)
    
    - task: DownloadSecureFile@1
      name: privateKey
      inputs:
        secureFile: cc8f3c6f998bee63fefaaabc5a2202eab06867b83f491813326481f56a95466f.key
    - script: |
        mkdir -p $(DOCKER_CONFIG)/trust/private
        cp $(privateKey.secureFilePath) $(DOCKER_CONFIG)/trust/private
    
    - task: Docker@2
      inputs:
        command: build
        Dockerfile: '**/Dockerfile'
        containerRegistry: $(containerRegistryServiceConnection)
        repository: $(imageRepository)
        tags: |
          $(tag)
        arguments: '--disable-content-trust=false'
    
    - task: Docker@2
      inputs: 
        command: push
        containerRegistry: $(containerRegistryServiceConnection)
        repository: $(imageRepository)
        tags: |
          $(tag)
        arguments: '--disable-content-trust=false'
      env:
        DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE: $(DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE)
    

    In the previous example, the DOCKER_CONFIG variable is set by the login command in the Docker task. We recommend that you set up DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE as a secret variable for your pipeline. The alternative approach of using a pipeline variable in YAML exposes the passphrase in plain text. DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE in this example refers to the private key's passphrase (not the repository passphrase). We only need the private key's passphrase in this example because the repository has been initiated already (prerequisites).