Sign app images by digest
This commit is contained in:
parent
f0a82545cf
commit
8ebc87f06f
97
lab.sh
97
lab.sh
|
|
@ -1871,35 +1871,84 @@ cosign_registry_flags() {
|
||||||
image_supply_chain_metadata_exists() {
|
image_supply_chain_metadata_exists() {
|
||||||
local image_ref="$1"
|
local image_ref="$1"
|
||||||
local registry_endpoint="$2"
|
local registry_endpoint="$2"
|
||||||
|
local digest_ref
|
||||||
local -a registry_flags=()
|
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 \
|
run_cosign verify \
|
||||||
--key "${COSIGN_PUBLIC_KEY_PATH}" \
|
--key "${COSIGN_PUBLIC_KEY_PATH}" \
|
||||||
--insecure-ignore-tlog=true \
|
--insecure-ignore-tlog=true \
|
||||||
"${registry_flags[@]}" \
|
"${registry_flags[@]}" \
|
||||||
"${image_ref}" >/dev/null 2>&1 &&
|
"${digest_ref}" >/dev/null 2>&1 &&
|
||||||
run_cosign verify-attestation \
|
run_cosign verify-attestation \
|
||||||
--key "${COSIGN_PUBLIC_KEY_PATH}" \
|
--key "${COSIGN_PUBLIC_KEY_PATH}" \
|
||||||
--type "${HOMELAB_SBOM_PREDICATE_TYPE}" \
|
--type "${HOMELAB_SBOM_PREDICATE_TYPE}" \
|
||||||
--insecure-ignore-tlog=true \
|
--insecure-ignore-tlog=true \
|
||||||
"${registry_flags[@]}" \
|
"${registry_flags[@]}" \
|
||||||
"${image_ref}" >/dev/null 2>&1
|
"${digest_ref}" >/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
write_image_sbom_predicate() {
|
write_image_sbom_predicate() {
|
||||||
local image_ref="$1"
|
local image_ref="$1"
|
||||||
local output_file="$2"
|
local output_file="$2"
|
||||||
|
local source_hash="$3"
|
||||||
|
|
||||||
docker buildx imagetools inspect "${image_ref}" --format '{{ json .SBOM.SPDX }}' > "${output_file}"
|
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 json
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
path = sys.argv[1]
|
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)
|
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):
|
if not isinstance(document, dict):
|
||||||
raise SystemExit("image SBOM predicate is not a JSON object")
|
raise SystemExit("image SBOM predicate is not a JSON object")
|
||||||
|
|
@ -1910,10 +1959,35 @@ if not document.get("packages"):
|
||||||
PY
|
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() {
|
publish_image_supply_chain_metadata() {
|
||||||
local image_ref="$1"
|
local image_ref="$1"
|
||||||
local registry_endpoint="$2"
|
local registry_endpoint="$2"
|
||||||
local source_hash="$3"
|
local source_hash="$3"
|
||||||
|
local digest_ref
|
||||||
local sbom_file
|
local sbom_file
|
||||||
local -a registry_flags=()
|
local -a registry_flags=()
|
||||||
|
|
||||||
|
|
@ -1922,18 +1996,19 @@ publish_image_supply_chain_metadata() {
|
||||||
return 0
|
return 0
|
||||||
fi
|
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)"
|
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 \
|
run_cosign sign \
|
||||||
--yes \
|
--yes \
|
||||||
--key "${COSIGN_KEY_PATH}" \
|
--key "${COSIGN_KEY_PATH}" \
|
||||||
--tlog-upload=false \
|
--tlog-upload=false \
|
||||||
-a "homelab.dev/source-hash=${source_hash}" \
|
-a "homelab.dev/source-hash=${source_hash}" \
|
||||||
"${registry_flags[@]}" \
|
"${registry_flags[@]}" \
|
||||||
"${image_ref}"
|
"${digest_ref}"
|
||||||
run_cosign attest \
|
run_cosign attest \
|
||||||
--yes \
|
--yes \
|
||||||
--key "${COSIGN_KEY_PATH}" \
|
--key "${COSIGN_KEY_PATH}" \
|
||||||
|
|
@ -1941,11 +2016,11 @@ publish_image_supply_chain_metadata() {
|
||||||
--type "${HOMELAB_SBOM_PREDICATE_TYPE}" \
|
--type "${HOMELAB_SBOM_PREDICATE_TYPE}" \
|
||||||
--tlog-upload=false \
|
--tlog-upload=false \
|
||||||
"${registry_flags[@]}" \
|
"${registry_flags[@]}" \
|
||||||
"${image_ref}"
|
"${digest_ref}"
|
||||||
rm -f "${sbom_file}"
|
rm -f "${sbom_file}"
|
||||||
|
|
||||||
if ! image_supply_chain_metadata_exists "${image_ref}" "${registry_endpoint}"; then
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue