Skip to content

Instantly share code, notes, and snippets.

@johnfosborneiii
Last active April 30, 2024 19:52
Show Gist options
  • Save johnfosborneiii/a99b7839835893016f7b8ded984fc0be to your computer and use it in GitHub Desktop.
Save johnfosborneiii/a99b7839835893016f7b8ded984fc0be to your computer and use it in GitHub Desktop.

Container Images

Kubernetes

Verify single image

$ cosign verify registry.k8s.io/kube-apiserver-amd64:v1.25.2

Get and Verify All Kubernetes Images

$ version=v1.25.0
curl -Ls https://sbom.k8s.io/v1.25.0/release \
| grep 'PackageName: registry.k8s.io/' \
| awk '{print $2}' > images.txt \
&& input=images.txt \
&& while IFS= read -r image; \
     fullimageurl=$image
     fullimageurl+=version
     echo image
     do cosign verify --certificate-identity-regexp=.* --certificate-oidc-issuer-regexp=.* asia.gcr.io/k8s-artifacts-prod/kubernetes/kube-apiserver-amd64:v1.25.0 2> /dev/null | jq -r '.[0].optional | "Issuer: \(.Issuer)\nSubject: \(.Subject)"'
     done < "$input"

Crane

Print OCI Config

$ crane config cgr.dev/chainguard/node | jq .config

Print all the repos you can access on cgr.dev

$ crane catalog cgr.dev
$ crane manifest <image> | jq .

Save SBOMs with Crane

$ crane export --platform=linux/amd64 cgr.dev/chainguard/glibc-dynamic:latest - 2>/dev/null | tar --ignore-command-error -xvf - --wildcards 'var/lib/db/sbom/*.json' -C . 2>/dev/null

Save SBOMs with docker

$ sudo docker cp $(docker ps -a | grep cgr.dev/c3.ai/glibc-dynamic:13 | head -n1 | awk '{print $1;}'):/var/lib/db/sbom -

List SBOM Directory Contents with Crane

$ crane export cgr.dev/chainguard-private/dotnet-sdk:6 - | tar -tvf - | grep -i 'var/lib/db/sbom'

Extract a Package SBOM with Crane

$ crane export cgr.dev/chainguard-private/dotnet-sdk:6 - | tar -Oxf - var/lib/db/sbom/aspnet-6-runtime-6.0.126-r1.spdx.json > /tmp/aspnet-6-runtime-sbom.json

Print the Operating System Identifier with Crane

$ IMAGE="cgr.dev/chainguard-private/dotnet-sdk:7"; echo $(crane export $IMAGE - | tar -Oxf - etc/os-release | head -n 1)

Verify SBOMs and SBOM integrity using cosign

$ cosign verify-attestation \
  --type https://spdx.dev/Document \
  --certificate-oidc-issuer=https://token.actions.githubusercontent.com \
  --certificate-identity=https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main \
  cgr.dev/chainguard/glibc-dynamic:latest 2>/dev/null \
  | jq -r .payload | base64 -d | jq

Print all the APKs for a given image

$ IMAGE="cgr.dev/chainguard-private/jdk:openjdk-17"; crane manifest $IMAGE | jq -r '.manifests[] | select (.platform.architecture=="amd64") | .digest' | xargs -I {} cosign verify-attestation --type=spdx --certificate-identity-regexp=.* --certificate-oidc-issuer-regexp=.* $IMAGE@{} 2> /dev/null | jq -r .payload | base64 -d | jq '.predicate' | jq '.packages[] | select(.externalRefs[]?.referenceCategory == "PACKAGE_MANAGER") | .externalRefs[] | select(.referenceCategory == "PACKAGE_MANAGER") | .referenceLocator'

Print images in a helm chart

#!/bin/bash


if [ "$#" -ne 3 ]; then
    echo "Usage: $0 <helm-rep-url> <name> <repo-url> <path-chart>"
    echo "Example: ./print-helm-chart-images.sh argo https://argoproj.github.io/argo-helm argo-cd"
    exit 1
