Docker Image digest using v2 registry API

How to Get the Docker Image Digest SHA

The Docker image digest SHA is a critical piece of evidence that makes a container and the content unique.  You get the Docker image digest SHA from an image stored in a docker registry.  Easy to do if the image has been pulled, just run docker image ls <image> –digests.  Below is code and directions for getting the job done.

Pull Docker Image Digest Code

Example: 


docker image ls bitnami/wordpress --digests
REPOSITORY          TAG                   DIGEST                                                                    IMAGE ID            CREATED             SIZE
bitnami/wordpress   5.4.1-debian-10-r35   sha256:11db66166c3d16c8251134e538b794ec08dfbe5f11bcc8066c6fe50e3282d6ed   7e0e802dc089        13 days ago         593MB

But doing it from the docker registry v2 API is a bit different.  Most solutions have you hit the https://registry-1.docker.io/v2/${org}/${img}/manifests/$tag and look for the config.digest JSON field.  The config.digest is the SHA for the manifest not the image itself.  The docker image digest SHA is there but we just looking in the wrong place. We need to look at the http headers for the when we hit that same API endpoint. Docker-Content-Digest:

Interacting with Helm Charts

Here is a full example that includes interacting with Helm Charts. It finds the docker image digest for each image the chart uses. The image digest is found by using the two curl commands in the script (auth and manifest).


#!/bin/bash

helmrepouser=””
helmrepopass=””
helmrepo=”bitnami”
helmrepourl=”https://charts.bitnami.com/bitnami”

dockeruser=”DOCKERHUB USERID”
dockerpass=’DOCKERHUB PASSWORD’

chartorg=”bitnami”
chartname=”wordpress”
chartversion=”9.3.8″

helmexe=”helm”

if [ “$dockeruser” == “” ]; then
dockeruser=$helmrepouser
fi

if [ “$dockerpass” == “” ]; then
dockerpass=$helmrepopass
fi

if [ “$helmrepouser” != “” ]; then
helmlogin=”–username $helmrepouser”
fi

if [ “$helmrepouser” != “” ]; then
helmlogin=”$helmlogin –password $helmrepopass”
fi

if [ “$chartorg” == “” ]; then
chartorg=`echo $chartname | cut -d / -f 1`
chartname=`echo $chartname | cut -d / -f 2`
fi

# Add Helm Repo
$helmexe repo add $helmlogin $helmrepo $helmrepourl 2>&1 1>/dev/null

# Get latest Helm Charts
$helmexe repo update 2>&1 1>/dev/null

# Fetch the Helm Chart as .tgz
$helmexe fetch $chartorg/$chartname –version $chartversion 2>&1 1>/dev/null

# Calc digest of the Helm Chart
digest=`sha256sum $chartname*.tgz | cut -f1 -d ” “`

# Print out the starting JSON
echo “{”
echo “\”digest\”: \”$digest\”,”
echo “\”images\”: [”
tar -xf *.tgz
IFS=$’\n’

# Find the Docker image digest: fields in the expanded Helm Chart and loop over each image line
let cnt=0
for line in $($helmexe template $chartname | grep “image:” | cut -f 2- -d “:” | tr -d ‘”‘ | tr -d ” “)
do
if [ “$cnt” -gt “0” ]; then
echo “,”
fi

let cnt=$cnt+1

# find the number of slashes since registry name and org are not required for images in docker.io
slashes=$(echo $line | awk -F”/” ‘{print NF-1}’)

if [ “$slashes” == “2” ]; then
reg=$(echo $line | cut -d / -f 1)
org=$(echo $line | cut -d / -f 2)
img=$(echo $line | cut -d / -f 3- | cut -d “:” -f 1)
tag=$(echo $line | cut -d / -f 3- | cut -d “:” -f 2)
elif [ “$slashes” == “1” ]; then
reg=”docker.io”
org=$(echo $line | cut -d / -f 1)
img=$(echo $line | cut -d / -f 2- | cut -d “:” -f 1)
tag=$(echo $line | cut -d / -f 2- | cut -d “:” -f 2)
else
reg=”docker.io”
org=”library”
img=$(echo $line | cut -d “:” -f 1)
tag=$(echo $line | cut -d “:” -f 2)
fi

# docker.io v2 API endpoint lives in a different location than the registry url
if [ “$reg” == “docker.io” ]; then
apidomain=”registry-1.docker.io”
else
apidomain=$reg
fi

# Find the docker authentication relm
authdomsvc=`curl –head “https://${apidomain}/v2/” 2>&1 | grep realm | cut -f2- -d “=” | tr “,” “?” | tr -d ‘”‘ | tr -d “\r”`

# Set the scope to look for our image
authscope=”repository:${org}/${img}:pull”

# Login to docker v2 API endpoint and save token
TOKEN=$(curl -s -X GET -u ${dockeruser}:${dockerpass} “${authdomsvc}&scope=${authscope}&offline_token=1&client_id=shell” | jq -r ‘.token’)

# Grab the docker image digest from the Docker-Content-Digest: that is in the http header
# Use “Accept: application/vnd.docker.distribution.manifest.v2+json” in order to get the version 2 schema
digest=$(curl –head -s -H “Authorization: Bearer ${TOKEN}” -H “Accept: application/vnd.docker.distribution.manifest.v2+json” https://${apidomain}/v2/${org}/${img}/manifests/$tag | grep “Docker-Content-Digest:” | cut -f 2 -d ” ” | tr -d ‘”‘ | tr -d “\r”)

# Print out the details for each image
cat <<- EOF
{
“registry”: “$reg”,
“organization”: “$org”,
“image_name”: “$img”,
“image_tag”: “$tag”,
“image_digest”: “$digest”
}
EOF
done

# Print closing JSON
echo “]”
echo “}”

# Cleanup
rm $chartname*.tgz

 

Results of from the script:


{
"digest": "8e1774518cba1f58bde35342e0f243a4e2d416bad78b4d5e001c5eedb0e86b58",
"images": [
{
"registry": "docker.io",
"organization": "bitnami",
"image_name": "wordpress",
"image_tag": "5.4.1-debian-10-r35",
"image_digest": "sha256:11db66166c3d16c8251134e538b794ec08dfbe5f11bcc8066c6fe50e3282d6ed"
},
{
"registry": "docker.io",
"organization": "bitnami",
"image_name": "mariadb",
"image_tag": "10.3.23-debian-10-r21",
"image_digest": "sha256:93746d7b2a1ba8edbb892a3659d2118e792de9211de25957504cbd7b9d8d8fc0"
},
{
"registry": "docker.io",
"organization": "bitnami",
"image_name": "wordpress",
"image_tag": "5.4.1-debian-10-r35",
"image_digest": "sha256:11db66166c3d16c8251134e538b794ec08dfbe5f11bcc8066c6fe50e3282d6ed"
},
{
"registry": "docker.io",
"organization": "dduportal",
"image_name": "bats",
"image_tag": "0.4.0",
"image_digest": "sha256:b2d533b27109f7c9ea1e270e23f212c47906346f9cffaa4da6da48ed9d8031da"
},
{
"registry": "docker.io",
"organization": "bitnami",
"image_name": "mariadb",
"image_tag": "10.3.23-debian-10-r21",
"image_digest": "sha256:93746d7b2a1ba8edbb892a3659d2118e792de9211de25957504cbd7b9d8d8fc0"
}
]
}`