Sign app images by digest
This commit is contained in:
parent
f0a82545cf
commit
8ebc87f06f
99
lab.sh
99
lab.sh
|
|
@ -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:
|
||||
document = json.load(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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue