Skip to main content

Signing a Container Image with Notation and Azure Key Vault

Container Secure Supply Chain

Containers Secure Supply Chain (CSSC) framework is a seamless, agile ecosystem of tools and processes built to integrate and execute security controls throughout the lifecycle of containers. The container secure supply chain strategy is built considering all the security needs of the container applications. To find out more about the CSSC framework, visit the Azure Container Secure Supply Chain page.

As a quick overview, a container supply chain is built in stages to ensure that the container is secure at every stage of the lifecycle. Microsoft identifies these stages in the container supply chain:

secure supply chain

Container images are signed as part of the Acquire stage of the platform. Once a container image acquired from an external source or third-party vendor is verified for functionality and security, it is signed before being added to a catalog of approved container images. In this exercise, we will sign a container image using Notation, an open source supply chain security tool developed by the Notary Project community.

Prerequisites

For this workshop, you will also need to install the following tools:

Install Notation

First, set a local variable for the version of Notation you want to install (in this lab we will use version 1.2.0). Also set environment variables for the operating system and architecture you are using.

export NOTATION_VERSION=1.2.0
export OS=$(uname | tr '[:upper:]' '[:lower:]')
export ARCH=$(uname -m)

Use the following commands to download and install Notation.

curl -LO https://github.com/notaryproject/notation/releases/download/v$NOTATION_VERSION/notation_$NOTATION_VERSION\_${OS}_${ARCH}.tar.gz
curl -LO https://github.com/notaryproject/notation/releases/download/v$NOTATION_VERSION/notation_$NOTATION_VERSION\_checksums.txt
shasum --check notation_$NOTATION_VERSION\_checksums.txt

If the checksum verification is successful, you should see something like this (the result shown here is for Linux AMD64):

shasum: notation_1.2.0_darwin_amd64.tar.gz: No such file or directory
notation_1.2.0_darwin_amd64.tar.gz: FAILED open or read
shasum: notation_1.2.0_darwin_arm64.tar.gz: No such file or directory
notation_1.2.0_darwin_arm64.tar.gz: FAILED open or read
notation_1.2.0_linux_amd64.tar.gz: OK
shasum: notation_1.2.0_linux_arm64.tar.gz: No such file or directory
notation_1.2.0_linux_arm64.tar.gz: FAILED open or read
shasum: notation_1.2.0_linux_armv7.tar.gz: No such file or directory
notation_1.2.0_linux_armv7.tar.gz: FAILED open or read
shasum: notation_1.2.0_windows_amd64.zip: No such file or directory
notation_1.2.0_windows_amd64.zip: FAILED open or read
shasum: WARNING: 5 listed files could not be read

If the checksum verification is successful, extract the binary and move it to the desired bin directory in your $PATH.

tar xvf notation_$NOTATION_VERSION\_${OS}_${ARCH}.tar.gz
mv ./notation /usr/local
ln -s /usr/local/notation /usr/local/bin/notation

Verify the installation by running the following command:

notation version

You should see the version of Notation installed.

Notation - a tool to sign and verify artifacts.

Version: 1.2.0
Go version: go1.23.0
Git commit: 4700ad6f1bef13e411772d7ae4399f891fc3a6ae

Install the Notation Azure Key Vault Plugin

After installing Notation, install the Notation Azure Key Vault plugin. You can find the URL and the SHA256 checksum for the Notation Azure Key Vault plugin on the release page.

notation plugin install --url https://github.com/Azure/notation-azure-kv/releases/download/v${NOTATION_VERSION}/notation-azure-kv_${NOTATION_VERSION}_${OS}_${ARCH}.tar.gz --sha256sum <checksum_from_the_release_page>

Once the plugin is installed, confirm the azure-kv plugin is installed by running the following command:

notation plugin ls

Create Azure Container Registry and Azure Key Vault

note

If you have already created an Azure Container Registry and Azure Key Vault, you can skip this section. Make sure the environment variables AKV_NAME and ACR_NAME are set correctly.

Before beginning this exercise, let's set the environment variables for the Azure Container Registry and Azure Key Vault which was created at the beginning of the lab with the Bicep template.

Set the local variables containing information about the certificate to be used for signing the container image.