fi

name=$1
helmrepo=$2
chartpath=$3

renderedhelmfile=$(mktemp --suffix=.yaml)

helm repo add $name $helmrepo
helm template $name/$chartpath > "$renderedhelmfile"
mapfile -t deployment_array < <(cat "$renderedhelmfile" | yq '. | select(.kind == "Deployment") | .metadata.name' | sort -u | grep -E '^[a-zA-Z]')

for helmdeployment in "${deployment_array[@]}"; do
    echo "Deployment: $helmdeployment"
    echo -n "image: "
    cat "$renderedhelmfile" | yq eval '. | select(.kind == "Deployment" and .metadata.name == "'$helmdeployment'") | .spec.template.spec.containers[].image' | sort -u
    echo ""
done

Print the timestamp and installed APK list from a multi-arch digest

#!/bin/bash

IMAGE_NAME="chainguard/wolfi-base"  # Change as needed
DIGEST="sha256:909ed72dc072188cce611c98f556b3a4e9a62ad4078d766e78c180eb8b02fa23"  # Change as needed
ARCHITECTURE="amd64"  # Change as needed

tok=$(curl -H "Authorization: Bearer $(echo 'cgr.dev' | docker-credential-cgr get)" \
        -v "https://cgr.dev/token?scope=repository:${IMAGE_NAME}:pull" 2> /dev/null \
        | jq -r .token)
timestamp=$(curl -H "Authorization: Bearer $tok" \
    "https://cgr.dev/v2/${IMAGE_NAME}/_chainguard/history/latest" 2> /dev/null \
    | jq -r ".history[] | select(.digest == \"${DIGEST}\") | .updateTimestamp")
apks=$(crane manifest cgr.dev/${IMAGE_NAME} 2> /dev/null | jq -r ".manifests[] | select (.platform.architecture==\"${ARCHITECTURE}\") | .digest" \
    | xargs -I {} cosign verify-attestation --type=spdx --certificate-identity-regexp=.* --certificate-oidc-issuer-regexp=.* cgr.dev/${IMAGE_NAME}@{} 2> /dev/null \
    | jq -r .payload | base64 -d | jq '.predicate' 2> /dev/null \
    | jq -r '[.packages[] | select(.externalRefs[]?.referenceCategory == "PACKAGE_MANAGER") | .externalRefs[] | select(.referenceCategory == "PACKAGE_MANAGER") | .referenceLocator]')
echo "{\"updateTimestamp\": \"$timestamp\", \"apks\": $apks}" | jq

done

Identify CVEs from a JAR perspective for an array of images

#!/bin/bash

# Define an array of image names
images=(
  "spark:latest"
  "tomcat:latest"
  "paketobuildpacks/spring-boot:latest"
  "zookeeper:latest"
  "cassandra:latest"
  "flink:latest"
  "cockroachdb/cockroach:latest"
  "bitnami/kafka:latest"
  "keycloak/keycloak:latest"
  "jenkins/jenkins:latest"
  "elasticsearch:8.11.0"
  "apachepinot/pinot-presto:latest"
  "selenium/standalone-chrome:latest"
  "trinodb/trino:latest"
  "apache/druid:28.0.0"
  "apache/hive:4.0.0-beta-2-SNAPSHOT"
  "logstash:8.11.1"
)

# File to store full inventory of .jars
jarlistfile=$(mktemp --suffix=.csv)
echo "Container Image,Jar Name,Full Path" > "$jarlistfile"
# File to store Image CVE Results
tempcsvfile1=$(mktemp --suffix=.csv)
echo "Image,Total CVEs,Critical CVEs,High CVEs,Medium CVEs" > "$tempcsvfile1"
# File to store Image CVE Results by JAR
tempcsvfile2=$(mktemp --suffix=.csv)
echo "Image,Artifact,Jar CVEs,Jar CVEs with Fixes,Critical Jar CVEs,Critical Jar CVEs with Fixes,High Jar CVEs,High Jar CVEs with Fixes" > "$tempcsvfile2"

