#!/usr/bin/env bash # Deterministic SBOM + attestation helper for DOCKER-44-002 # Usage: ./sbom_attest.sh [output-dir] [cosign-key] # - image-ref: fully qualified image (e.g., ghcr.io/stellaops/policy:1.2.3) # - output-dir: defaults to ./sbom # - cosign-key: path to cosign key (PEM). If omitted, uses keyless if allowed (COSIGN_EXPERIMENTAL=1) set -euo pipefail IMAGE_REF=${1:?"image ref required"} OUT_DIR=${2:-sbom} COSIGN_KEY=${3:-} mkdir -p "${OUT_DIR}" # Normalize filename (replace / and : with _) name_safe() { echo "$1" | tr '/:' '__' } BASENAME=$(name_safe "${IMAGE_REF}") SPDX_JSON="${OUT_DIR}/${BASENAME}.spdx.json" CDX_JSON="${OUT_DIR}/${BASENAME}.cdx.json" ATTESTATION="${OUT_DIR}/${BASENAME}.sbom.att" # Freeze timestamps for reproducibility export SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-1704067200} # Generate SPDX 3.0-ish JSON (syft formats are stable and offline-friendly) syft "${IMAGE_REF}" -o spdx-json > "${SPDX_JSON}" # Generate CycloneDX 1.6 JSON syft "${IMAGE_REF}" -o cyclonedx-json > "${CDX_JSON}" # Attach SBOMs as cosign attestations (one per format) export COSIGN_EXPERIMENTAL=${COSIGN_EXPERIMENTAL:-1} COSIGN_ARGS=("attest" "--predicate" "${SPDX_JSON}" "--type" "spdx" "${IMAGE_REF}") if [[ -n "${COSIGN_KEY}" ]]; then COSIGN_ARGS+=("--key" "${COSIGN_KEY}") fi cosign "${COSIGN_ARGS[@]}" COSIGN_ARGS=("attest" "--predicate" "${CDX_JSON}" "--type" "cyclonedx" "${IMAGE_REF}") if [[ -n "${COSIGN_KEY}" ]]; then COSIGN_ARGS+=("--key" "${COSIGN_KEY}") fi cosign "${COSIGN_ARGS[@]}" echo "SBOMs written to ${SPDX_JSON} and ${CDX_JSON}" >&2 echo "Attestations pushed for ${IMAGE_REF}" >&2