CERT_NAME="wabbit-networks-io"
CERT_SUBJECT="CN=wabbit-networks.io,O=Notation,L=Seattle,ST=WA,C=US"
CERT_PATH="./${CERT_NAME}.pem"

Now set the local variables containing information about the container registry and the image source code directory containing the Dockerfile to build.

REPO="net-monitor"
TAG="v1"
IMAGE="${ACR_SERVER}/${REPO}:${TAG}"
IMAGE_SOURCE="https://github.com/wabbit-networks/net-monitor.git#main"

Create a self-signed certificate in Azure Key Vault

Use the following command to create a certificate policy file named my_policy.json which will be used the create the self-signed certificate in Azure Key Vault. The subject value will be used as the trust identity during verification.

cat <<EOF > ./my_policy.json
{
"issuerParameters": {
"certificateTransparency": null,
"name": "Self"
},
"keyProperties": {
"exportable": false,
"keySize": 2048,
"keyType": "RSA",
"reuseKey": true
},
"secretProperties": {
"contentType": "application/x-pem-file"
},
"x509CertificateProperties": {
"ekus": [
"1.3.6.1.5.5.7.3.3"
],
"keyUsage": [
"digitalSignature"
],
"subject": "${CERT_SUBJECT}",
"validityInMonths": 12
}
}
EOF

Use the following command to create a certificate compatible with Notary Project certificate requirement in the Azure Key Vault instance.

az keyvault certificate create \
--vault-name ${AKV_NAME} \
--name ${CERT_NAME} \
--policy @my_policy.json

Signing a Container Image using Notation and Azure Key Vault Plugin

To sign a container image using Notation and Azure Key Vault, you first need to authenticate to your Azure Container Registry using the following command.

az acr login --name ${ACR_NAME}

Build and push a new image with ACR Tasks. Use the digest value to identify the image to sign.

DIGEST="$(az acr build -r ${ACR_NAME} -t ${ACR_SERVER}/${REPO}:${TAG} ${IMAGE_SOURCE} --no-logs --query "outputImages[0].digest" -o tsv)"
IMAGE="${ACR_SERVER}/${REPO}@${DIGEST}"

Get the ID of the signing key. The following command will get the Key ID of the latest version of the certificate.

KEY_ID="$(az keyvault certificate show -n $CERT_NAME --vault-name $AKV_NAME --query 'kid' -o tsv)"

Sign the image with the CBOR Object Signing and Encryption (COSE): Structures and Process format using the Notation Azure Key Vault plugin and the key retrieved in the previous step with the following command.

notation sign \
--signature-format cose \
--id ${KEY_ID} \
--plugin azure-kv \
--plugin-config self_signed=true ${IMAGE}

Verify the image using Notation

To verify the signed container image, add the root certificate that signs the leaf certificate to the trust store. The following command will download the root certificate and add it to the trust store. In the case of a self-signed certificate, the root certificate is the self-signed certificate

Run the following command to download the root certificate.

az keyvault certificate download \
--name ${CERT_NAME} \
--vault-name ${AKV_NAME} \
--file ${CERT_PATH}

Add the root certificate to the trust store using the following command.

STORE_TYPE="ca"
STORE_NAME="wabbit-networks.io"
notation cert add --type ${STORE_TYPE} --store ${STORE_NAME} ${CERT_PATH}

Verify the image using the following command.

notation cert ls

Configure the trust policy before verification. The trust policy is a JSON file that specifies the trust policy for the image. The trust policy is used to verify the signature of the image. For more information on trust policies and trust stores, see Trust store and trust policy specification

Run the following command to create a trust policy file named trust_policy.json.

cat <<EOF > ./trust_policy.json
{
"version": "1.0",
"trustPolicies": [
{
"name": "wabbit-networks-images",
"registryScopes": [ "${ACR_SERVER}/${REPO}" ],
"signatureVerification": {
"level" : "strict"
},
"trustStores": [ "${STORE_TYPE}:${STORE_NAME}" ],
"trustedIdentities": [
"x509.subject: ${CERT_SUBJECT}"
]
}
]
}
EOF

Import and verify the trust policy from the trust_policy.json file using the following Notation CLI commands.

notation policy import ./trust_policy.json
notation policy show

Verify the image using the following command:

notation verify $IMAGE

Upon successful verification of the image using the trust policy, the sha256 digest of the verified image is returned in a successful output message.