Skip to main content

Signing a Docker image

The Docker image signing procedure below is partly aligned with the actors defined in the Notary Notation Project Signing Scenario and the example given in Notary Notation Create Trust Policy and Verify Artifacts, where:

  • Wabbit Networks company builds, signs and distributes their net-monitor software though public registries,

  • ACME Rockets consumes the net-monitor software from a public registry importing the artifacts and reference artifacts (signatures, SBoMs) into their private registry. The private registry also contains additional artifacts that ACME Rockets sign themselves.

In the subsequent steps, we set up a registry on the local machine using the ORAS CLI project registry. We proceed to build and tag a Docker image from the net-monitor sources on the GitHub project, and subsequently push it to the local registry. Following this, the image undergoes signing using the key established in chapter and the certificate introduced in chapter.

For the sake of simplicity, the procedure does not differentiate between the roles of the signature issuer and verifier.

CAUTION

The following example creates a registry with oras-project/registry in your local Docker environment. This registry should only be used for development purposes. When using other registries, ensure the registry is compatible with OCI Image specification v1.1.0. Starting with v1.0.0-rc.1 of notation, by default, signatures are stored using OCI Artifact Manifest, which is defined in OCI Image spec v1.1.0.

Create a local registry based on the oras-project:

docker run -d -p 5000:5000 ghcr.io/oras-project/registry:v1.0.0-rc.4
Note

For Apple silicon, add the --platform linux/arm64 parameter.

WARNING

Use oras-project/registry:v1.0.0-rc.4. The version v1.0.0-rc.3 as describe here will fail at the signing command.

Clone from github repository, build the docker image, add image tag (net-monitor:v1). Then push it to your local registry:

docker buildx build -t localhost:5000/net-monitor:v1 https://github.com/wabbit-networks/net-monitor.git#main

docker push localhost:5000/net-monitor:v1
CAUTION

Always use the digest value of an image when signing since they are immutable. Tag values are mutable and can reference a different container image than the original signed container image.

Save the digest value of the image from the output of docker push. Example:

The push refers to repository [localhost:5000/net-monitor] 

2556c54bfdf3: Pushed

fb6ca4f9c8d3: Pushed

ded7a220bb05: Pushed

v1: digest: sha256: 9bba24af535af5ce658c5cd78c865d574cb65443c643d8538b73ea7d3e9bb64e size: 942

Alternatively, you can find the image digest using the docker inspect or docker images --digests command. In the above example, the digest value of the image is:

sha256:9bba24af535af5ce658c5cd78c865d574cb65443c643d8538b73ea7d3e9bb64e

Set the value of $IMAGE environment variable to the name of the image and its digest value to facilitate the use of subsequent commands:

IMAGE=localhost:5000/net-monitor@sha256:9bba24af535af5ce658c5cd78c865d574cb65443c643d8538b73ea7d3e9bb64e

Use the below command to list the current signatures for the image and confirm there are no signatures listed:

notation ls $IMAGE 

localhost:5000/net-monitor@sha256: 9bba24af535af5ce658c5cd78c865d574cb65443c643d8538b73ea7d3e9bb64e has no associated signature

Change the working directory to $CONFIG_PATH:

cd $CONIFG_PATH

Use notation sign to sign the image:

notation sign $IMAGE --key "keyId"

Notation sign command parameters:

Command parametersCommand and parameter description
"keyId": "pluginKeyId"Replace the variable with your signing key identifier.

SKA Keys and Multi-Authorization

If you have configured your key with multi-authorization policies based on Smart Key Attributes, the signing operation will only be executed if the approvals are provided in a timely manner and in the quantity specified by the "usage" policy.


Create a Trust Policy

Users who consume a signed artifact from a registry use the trust policy to specify trusted identities which will sign the artifacts, and level of signature verification to use. The trust policy must be defined manually in a JSON document.

Create or edit the notation trust policy trustpolicy.json file in the {NOTATION_CONFIG} directory. Create the file by executing the command:

vi ${HOME}/.config/notation/trustpolicy.json 

Add a trust policy with the following structure: Go to section "policy" in the input_example.json file and prepare the parameters, according to chapter (use any editor you are familiar with):

{
"version": "1.0",
"trustPolicies": [
{
"name": "trust-policy-example",
"registryScopes": [ "*" ],
"signatureVerification": {
"level" : "strict"
},
"trustStores": [ "ca:valid-example" ],
"trustedIdentities": [
"*"
]
}
]
}

Replace the variables according to your parameters:

Policy parametersCommand and parameter description
"version": "1.0"The parameter is the version of the trust policy. The supported value is 1.0.
"trustPolicies"The parameter represents a collection of trust policies.
"name": "trust-policy-name"Replace the variable with the name of your trust policy.
"registryScopes": [ "*" ]The parameter determines the registry artifacts to which the trust policy applies. The scope field supports filtering based on fully qualified repository URI
${registry-name}/${namespace}/${repository-name}.
"signatureVerification": {...}The parameter dictates how signature verification is performed. An object that specifies a predefined verification level. Parameter values:
strict, permissive, audit.
Detailed explanation of each level can be found here. Keep the level at strict for this guide.
"trustStores": [ "ca:trustStoreName" ]The parameter specifies a set of one or more named trust stores, each of which contain the trusted roots against which signatures are verified. Each named trust store uses the format {trust-store-type}:{named-store}.Replace the variable with your named store. Currently supported values for trust store types are:
CA, Signing Authority, TSA.
"trustIdentities": "[ "*" ]"Identities that are trusted to sign the artifact, supported values:
- E.g. x509.subject: C=US, ST=WA, L=Seattle, O=acme-rockets.io, OU=Finance, CN=SecureBuilder

Example trustpolicy.json file:

{ 
"version": "1.0",

"trustPolicies": [

{

"name": "securosys-trust-policy",

"registryScopes": [ "*" ],

"signatureVerification": {

"level": "strict"

},

"trustStores": [ "ca:securosysTrustStore" ],

"trustedIdentities": [ "*" ]
}
]
}

For more information on the trust policies for Notation check the following documents:


Verify Image

Use notation verify to validate the image:

notation verify $IMAGE

Example of successful image signature verification response:

Successfully verified signature for localhost:5000/net-monitor@sha256:9bba24af535af5ce658c5cd78c865d574cb65443c643d8538b73ea7d3e9bb64e