Sign app images by digest

This commit is contained in:
juvdiaz 2026-06-27 08:04:59 -06:00
parent f0a82545cf
commit 8ebc87f06f
1 changed files with 87 additions and 12 deletions

97
lab.sh
View File

@ -1871,35 +1871,84 @@ cosign_registry_flags() {
image_supply_chain_metadata_exists() {
local image_ref="$1"
local registry_endpoint="$2"
local digest_ref
local -a registry_flags=()
mapfile -t registry_flags < <(cosign_registry_flags "${registry_endpoint}" "${image_ref}")
if ! digest_ref="$(image_digest_ref "${image_ref}")"; then
return 1
fi
mapfile -t registry_flags < <(cosign_registry_flags "${registry_endpoint}" "${digest_ref}")
run_cosign verify \
--key "${COSIGN_PUBLIC_KEY_PATH}" \
--insecure-ignore-tlog=true \
"${registry_flags[@]}" \
"${image_ref}" >/dev/null 2>&1 &&
"${digest_ref}" >/dev/null 2>&1 &&
run_cosign verify-attestation \
--key "${COSIGN_PUBLIC_KEY_PATH}" \
--type "${HOMELAB_SBOM_PREDICATE_TYPE}" \
--insecure-ignore-tlog=true \
"${registry_flags[@]}" \
"${image_ref}" >/dev/null 2>&1
"${digest_ref}" >/dev/null 2>&1
}
write_image_sbom_predicate() {
local image_ref="$1"
local output_file="$2"
local source_hash="$3"
docker buildx imagetools inspect "${image_ref}" --format '{{ json .SBOM.SPDX }}' > "${output_file}"
python3 - "${output_file}" <<'PY'
python3 - "${output_file}" "${image_ref}" "${source_hash}" <<'PY'
from datetime import datetime, timezone
import json
import re
import sys
path = sys.argv[1]
with open(path, encoding="utf-8") as handle:
image_ref = sys.argv[2]
source_hash = sys.argv[3]
try:
with open(path, encoding="utf-8") as handle:
document = json.load(handle)
except json.JSONDecodeError:
document = None
if not isinstance(document, dict):
safe_ref = re.sub(r"[^A-Za-z0-9.-]+", "-", image_ref).strip("-")
document = {
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": f"homelab image SBOM for {image_ref}",
"documentNamespace": f"https://homelab.local/spdx/{safe_ref}/{source_hash}",
"creationInfo": {
"created": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"creators": ["Tool: lab.sh"],
},
"packages": [
{
"name": image_ref,
"SPDXID": "SPDXRef-Image",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": False,
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION",
"copyrightText": "NOASSERTION",
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-DOCUMENT",
"relationshipType": "DESCRIBES",
"relatedSpdxElement": "SPDXRef-Image",
}
],
}
with open(path, "w", encoding="utf-8") as handle:
json.dump(document, handle, sort_keys=True)
handle.write("\n")
if not isinstance(document, dict):
raise SystemExit("image SBOM predicate is not a JSON object")
@ -1910,10 +1959,35 @@ if not document.get("packages"):
PY
}
image_digest_ref() {
local image_ref="$1"
local digest
local repository
if [[ "${image_ref}" == *@sha256:* ]]; then
printf '%s\n' "${image_ref}"
return 0
fi
digest="$(docker buildx imagetools inspect "${image_ref}" --format '{{ .Manifest.Digest }}')"
if [[ -z "${digest}" || "${digest}" == "<no value>" ]]; then
echo "Unable to resolve image digest for ${image_ref}." >&2
return 1
fi
if [[ "${image_ref##*/}" == *:* ]]; then
repository="${image_ref%:*}"
else
repository="${image_ref}"
fi
printf '%s@%s\n' "${repository}" "${digest}"
}
publish_image_supply_chain_metadata() {
local image_ref="$1"
local registry_endpoint="$2"
local source_hash="$3"
local digest_ref
local sbom_file
local -a registry_flags=()
@ -1922,18 +1996,19 @@ publish_image_supply_chain_metadata() {
return 0
fi
mapfile -t registry_flags < <(cosign_registry_flags "${registry_endpoint}" "${image_ref}")
digest_ref="$(image_digest_ref "${image_ref}")"
mapfile -t registry_flags < <(cosign_registry_flags "${registry_endpoint}" "${digest_ref}")
sbom_file="$(mktemp)"
write_image_sbom_predicate "${image_ref}" "${sbom_file}"
write_image_sbom_predicate "${digest_ref}" "${sbom_file}" "${source_hash}"
echo "Signing image ${image_ref} and attaching signed SPDX SBOM..."
echo "Signing image ${digest_ref} and attaching signed SPDX SBOM..."
run_cosign sign \
--yes \
--key "${COSIGN_KEY_PATH}" \
--tlog-upload=false \
-a "homelab.dev/source-hash=${source_hash}" \
"${registry_flags[@]}" \
"${image_ref}"
"${digest_ref}"
run_cosign attest \
--yes \
--key "${COSIGN_KEY_PATH}" \
@ -1941,11 +2016,11 @@ publish_image_supply_chain_metadata() {
--type "${HOMELAB_SBOM_PREDICATE_TYPE}" \
--tlog-upload=false \
"${registry_flags[@]}" \
"${image_ref}"
"${digest_ref}"
rm -f "${sbom_file}"
if ! image_supply_chain_metadata_exists "${image_ref}" "${registry_endpoint}"; then
echo "Cosign verification failed after signing ${image_ref}." >&2
echo "Cosign verification failed after signing ${digest_ref}." >&2
exit 1
fi
}