Using the Image Diff API
The Chainguard Images Diff API generates a JSON-formatted list of package additions, removals, and detected CVEs in Chainguard Images. It is intended to provide an authenticated, machine readable, formatted list of changes between two Chainguard Image builds. Combined with the Tag History API, you can use the Diff API to compare arbitrary builds of an Image over time and programmatically detect changes that might require gating a release or modifying your application code.
This guide will cover:
- How to get an authentication token to query the Diff API
- Identifying Image digests in a repository for comparison
- Making authenticated API calls using
curl
and programmatically using Go - Parsing the results of an API call
Prerequisites
To get started there are a few prerequisites that you will need to install.
- First is
chainctl
to authenticate to the Chainguard Registry. Our How to Install chainctl guide lists different ways to install it. Ensure that you have the chainctl credential helper configured as well. - The next tool is
crane
, which you will use to interact with the Chainguard Registry. Follow the crane installation guide that matches your operating system. - You will also need
curl
or a programming language of your choice to make authenticated API calls. There is an example using Go at the end of this guide. - Finally, the
jq
is useful to parse and filter the resulting JSON output from the API on the command line.
With these tools in place, you can begin gathering the information that you need to authenticate to and query the Diff API.
Authenticating and Getting a Token
To get started, ensure you are authenticated to the Registry using the chainctl auth login
command. Then locate your authentication token and create a shell variable for it.
If you are using Linux, run the following:
TOKEN=$(cat ~/.cache/chainguard/https\:--console-api.enforce.dev/oidc-token)
If you are using macOS, run the following:
TOKEN=$(cat ~/Library/Caches/chainguard/https:--console-api.enforce.dev/oidc-token)
Note that if at any point in this tutorial you receive an error like the following, your token has expired. Log in and set your $TOKEN
shell variable again:
{"code":16, "message":"failed to verify token", "details":[]}
For the purposes of this guide, we will use the public-images
Chainguard IAM organization. Set an environment variable corresponding to the organization’s UIDP, which will be used later on:
ORG_UIDP=$(chainctl iam organization describe public-images -ojson |jq -r '.id')
If you are using a private registry, be sure to substitute your IAM organization name in place of public-images
to use your specific private organization’s UIDP identifier in your subsequent commands.
Getting Image Digests to Compare
Now you will need to find Image digests to compare. You can compare any two arbitrary digests using the API. In this example we will use the public cgr.dev/chainguard/nginx
repository and find digests for the latest
and latest-dev
tags. You can also compare Image digests for the same tag by combining data from the Tag History API.
The first digest in this example will be referred to as the FROM_DIGEST
, and the second comparison Image as TO_DIGEST
.
Begin by creating a shell variable to hold the repository URL:
REPO_URL="cgr.dev/chainguard/nginx"
If you are using a private registry, be sure to replace cgr.dev/chainguard/nginx
with your relevant private Chainguard Registry and repository name as required.
Now run the following crane
command to get a list of tags contained in the public cgr.dev/chainguard/nginx
repository:
crane ls "${REPO_URL}" --omit-digest-tags
You will receive output like the following:
latest
latest-dev
next
Since we will compare the latest
and latest-dev
Images in this guide, run the following to record their digest
values as shell variables for reuse:
FROM_DIGEST=$(crane digest --platform "linux/amd64" "${REPO_URL}:latest")
TO_DIGEST=$(crane digest --platform "linux/amd64" "${REPO_URL}:latest-dev")
If you are using a different platform like linux/arm64
, substitute it into the crane
commands.
Now you can make a request to the Diff API using curl
or programmatically with a language of your choice.
Making a Request to the Diff API Using curl
To make API requests to the Diff API using curl
, set a shell variable to the name of the Image repository:
REPO_NAME=$(echo $REPO_URL | cut -d'/' -f3)
Now you can run the following command to record the repository UIDP identifier as a variable:
REPO_UIDP=$(curl -s -X GET -H "Authorization: Bearer $TOKEN" \
"https://console-api.enforce.dev/registry/v1/repos?name=${REPO_NAME}" | \
jq -r ".items[] | select(.id | startswith(\"${ORG_UIDP}\")) | .id")
With all of these variables set, you can call the Diff API with the following command:
curl -s -X GET -H \
"Authorization: Bearer $TOKEN" \
"https://console-api.enforce.dev/registry/v1/repos/${REPO_UIDP}/diff?from_digest=${FROM_DIGEST}&to_digest=${TO_DIGEST}" | \
jq -r
You will receive output like the following:
. . .
{
"packages": {
"added": [
. . .
{
"name": "bash",
"version": "5.2.21-r0",
"reference": "pkg:apk/wolfi/bash@5.2.21-r0?arch=x86_64"
},
{
"name": "busybox",
"version": "1.36.1-r2",
"reference": "pkg:apk/wolfi/busybox@1.36.1-r2?arch=x86_64"
},
. . .
"removed": [
. . .
]
},
"vulnerabilities": {
. . .
}
}
In this example output, the list of added
packages include things like bash
and other helpful utilities that are packaged in the nginx development Image. If there are any vulnerabilities detected in an Image they will be listed in that section of the output.
Making a Request Programmatically to the Diff API Using Go
If you would like to query the Diff API programmatically, there is an example Go application hosted in our platform-examples GitHub repository. The example uses the Chainguard SDK and is a good way to learn how to interact with the API through a declarative approach.
Clone the repository, or copy the code manually. Again we’re using the cgr.dev/chainguard/nginx
Image as an example:
git clone https://github.com/chainguard-dev/platform-examples
cd platform-examples/image-diff
Following that, set some shell variables:
REPO_NAME=nginx
REPO_URL="cgr.dev/chainguard/${REPO_NAME}"
FROM_DIGEST=$(crane digest --platform "linux/amd64" "${REPO_URL}:latest")
TO_DIGEST=$(crane digest --platform "linux/amd64" "${REPO_URL}:latest-dev")
Now run the go get
and go run
commands to build and execute the example program:
go get
go run . $REPO_NAME $FROM_DIGEST $TO_DIGEST |jq -r
If you receive an error about a missing oidc-token
, login with chainctl auth login
and try running the program again.
You will receive output like the following:
. . .
{
"packages": {
"added": [
. . .
{
"name": "bash",
"version": "5.2.21-r0",
"reference": "pkg:apk/wolfi/bash@5.2.21-r0?arch=x86_64"
},
{
"name": "busybox",
"version": "1.36.1-r2",
"reference": "pkg:apk/wolfi/busybox@1.36.1-r2?arch=x86_64"
},
. . .
"removed": [
. . .
]
},
"vulnerabilities": {
. . .
}
}
Again, replace chainguard/nginx
with your relevant private registry and Image name as required. As with the curl
example, you can also compare Image digests for the same tag by combining data from the Tag History API.
Grype Information
The output from either the curl
or programmatic requests to the Diff API will include a vulnerabilities
section that shows the versions of Grype that were used to scan for vulnerabilities in each image.
The metadata
array in the vulnerability result section will resemble the following, where each digest corresponds to the FROM_DIGEST
and TO_DIGEST
respectively:
[
{
"digest": "sha256:f91e3713d0349fe145262ae00b2de79cf315e49e73b7bfc28a10ba1545578230",
"scanner": {
"name": "grype",
"version": "v0.74.1"
},
"vulnerabilityDbLastBuildTime": "2024-02-27T01:23:18Z",
"vulnerabilityDbSchemaVersion": "5"
},
{
"digest": "sha256:077db361ef9ab680350915c90799fd19bda550dfbb17756636dfa4bba9b24c4a",
"scanner": {
"name": "grype",
"version": "v0.74.1"
},
"vulnerabilityDbLastBuildTime": "2024-02-27T01:23:18Z",
"vulnerabilityDbSchemaVersion": "5"
}
]
You can use this information to independently verify scan results using a local copy of Grype.
Last updated: 2024-03-21 15:22