echo "Pulling the latest image for..."
for IMAGE in "${images[@]}"; do
    echo "$IMAGE"
    DOCKER_CLI_HINTS=false docker pull "$IMAGE" 2>&1 1>/dev/null
done
echo "---------------------------------------------"

crane export "$IMAGE" 2>/dev/null - | tar -tvf - 2>/dev/null | grep "\.jar$" | while read -r line; do
      jar_name_full_path=$(echo "$line" | awk '{print $NF}')
      jar_name=$(basename "$jar_name_full_path")
      echo "$IMAGE,$jar_name,$jar_name_full_path" >> "$jarlistfile"
    done

echo "Running grype scan for..."
for IMAGE in "${images[@]}"; do
    
    echo "$IMAGE"
    GRYPE_OUTPUT=$(grype $IMAGE -o json 2>/dev/null)

    echo "$GRYPE_OUTPUT" | jq -r --arg img "$IMAGE" '
      [$img, ([.matches[].vulnerability] | length), ([.matches[] | select(.vulnerability.severity == "Critical")] | length), ([.matches[] | select(.vulnerability.severity == "High")] | length), ([.matches[] | select(.vulnerability.severity == "Medium")] | length)] | 
      @csv' >> "$tempcsvfile1"
    
    echo "$GRYPE_OUTPUT" | jq -r --arg img "$IMAGE" '
    [ .matches[]
      | select(.artifact.type == "java-archive")
      | {artifact: .artifact.name, vulnerability: {id: .vulnerability.id, severity: (.vulnerability.severity) | ascii_upcase, fix_state: (.vulnerability.fix.state)}}
    ] | group_by(.artifact)
      | map({
          artifact: .[0].artifact,
          jar_cves: length,
          jar_cves_with_fixes: (map(select(.vulnerability.fix_state == "fixed")) | length),
          critical_jar_cves: (map(select(.vulnerability.severity | ascii_upcase == "CRITICAL")) | length),
          critical_jar_cves_with_fixes: (map(select(.vulnerability.fix_state == "fixed" and (.vulnerability.severity | ascii_upcase == "CRITICAL"))) | length),
          high_jar_cves: (map(select(.vulnerability.severity | ascii_upcase == "HIGH")) | length),
          high_jar_cves_with_fixes: (map(select(.vulnerability.fix_state == "fixed" and (.vulnerability.severity | ascii_upcase == "HIGH"))) | length)
        })
      | .[]
      | [$img, .artifact, (.jar_cves|tostring), (.jar_cves_with_fixes|tostring), (.critical_jar_cves|tostring), (.critical_jar_cves_with_fixes|tostring), (.high_jar_cves|tostring), (.high_jar_cves_with_fixes|tostring)] 
      | @csv' >> "$tempcsvfile2"
  
done

# Output the file paths
echo "Inventory List of Jars is saved to: $jarlistfile"
echo "Image Vulnerability Results (CSV) saved to: $tempcsvfile1"
echo "Image Vulnerability results by JAR (CSV) saved to: $tempcsvfile2"
echo "---------------------------------------------"

List all images in a Chainguard IAM Group

$ chainctl img repo list --group=private-images -o table | cut -d'|' -f2 | sort

Find what apk has a given command with curl

$ curl -s https://packages.wolfi.dev/os/x86_64/APKINDEX.tar.gz | tar -xzO APKINDEX | grep -B 10 "p:.*cmd:setcap" | grep "P:" | uniq

Find what apk provided a file

$ apk info -W

Find what apk provided a shared library

$ apk search so:libstdc*

Run a locally built Wolfi APK without building an image

$ docker run -it --rm -v /path/to/fooapk/:/ --entrypoint sh cgr.dev/chainguard/wolfi-base:latest
$ apk update
$ apk add --allow-untrusted /foo.apk
# Note: You can also copy your local signing key such as local-melange.rsa.pub to /etc/apk/keys/

