Created
March 31, 2026 18:18
-
-
Save hrodrig/4e1d56560714d722afc05ec4ddbfc123 to your computer and use it in GitHub Desktop.
Resolve bitnamisecure/postgresql digest-style tags → platforms + versions (docker, jq, crane)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # SPDX-License-Identifier: ISC | |
| # | |
| # Copyright 2026 Hermes Rodriguez | |
| # | |
| # Permission to use, copy, modify, and/or distribute this software for any | |
| # purpose with or without fee is hereby granted, provided that the above | |
| # copyright notice and this permission notice appear in all copies. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | |
| # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | |
| # AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | |
| # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | |
| # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | |
| # OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | |
| # PERFORMANCE OF THIS SOFTWARE. | |
| # | |
| # Personal / educational helper: resolves digest-style tags on OCI registries, | |
| # including Bitnami Secure PostgreSQL patterns (Notary subject chain, TAC, | |
| # Docker manifest lists, SLSA DSSE). Requires: docker, jq, crane. | |
| set -eu | |
| usage() { | |
| cat <<'EOF' | |
| Resolve Bitnami Secure PostgreSQL tag details (version + per-arch digest). | |
| Usage: | |
| resolve-bitnami-postgresql-tag.sh <tag-without-prefix> | |
| resolve-bitnami-postgresql-tag.sh <sha256-...> | |
| resolve-bitnami-postgresql-tag.sh --repo <repo> <tag-without-prefix> | |
| Examples: | |
| resolve-bitnami-postgresql-tag.sh 3b18e4b8fd00bd17db692ab9a54d3c11191a9e300ffaaf7dcc5d2ef707f1e26f | |
| resolve-bitnami-postgresql-tag.sh sha256-3b18e4b8fd00bd17db692ab9a54d3c11191a9e300ffaaf7dcc5d2ef707f1e26f | |
| resolve-bitnami-postgresql-tag.sh --repo docker.io/bitnami/postgresql 621359194c6a0f0fd502c1996f40f539c3e5f2da43401b6d52ee5614722b7cba | |
| EOF | |
| } | |
| require_cmd() { | |
| if ! command -v "$1" >/dev/null 2>&1; then | |
| echo "❌ missing dependency: $1" >&2 | |
| exit 1 | |
| fi | |
| } | |
| repo="docker.io/bitnamisecure/postgresql" | |
| if [ "${1:-}" = "--repo" ]; then | |
| if [ $# -lt 3 ]; then | |
| usage | |
| exit 1 | |
| fi | |
| repo="$2" | |
| shift 2 | |
| fi | |
| if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then | |
| usage | |
| exit 0 | |
| fi | |
| if [ $# -ne 1 ]; then | |
| usage | |
| exit 1 | |
| fi | |
| require_cmd docker | |
| require_cmd jq | |
| require_cmd crane | |
| raw_tag="$1" | |
| case "$raw_tag" in | |
| sha256-*) tag="$raw_tag" ;; | |
| *) tag="sha256-$raw_tag" ;; | |
| esac | |
| ref="${repo}:${tag}" | |
| echo "🔍 Resolving ${ref}" | |
| manifest_json="$(docker manifest inspect "$ref")" | |
| resolve_arch_digest() { | |
| arch="$1" | |
| _mj="$2" | |
| printf "%s" "$_mj" | jq -r --arg arch "$arch" '.manifests[]? | select(.platform.os=="linux" and .platform.architecture==$arch) | .digest' | head -n 1 | |
| } | |
| # Ensure digest ref includes sha256: for crane/docker references. | |
| with_sha256_prefix() { | |
| _dig="$1" | |
| [ -z "$_dig" ] && return | |
| case "$_dig" in | |
| sha256:*) printf "%s" "$_dig" ;; | |
| *) printf "sha256:%s" "$_dig" ;; | |
| esac | |
| } | |
| # Print linux/amd64 and linux/arm64 blocks with versions from image config labels. | |
| # Args: repo, amd64_digest (hex or sha256:... or empty), arm64_digest, optional note. | |
| emit_runnable_report() { | |
| _repo="$1" | |
| _amd_plain="$2" | |
| _arm_plain="$3" | |
| case "$_amd_plain" in sha256:*) _amd_plain="${_amd_plain#sha256:}" ;; esac | |
| case "$_arm_plain" in sha256:*) _arm_plain="${_arm_plain#sha256:}" ;; esac | |
| if [ -n "${4:-}" ]; then | |
| printf "%s\n" "$4" | |
| fi | |
| _amd_ref="$(with_sha256_prefix "$_amd_plain")" | |
| _arm_ref="$(with_sha256_prefix "$_arm_plain")" | |
| _ver_amd="n/a" | |
| _ver_arm="n/a" | |
| if [ -n "$_amd_plain" ]; then | |
| _ver_amd="$(crane config "${_repo}@${_amd_ref}" 2>/dev/null | jq -r '.config.Labels["org.opencontainers.image.version"] // "unknown"')" | |
| fi | |
| if [ -n "$_arm_plain" ]; then | |
| _ver_arm="$(crane config "${_repo}@${_arm_ref}" 2>/dev/null | jq -r '.config.Labels["org.opencontainers.image.version"] // "unknown"')" | |
| fi | |
| echo "" | |
| echo "Tag: ${tag}" | |
| echo "Repo: ${_repo}" | |
| echo "" | |
| echo "linux/amd64" | |
| echo " digest: ${_amd_plain:-n/a}" | |
| echo " version: ${_ver_amd}" | |
| if [ -n "$_amd_plain" ]; then | |
| echo " image: ${_repo}@${_amd_ref}" | |
| else | |
| echo " image: n/a" | |
| fi | |
| echo "" | |
| echo "linux/arm64" | |
| echo " digest: ${_arm_plain:-n/a}" | |
| echo " version: ${_ver_arm}" | |
| if [ -n "$_arm_plain" ]; then | |
| echo " image: ${_repo}@${_arm_ref}" | |
| else | |
| echo " image: n/a" | |
| fi | |
| } | |
| digests_from_manifest_list_json() { | |
| _mj="$1" | |
| _amd="$(printf "%s" "$_mj" | jq -r '.manifests[]? | select(.platform.os=="linux" and .platform.architecture=="amd64") | .digest' | head -n 1)" | |
| _arm="$(printf "%s" "$_mj" | jq -r '.manifests[]? | select(.platform.os=="linux" and .platform.architecture=="arm64") | .digest' | head -n 1)" | |
| printf "%s\n%s\n" "$_amd" "$_arm" | |
| } | |
| try_resolve_slsa_dsse() { | |
| _repo="$1" | |
| _json="$2" | |
| _dsse_d="$(printf "%s" "$_json" | jq -r '.layers[]? | select(.mediaType == "application/vnd.dsse.envelope.v1+json") | .digest' | head -n 1)" | |
| [ -z "$_dsse_d" ] && return 1 | |
| _blob="$(crane blob "${_repo}@${_dsse_d}" 2>/dev/null)" || return 1 | |
| _stmt="$(printf "%s" "$_blob" | jq -r ".payload | @base64d" 2>/dev/null)" || return 1 | |
| [ -z "$_stmt" ] && return 1 | |
| _amd_hex="$(printf "%s" "$_stmt" | jq -r '.subject[]? | select(.name | test("linux/amd64")) | .digest.sha256' | head -n 1)" | |
| _arm_hex="$(printf "%s" "$_stmt" | jq -r '.subject[]? | select(.name | test("linux/arm64")) | .digest.sha256' | head -n 1)" | |
| if [ -z "$_amd_hex" ] && [ -z "$_arm_hex" ]; then | |
| return 1 | |
| fi | |
| emit_runnable_report "${_repo}" "${_amd_hex}" "${_arm_hex}" "ℹ️ Per-arch digests taken from SLSA provenance (in-toto statement inside DSSE)." | |
| return 0 | |
| } | |
| # Tags copied from some UIs point at an OCI index whose only entry is a Notary | |
| # signature. subject may be: a VMware TAC bundle, a Docker/OCI multi-arch list, | |
| # or an SLSA provenance manifest (DSSE) listing per-arch image digests. | |
| resolve_attestation_chain() { | |
| _repo="$1" | |
| _d="$2" | |
| _depth=0 | |
| while [ "$_depth" -lt 8 ]; do | |
| _m="$(crane manifest "${_repo}@${_d}" 2>/dev/null)" || return 1 | |
| _subj="$(printf "%s" "$_m" | jq -r ".subject.digest // empty")" | |
| if [ -n "$_subj" ]; then | |
| _d="$_subj" | |
| _depth=$((_depth + 1)) | |
| continue | |
| fi | |
| _pmd="$(printf "%s" "$_m" | jq -r '.layers[]? | select(.mediaType == "application/vnd.vmware.tac.product-metadata.layer.v1.json") | .digest' | head -n 1)" | |
| if [ -n "$_pmd" ]; then | |
| _meta="$(crane blob "${_repo}@${_pmd}" 2>/dev/null)" || return 1 | |
| _ver="$(printf "%s" "$_meta" | jq -r '.version // "unknown"')" | |
| _name="$(printf "%s" "$_meta" | jq -r '.name // "unknown"')" | |
| echo "" | |
| echo "⚠️ This tag resolves to supply-chain metadata (e.g. Notary + VMware TAC), not a runnable image manifest list." | |
| echo " Product version from TAC product-metadata: ${_name} ${_ver}" | |
| echo "" | |
| echo "Tag: ${tag}" | |
| echo "Repo: ${_repo}" | |
| echo "" | |
| echo "linux/amd64" | |
| echo " digest: n/a (pin a manifest-list tag like :latest, then use the per-arch digest from that list)" | |
| echo " version: n/a" | |
| echo " image: n/a" | |
| echo "" | |
| echo "linux/arm64" | |
| echo " digest: n/a" | |
| echo " version: n/a" | |
| echo " image: n/a" | |
| return 0 | |
| fi | |
| _pair="$(digests_from_manifest_list_json "$_m")" | |
| _amd_d="$(printf "%s" "$_pair" | sed -n "1p")" | |
| _arm_d="$(printf "%s" "$_pair" | sed -n "2p")" | |
| if [ -n "$_amd_d" ] || [ -n "$_arm_d" ]; then | |
| emit_runnable_report "${_repo}" "${_amd_d}" "${_arm_d}" "ℹ️ Runnable images resolved from a manifest list after following the attestation chain." | |
| return 0 | |
| fi | |
| if try_resolve_slsa_dsse "${_repo}" "$_m"; then | |
| return 0 | |
| fi | |
| _cfgmt="$(printf "%s" "$_m" | jq -r ".config.mediaType // empty")" | |
| case "$_cfgmt" in | |
| *docker.container.image.v1*|*oci.image.config.v1*) | |
| _dref="$(with_sha256_prefix "$(printf "%s" "$_d" | sed 's/^sha256://')")" | |
| _lbl="$(crane config "${_repo}@${_dref}" 2>/dev/null | jq -r '.config.Labels["org.opencontainers.image.version"] // "unknown"')" | |
| echo "" | |
| echo "ℹ️ Single-platform image manifest (no amd64/arm64 in parent index); config digest ${_d}" | |
| echo "" | |
| echo "Tag: ${tag}" | |
| echo "Repo: ${_repo}" | |
| echo "" | |
| echo "linux/amd64" | |
| echo " digest: n/a" | |
| echo " version: n/a" | |
| echo " image: n/a" | |
| echo "" | |
| echo "linux/arm64" | |
| echo " digest: n/a" | |
| echo " version: n/a" | |
| echo " image: n/a" | |
| echo "" | |
| echo "Resolved OCI version label for this manifest: ${_lbl}" | |
| return 0 | |
| ;; | |
| esac | |
| return 1 | |
| done | |
| return 1 | |
| } | |
| amd64_digest="$(resolve_arch_digest amd64 "$manifest_json")" | |
| arm64_digest="$(resolve_arch_digest arm64 "$manifest_json")" | |
| if [ -z "$amd64_digest" ] && [ -z "$arm64_digest" ]; then | |
| resolved=0 | |
| while IFS= read -r cand; do | |
| [ -z "$cand" ] && continue | |
| if resolve_attestation_chain "$repo" "$cand"; then | |
| resolved=1 | |
| break | |
| fi | |
| done <<EOF | |
| $(printf "%s" "$manifest_json" | jq -r ".manifests[]? | .digest") | |
| EOF | |
| if [ "$resolved" -eq 0 ]; then | |
| echo "❌ no linux/amd64 or linux/arm64 digest found in manifest list (and no TAC/single-image fallback matched)" >&2 | |
| exit 1 | |
| fi | |
| exit 0 | |
| fi | |
| resolve_version() { | |
| digest="$1" | |
| if [ -z "$digest" ]; then | |
| printf "n/a" | |
| return | |
| fi | |
| crane config "${repo}@${digest}" 2>/dev/null | jq -r '.config.Labels["org.opencontainers.image.version"] // "unknown"' | |
| } | |
| amd64_version="$(resolve_version "$amd64_digest")" | |
| arm64_version="$(resolve_version "$arm64_digest")" | |
| echo "" | |
| echo "Tag: ${tag}" | |
| echo "Repo: ${repo}" | |
| echo "" | |
| echo "linux/amd64" | |
| echo " digest: ${amd64_digest:-n/a}" | |
| echo " version: ${amd64_version}" | |
| echo " image: ${repo}@${amd64_digest:-n/a}" | |
| echo "" | |
| echo "linux/arm64" | |
| echo " digest: ${arm64_digest:-n/a}" | |
| echo " version: ${arm64_version}" | |
| echo " image: ${repo}@${arm64_digest:-n/a}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment