| copyright |
|
||
|---|---|---|---|
| lastupdated | 2026-06-09 | ||
| keywords | DevSecOps, IBM Cloud, public key, private key, GPG | ||
| subcollection | devsecops |
{{site.data.keyword.attribute-definition-list}}
{: #devsecops-image-verify}
Maintain the integrity of images that are built in the Continuous Integration (CI) pipeline before deployment by verifying image signatures. {: shortdesc}
{: #devsecops-image-verify-before}
Before you start working with image verification, be sure that you have the following prerequisites.
- You must have the GPG public key. For help generating your GPG key, see the documentation.
- You must add the environment variable
code-signing-certificatewith the base 64-encoded GPG public key. - You must configure registry credentials for accessing container images during verification (see Configuring registry credentials).
{: #devsecops-image-verify-verify}
A new prod-verify-artifact stage verifies the signature of an image in the Continuous Delivery (CD) pipeline. This stage performs the following steps:
-
Decodes the user-provided encoded GPG public key into a temporary file.
-
Creates a Docker container policy (
/etc/containers/policy.json) with the public key.{ "default": [ { "type": "reject" } ], "transports": { "docker-daemon": { "": [ { "type": "reject" } ] }, "docker": { "": [ { "type": "signedBy", "keyType": "GPGKeys", "keyPath": "/tmp/GPGPublicKey" } ] } } }{: codeblock}
-
Retrieves the list of artifacts for each artifact by using Skopeo{: external} to pull the image with the container policy.
skopeo copy docker://"${image}" dir:"${tmp_sign_dir}" --src-creds iamapikey:"${ibmcloud_api_key}"
{: codeblock}
If the signature is valid and verified by the public key that is provided by the user, the image pull is successful.
{: #devsecops-verify-registry-credentials}
When verifying container image signatures, the pipeline needs credentials to authenticate with the container registry and access the signed images. The DevSecOps pipeline supports dynamic credential resolution at runtime, allowing you to configure credentials in multiple ways with automatic fallback mechanisms.
{: #devsecops-verify-credential-hierarchy}
The pipeline dynamically resolves both username and API key credentials at runtime using the following hierarchy:
- Namespace-specific API key:
signing-token-apikey-{registry}-{namespace}(secret) - Registry-specific API key:
signing-token-apikey-{registry}(secret) - Docker config JSON:
signing-dockerconfigjson(secret) - ICR-specific fallbacks:
ciso-ibmcloud-api-key(secret)ibmcloud-api-key(secret)
- Namespace-specific username:
signing-token-username-{registry}-{namespace}(environment variable) - Registry-specific username:
signing-token-username-{registry}(environment variable) - Default:
iamapikey(if no username is configured)
Where:
{registry}is the registry hostname (e.g.,us.icr.io,de.icr.io){namespace}is the full namespace path with slashes and dots replaced by underscores (e.g.,my_namespace_path)
{: #devsecops-verify-namespace-credentials}
For fine-grained access control, you can configure credentials specific to a registry namespace:
API Key (Secret): signing-token-apikey-{registry}-{namespace}
Username (Environment Variable): signing-token-username-{registry}-{namespace}
Example: For image us.icr.io/my-namespace/my-app:latest
- Registry:
us.icr.io - Namespace:
my-namespace - API Key secret:
signing-token-apikey-us.icr.io-my_namespace - Username environment variable:
signing-token-username-us.icr.io-my_namespace - If username is not provided, defaults to:
iamapikey
{: #devsecops-verify-registry-credentials-config}
For broader access across all namespaces in a registry:
API Key (Secret): signing-token-apikey-{registry}
Username (Environment Variable): signing-token-username-{registry}
Example: For any image in us.icr.io
- API Key secret:
signing-token-apikey-us.icr.io - Username environment variable:
signing-token-username-us.icr.io - If username is not provided, defaults to:
iamapikey
{: #devsecops-verify-dockerconfig}
You can provide a base64-encoded Docker config JSON that contains credentials for multiple registries:
Secret name: signing-dockerconfigjson
Format: Base64-encoded JSON matching Docker's config.json format:
{
"auths": {
"us.icr.io": {
"username": "iamapikey",
"password": "your-api-key"
},
"us.icr.io/my-namespace": {
"username": "iamapikey",
"password": "namespace-specific-key"
}
}
}The pipeline matches the most specific path first, allowing namespace-level overrides within the Docker config.
{: #devsecops-verify-credential-example}
For an image us.icr.io/production/my-app:v1.0.0:
Option 1: Namespace-specific (recommended for production)
- API Key secret:
signing-token-apikey-us.icr.io-production=your-namespace-api-key - Username environment variable (optional):
signing-token-username-us.icr.io-production=iamapikey - If username is not specified, it defaults to
iamapikey
Option 2: Registry-wide
- API Key secret:
signing-token-apikey-us.icr.io=your-registry-api-key - Username environment variable (optional):
signing-token-username-us.icr.io=iamapikey - If username is not specified, it defaults to
iamapikey
Option 3: Docker config JSON
- Secret:
signing-dockerconfigjson=base64-encoded-docker-config - Username is extracted from the Docker config JSON
Option 4: IBM Cloud default (automatic for ICR)
- API Key secret:
ibmcloud-api-key=your-ibmcloud-api-key - Username defaults to
iamapikey
{: #devsecops-non-image-artifact-verify}
In DevSecOps pipelines, the signature of signed artifacts is subject to verification. This section outlines the prerequisites that users must meet before proceeding with artifact verification.
{: #devsecops-non-image-artifacts-verify-before}
Before you can work with image verification, be sure that you have the following prerequisites.
-
Modify your pipeline configuration to enable the verifcation process. Add the following code snippet to your
.pipeline-config.yamlfile.verify-artifact: image: icr.io/continuous-delivery/pipeline/image-signing:1.0.0@sha256:e9d8e354668ba3d40be2aaee08298d2aa7f0e1c8a1829cca4094ec93830e3e6a image_pull_policy: IfNotPresent abort_on_failure: false dind: true script: | #!/usr/bin/env bash source /opt/commons/verify-artifact/verify_non_image_artifact.sh
{: codeblock}
-
Download the artifact and store it in the pipeline runtime.
Before invoking the
verify-artifactstage, download and store the required artifacts in the Cocoa runtime. By default, all artifacts listed in tehlist_artifactscollection are verified. The following sample code demonstrates how to download an artifact from Cloud Object Storage and save it in the Cocoa runtime:#!/usr/bin/env bash export SECRET_PATH="/config/ibmcloud-api-key" . "${ONE_PIPELINE_PATH}"/iam/get_token SKIP_SIGN_ARTIFACTS_TYPE="$(get_env skip-sign-artifact-type "")" IFS=';' read -ra ARTIFACT_LIST <<<"$SKIP_SIGN_ARTIFACTS_TYPE" list_artifacts | while IFS= read -r artifact; do type="$(load_artifact "$artifact" "type")" if [[ ! "${ARTIFACT_LIST[@]}" =~ "$type" ]] && [[ "$type" != "image" ]]; then inventory_entry="$(load_artifact "$artifact" "inventory-entry")" inventory_entry=${inventory_entry///artifacts/$WORKSPACE} artifact_url=$(jq -r .'app_artifacts.artifact_url' ${inventory_entry}) echo "downloading the artifact ${artifact_url}" curl -H "Authorization: bearer ${IAM_ACCESS_TOKEN}" --output outputfile ${artifact_url} save_file "${artifact}" outputfile fi done
{: codeblock}
{: #devsecops-non-image-artifact-verifcation}
In the artifact verification process, the user-provided GPG public key is imported into the GPG keyring. All artifacts listed in the list_artifacts collection will be verified. The stage attempts to retrieve the signature from the inventory and performs the following verification:
gpg --verify "${signature}" "${artifactName}"{: codeblock}
If the signature is valid and verified using the provided public key, the stage will record the evidence and mark the stage as successful.