Latest Chainguard Image Build Date (of its constituent packages:

$ crane config cgr.dev/chainguard/static | jq -r .created

chainctl

$ chainctl clusters print-config

Scan an array of images with grype

#!/bin/bash

# Define an array of image names
images=(
  "python"
  "redis"
  "bitnami/kubectl:1.29"
)

# Loop through each item and append ":latest" if no tag is present
for i in "${!images[@]}"; do
    if [[ "${images[i]}" != *:* ]]; then
        images[i]="${images[i]}:latest"
    fi
    echo "Pulling ${images[i]}"
    if docker pull ${images[i]} 2>&1 | grep -iq "error"; then
    echo "Error encountered while pulling $image. Exiting..."
    exit 1
  fi
done

echo "---------------------------------------------"

json='{"items":[]}'
totalCritical=0
totalHigh=0
totalMedium=0
totalLow=0
totalWontFix=0
totalCount=0

echo "Scanning images..."

for IMAGE in "${images[@]}"; do
  
  # echo "$IMAGE"
  # Capture the JSON output in a variable
  output=$(grype $IMAGE -o json 2>/dev/null | jq -c '{Total: [.matches[].vulnerability] | length, Critical: [.matches[] | select(.vulnerability.severity == "Critical")] | length, High: [.matches[] | select(.vulnerability.severity == "High")] | length, Medium: [.matches[] | select(.vulnerability.severity == "Medium")] | length, Low: [.matches[] | select(.vulnerability.severity == "Low")] | length, WontFix: [.matches[] | select(.vulnerability.fix.state == "wont-fix")] | length }')
  
  echo "$output"
  critical=$(jq '.Critical' <<< "$output")
  high=$(jq '.High' <<< "$output")
  medium=$(jq '.Medium' <<< "$output")
  low=$(jq '.Low' <<< "$output")
  wontfix=$(jq '.WontFix' <<< "$output")
  total=$(jq '.Total' <<< "$output")

  json=$(jq --arg image "$IMAGE" \
          --arg critical "$critical" \
          --arg high "$high" \
          --arg medium "$medium" \
          --arg low "$low" \
          --arg wontfix "$wontfix" \
          --arg total "$total" \
          '.items += [{
            image: $image,
            scan: {
              type: "grype",
              critical: ($critical | tonumber),
              high: ($high | tonumber),
              medium: ($medium | tonumber),
              low: ($low | tonumber),
              wontfix: ($wontfix | tonumber),
              total: ($total | tonumber)
            }
          }]' <<< "$json")


  totalCritical=$((totalCritical + critical))
  totalHigh=$((totalHigh + high))
  totalMedium=$((totalMedium + medium))
  totalLow=$((totalLow + low))
  totalWontFix=$((totalWontFix + wontfix))
  totalCount=$((totalCount + total))

done
echo "---------------------------------------------"

# Calculate averages
averageCritical=$((totalCritical / ${#images[@]}))
averageHigh=$((totalHigh / ${#images[@]}))
averageMedium=$((totalMedium / ${#images[@]}))
averageLow=$((totalLow / ${#images[@]}))
averageWontFix=$((totalWontFix / ${#images[@]}))

# Display totals and averages
echo "Total Vulnerabilities: $totalCount"
echo "Total Critcal CVEs: $totalCritical"
echo "Total High CVEs: $totalHigh"
echo "Total Medium CVEs: $totalMedium"
echo "Total Low CVEs: $totalLow"
echo -n "Average Vulnerabilities: "; echo "scale=2; $totalCount / ${#images[@]}" | bc
echo -n "Average Critcal CVEs: "; echo "scale=2; $totalCritical / ${#images[@]}" | bc
echo -n "Average High CVEs: "; echo "scale=2; $totalHigh / ${#images[@]}" | bc
echo -n "Average Medium CVEs: "; echo "scale=2; $totalMedium / ${#images[@]}" | bc
echo -n "Average Low CVEs: "; echo "scale=2; $totalLow / ${#images[@]}" | bc
echo "JSON Output:"
echo "$json"
echo "CSV Output:"
echo "$json" | jq -r '.items[] | [.scan.total, .scan.critical, .scan.high, .scan.medium, .scan.low, .scan.wontfix] | @csv'

Scan an array of images for a specific CVE

#!/bin/bash

vulnid="CVE-2023-6246"
images=(
  "cgr.dev/chainguard/cosign"
  "cgr.dev/chainguard/curl"
)

# Loop through each item and append ":latest" if no tag is present
for i in "${!images[@]}"; do
    if [[ "${images[i]}" != *:* ]]; then
        images[i]="${images[i]}:latest"
    fi
    echo "Pulling ${images[i]}"
    docker pull "${images[i]}" -o json 2>/dev/null      
done

echo "---------------------------------------------"

echo "Scanning images for CVE"

for IMAGE in "${images[@]}"; do
  echo "$IMAGE"
  output=$(grype $IMAGE -o json 2>/dev/null | jq --arg vulnid "$vulnid" '.matches[] | .matchDetails[] | select(.found.vulnerabilityID == $vulnid) | "Vulnerability \($vulnid) found!" // null')
  if [ -z "$output" ]; then
    echo "Not Affected"
  else
    echo "Vulnerability $vulnid FOUND"
  fi
done
echo "---------------------------------------------"

Verify attestation signatures and save attestations for an array of multi-arch images

#!/bin/bash

images=("cgr.dev/chainguard/glibc-dynamic:latest")
arch="amd64"
types=("https://slsa.dev/provenance/v1" "https://apko.dev/image-configuration" "https://spdx.dev/Document")

# Outer loop for images
for image in "${images[@]}"; do

    digest=$(crane manifest "$image" 2> /dev/null | jq -r ".manifests[] | select (.platform.architecture==\"${arch}\") | .digest")
    imgdigestref=$(echo "$image" | awk -F':' '{print $(NF-1)}')@$digest
    img_short_name=$(echo "$image" | awk -F'[:/]' '{print $(NF-1)"-"$NF}')

    echo "Checking image $imgdigestref"
    
    for type in "${types[@]}"; do
        
        short_type=""
        case $type in
        "https://slsa.dev/provenance/v1")
            short_type="slsa-prov-v1"
            ;;
        "https://apko.dev/image-configuration")
            short_type="apko-img-cfg"
            ;;
        "https://spdx.dev/Document")
            short_type="spdx-sbom-doc"
            ;;
        esac
        
        file="$img_short_name-$short_type.json"
        echo " *Verifying signature of attestation $type and saving to file $file"
        
        cosign verify-attestation \
        --type "$type" \
        --certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
        --certificate-identity="https://github.com/chainguard-images/images/.github/workflows/release.yaml@refs/heads/main" \
        "$imgdigestref" 2>/dev/null \
        | jq -r .payload | base64 -d | jq  > "$file"
    done
done

Reverse engineer the image tag from a digest for a multi-arch image

#!/bin/bash

if [ "$#" -ne 3 ]; then
    echo "Usage: $0 <full-image-name> <arch> <sha256-hash>"
    echo "Example: ./get-tag.sh cgr.dev/chainguard/glibc-dynamic amd64 sha256:00821ae6d724e1ac736aa3aabf3107a0c24583d53caaf5e99364257e2766b336"
    exit 1
fi

image=$1
arch=$2
sha256digest=$3

tags=$(crane ls "$image" --omit-digest-tags)
tagcount=0
matchcount=0

echo ""
echo "Found the following tags..."

for tag in $tags; do
    echo -n "$tag "
    let tagcount++
done

echo ""
echo ""
echo "Total number of tags found: $tagcount"

for tag in $tags; do
    
    digest=$(crane manifest "$image" 2> /dev/null | jq -r ".manifests[] | select (.platform.architecture==\"${arch}\") | .digest")
    imgdigestref=$(echo "$image" | awk -F':' '{print $(NF-1)}')@$digest

    # Check if the digest matches the provided SHA256 hash
    if [[ $imgdigestref =~ $sha256digest ]]; then
        echo "Match found for tag $tag"
        let matchcount++
    fi
done
echo "Total number of matches found: $matchcount"

Print the APK diff

$ chainctl images diff --platform=linux/amd64 \
   cgr.dev/chainguard-private/argocd:latest \
   cgr.dev/chainguard/argocd:latest 2>/dev/null | \
   (   output=$(cat); \
       echo -e "Packages added:\n$(echo "$output" | jq -r '.packages.added[] | select(.reference | startswith("pkg:apk")) | .name')"; \
       echo -e "Packages removed:\n$(echo "$output" | jq -r '.packages.removed[] | select(.reference | startswith("pkg:apk")) | .name')"
   )

Print history for specific Chainguard Image Stream

#!/bin/bash

show_help() {
    echo ""
    echo "Usage: $0 image-stream-base-uri image-stream-version"
    echo "       $0 cgr.dev/chainguard-private/python 3.9"
    echo ""
    echo "Options:"
    echo "  --platform          Specifies the platform in the form os/arch[/variant][:osversion] (default: linux/amd64)"
    echo "  --image-ref-type    tags | digests (default: tags)"
    echo "  -h, --help          Display this help message and exit"
    echo ""
}

if [ $# -ne 2 ]; then
    show_help
    exit 1
fi

repository="$1"
version="$2"

# Removing cgr.dev/
repository=${1#cgr.dev/}

# Fetch history using the provided parameters
history=$(curl -s -H "$(crane auth token -H cgr.dev/$repository)" "https://cgr.dev/v2/$repository/_chainguard/history/$version" | jq .)
IFS=$'\n' read -r -d '' -a historyapidigestarray <<< $(echo $history | jq -r '.history[].digest')

echo ""
echo "History API digests found: ${#historyapidigestarray[@]}"
echo ""
echo "oldest:"
echo -n "  - digest: "
echo "$history" | jq -r '.history[0].digest'
echo -n "  - timestamp: "
echo "$history" | jq -r '.history[0].updateTimestamp'
echo "newest:"
echo -n "  - digest: "
echo "$history" | jq -r '.history[-1].digest'
echo -n "  - timestamp: "
echo "$history" | jq -r '.history[-1].updateTimestamp'
echo ""

# Extract the substring after the last '/' in the first parameter
repository_name=${1##*/}
temp_file=$(mktemp /tmp/${repository_name}-${version//./}-history-XXXXXX.json)
echo $history | jq > $temp_file
echo "Full history written to $temp_file"

Check CVEs and release for an ImageStream history

#!/bin/bash

show_help() {
    echo "Warning: If you are on Mac run: "
    echo "           brew install bash coreutils"
    echo "Then modify all instances of date to gdate and run with "bash $0 image-stream-base-uri image-stream-version"
    echo ""
    echo "Usage: $0 image-stream-base-uri image-stream-version"
    echo "       $0 cgr.dev/chainguard-private/python 3.9"
    echo ""
    echo "Options:"
    echo "  --platform          Specifies the platform in the form os/arch[/variant][:osversion] (default: linux/amd64)"
    echo "  --image-ref-type    tags | digests (default: tags)"
    echo "  -h, --help          Display this help message and exit"
    echo ""
}

if [ $# -ne 2 ]; then
    show_help
    exit 1
fi

repository="$1"
version="$2"

# Removing cgr.dev/
repository=${1#cgr.dev/}

# Fetch history using the provided parameters
history=$(curl -s -H "$(crane auth token -H cgr.dev/$repository)" "https://cgr.dev/v2/$repository/_chainguard/history/$version" | jq .)
IFS=$'\n' read -r -d '' -a historyapidigestarray <<< $(echo $history | jq -r '.history[].digest')

oldest_digest=$(echo "$history" | jq -r '.history[0].digest')
oldtimestamp=$(echo "$history" | jq -r '.history[0].updateTimestamp')
newest_digest=$(echo "$history" | jq -r '.history[-1].digest')
newtimestamp=$(echo "$history" | jq -r '.history[-1].updateTimestamp')

oldimage="cgr.dev/$repository@$oldest_digest"
newimage="cgr.dev/$repository@$newest_digest"
oldcvejson=$(grype $oldimage -o json 2>/dev/null | jq '{Total: [.matches[].vulnerability] | length, Critical: [.matches[] | select(.vulnerability.severity == "Critical")] | length, High: [.matches[] | select(.vulnerability.severity == "High")] | length, WontFix: [.matches[] | select(.vulnerability.fix.state == "wont-fix")] | length }')
newcvejson=$(grype $newimage -o json 2>/dev/null | jq '{Total: [.matches[].vulnerability] | length, Critical: [.matches[] | select(.vulnerability.severity == "Critical")] | length, High: [.matches[] | select(.vulnerability.severity == "High")] | length, WontFix: [.matches[] | select(.vulnerability.fix.state == "wont-fix")] | length }')
cveresults=$(echo "$oldcvejson" | jq --argjson new "$newcvejson" '{
    Total: (.Total - $new.Total),
    Critical: (.Critical - $new.Critical),
    High: (.High - $new.High),
    WontFix: (.WontFix - $new.WontFix)
}')
epoch1=$(date -u -d "$oldtimestamp" +"%s")
epoch2=$(date -u -d "$newtimestamp" +"%s")
diff_seconds=$((epoch2 - epoch1))
diff_days=$((diff_seconds / 86400))
total_cves=$(echo "$cveresults" | jq -r '.Total | tostring')
critical_cves=$(echo "$cveresults" | jq -r '.Critical | tostring')
high_cves=$(echo "$cveresults" | jq -r '.High | tostring')

echo ""
echo "Summary:"
echo "  ${#historyapidigestarray[@]} releases over the course of $diff_days days"
echo "  These releases remediated $total_cves CVEs ($critical_cves Critical and $high_cves High)"

Generate Image CHANGELOG between 2 images using the chainctl diff API

#!/bin/bash

print_dots() {
    while true; do
        echo -n "."
        sleep 1
    done
}

show_help() {
    echo ""
    echo "Usage: $0 [options] <image_name> <previous_version> <new_version>"
    echo ""
    echo "Options:"
    echo "  --platform          Specifies the platform in the form os/arch[/variant][:osversion] (default: linux/amd64)"
    echo "  --image-ref-type    tags | digests (default: tags)"
    echo "  -h, --help          Display this help message and exit"
    echo ""
}

# Formatting variables
header="\e[1m"  # Bold text
normal="\e[0m"  # Normal text
green="\e[32m"  # Green
red="\e[31m"    # Red
yellow="\e[33m"    # Yellow
blue="\e[34m"   # Blue
purple="\e[35m"   # Purple
cyan="\e[36m"
lightgrey="\e[37m"

# Defaults
platform="linux/amd64"
reftype="tags"

# Check if they specified the platform
if [[ "$1" == "--platform" ]]; then
    platform="$2"
    shift 2
fi

# Prepend '@' to versions if digests is true
if [ "$1" = "--image-ref-type=digests" ] || [ "$1" = "--image-ref-type" -a "$2" = "digests" ]; then
    reftype="digests"
    shift
    if [[ "$1" == "digests" ]]; then
        shift
    fi
fi

# Assigning arguments to variables
image_name="$1"
previous_version="$2"
new_version="$3"

if [ "$reftype" = "digests" ]; then
    new_version="@$new_version"
    previous_version="@$previous_version"
fi

echo ""
echo -ne "Platform: ${purple}$platform${normal}\n"
echo -ne "Image Reference Type: ${purple}$reftype${normal}\n"
echo -ne "Image Name: ${purple}$image_name${normal}\n"
echo -ne "Previous Version: ${purple}$previous_version${normal}\n"
echo -ne "New Version: ${purple}$new_version${normal}\n"
echo ""

# Check if all required arguments are provided
if [[ -z "$image_name" || -z "$previous_version" || -z "$new_version" ]]; then
    show_help
    exit 1
fi

echo -ne "${header}Generating CHANGELOG${normal}"
print_dots &
dots_pid=$!
diff_api_json_output=$(chainctl images diff --platform="$platform" "$image_name":"$previous_version" "$image_name":"$new_version" 2>/dev/null)
kill $dots_pid > /dev/null 2>&1
echo ""
echo ""

echo -ne "Packages Added:${normal}"
packages_added=$(echo "$diff_api_json_output" | jq -r '.packages.added[] | select(.reference | startswith("pkg:apk")) | .name')
if [ -z "$packages_added" ]; then
    echo -n ""
else
    for pkg_added in $packages_added; do
        echo -ne "\n  - ${green}$pkg_added${normal}"
    done
        
fi

echo -ne "\nPackages Removed:${normal}"
packages_removed=$(echo "$diff_api_json_output" | jq -r '.packages.removed[] | select(.reference | startswith("pkg:apk")) | .name')
if [ -z "$packages_removed" ]; then
    echo -n ""
else
    for pkg_rm in $packages_removed; do
        echo -ne "\n  - ${red}$pkg_rm${normal}"
    done    
fi

echo -ne "\nPackages Changed:${normal}"
packages_changed=$(echo "$diff_api_json_output" | jq -r '.packages.changed[] | "\(.name) \(.previous.version) \(.current.version)"')
if [ -z "$packages_changed" ]; then
    echo -n ""
else
    echo "$packages_changed" | while read -r name prev_version curr_version; do
        prev_version=${prev_version//_/.}
        curr_version=${curr_version//_/.}
        IFS='.' read -ra PREV <<< "$prev_version"
        IFS='.' read -ra CURR <<< "$curr_version"
        upgraded=false
        downgraded=false
        for i in {0..2}; do
            if [[ ${CURR[i]} -gt ${PREV[i]} ]]; then
                upgraded=true
                break
            elif [[ ${CURR[i]} -lt ${PREV[i]} ]]; then
                downgraded=true
                break
            fi
        done
        if $upgraded; then
            echo -ne "\n  - ${blue}$name: Upgraded from $prev_version to $curr_version${normal}"
        elif $downgraded; then
            echo -ne "\n  - ${red}$name: Downgraded from $prev_version to $curr_version${normal}"
        fi
    done
fi

# Extracting and printing vulnerabilities removed
echo -e "\nVulnerabilities Removed:${normal}"

# Define an array of severities for organization
declare -a severities=("Critical" "High" "Medium" "Low" "Unknown")

# Loop through each severity and print vulnerabilities
for severity in "${severities[@]}"; do
    # Extract vulnerabilities of this severity
    vulnerabilities=$(echo "$diff_api_json_output" | jq -r --arg sev "$severity" '.vulnerabilities.removed[] | select(.severity == $sev) | .id')

    # Check if there are vulnerabilities of this severity
    if [ -z "$vulnerabilities" ]; then
        echo -e "  ${purple}${severity}:${normal}"
    else
        echo -e "  ${purple}${severity}:${normal}"
        for vuln in $vulnerabilities; do
            echo -ne "    - $vuln${normal}\n"
        done
    fi
done
echo -e "\n${header}Finished!\n${normal}"

OpenSSF Scorecards

API

$ curl https://api.securityscorecards.dev/projects/github.com/kubernetes/kubernetes \
  | jq | grep -i -B 1 -A 1 \"score\"

Sigstore

step CLI

$ step oauth --provider=https://oauth2.sigstore.dev/auth --client-id=sigstore --listen \
  localhost:0 --oidc --bare 2>/dev/null > id_token
$ cat id_token | step crypto jwt inspect --insecure

Rekor

Get container digest

$ rekor-cli search --sha <digest> | uniq
$ rekor-cli get --uuid <UUID>  --format=json | jq -r .Attestation |  base64 -D |  jq -r .
$ rekor-cli search --artifact <(echo -n CHAINS_GIT_COMMIT)

Get UUID

rekor-cli get --uuid <UUID>  --format=json | jq -r .Body.RekordObj.signature.publicKey.content | base64 -D |  openssl x509  -text- noout
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment