feat: judge, sign and verify netOrk's images in one place (NetOrk/netork#1390)
CI / Tests, lint (push) Successful in 18s

The CIS gate, the SBOM, the cosign signature and the matcher's verdict lived
in NetOrk/base-os, the head of the chain. Every link below it -- base-python,
engine, ui, satellite, the service images -- needs the same, so the tools
move here, and every image repository pins them by commit.

- image_tools: cis.py, cis_os.sh, sbom.py and verify.py from base-os. The
  image's own repository holds its cis.toml and packages.allow; both default
  to the working directory.
- bin/sign.sh signs whatever platforms a reference holds: base-os's index of
  amd64 and arm64 (EXPECT_PLATFORMS) or a dev image's single manifest
  (image_tools/platforms.py).
- bin/verify-base.sh checks every FROM image of a Dockerfile against
  cosign.pub before a build; one without a digest is refused
  (image_tools/dockerfile.py).
- bin/install-cosign.sh installs cosign v3.1.3 for amd64 and arm64 runners,
  checked against the release checksums.
- actions/verify-base, judge, sign wrap them for the workflows.
- cosign.pub is the public half of the COSIGN_PRIVATE_KEY_B64 org secret,
  the key rotated on 2026-10-22.
This commit is contained in:
Christian Manivong committed 2026-10-22 16:23:00 +02:00
commit d139d66c3d
27 files changed
+2085

No files matched your search

Executable
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
# sign.sh <image>@<digest> <dir>: sign a published image and attach what it was judged by.
#
# The reference and every platform image behind it get a cosign signature made
# with the key in COSIGN_PRIVATE_KEY_B64 (its password in COSIGN_PASSWORD). Each
# platform image gets its CIS result (<dir>/cis-<arch>.json, image_tools/cis.py)
# and its CycloneDX SBOM (<dir>/sbom-<arch>.cdx.json, image_tools/sbom.py) as
# in-toto attestations. This takes the place of Docker Content Trust (CIS Docker
# 4.5), as cosign does for Docker's Hardened Images.
#
# The platforms are whatever the reference holds (image_tools/platforms.py): an
# index of amd64 and arm64, or one manifest. EXPECT_PLATFORMS ("amd64 arm64")
# makes any other set an error.
#
# Nothing goes to a public transparency log: cosign-signing.json names none, so
# the names in this registry stay inside. Everything is verified against
# cosign.pub before it returns.
set -euo pipefail
: "${COSIGN_PRIVATE_KEY_B64:?is empty: the secret is missing}" "${COSIGN_PASSWORD:?is empty: the secret is missing}"
# The key comes base64 in one line: the runner prints a step's env and masks a
# secret only where it appears whole, and a PEM key's lines did not (2026-10-22).
COSIGN_PRIVATE_KEY=$(printf '%s' "$COSIGN_PRIVATE_KEY_B64" | base64 -d)
export COSIGN_PRIVATE_KEY
ref=$1
dir=$(cd "$2" && pwd)
cd "$(dirname "$0")/.."
CIS_TYPE=https://netork.io/attestations/cis/v1
# quietly <command>: cosign's chatter only when it fails.
quietly() { local out; out=$("$@" 2>&1) || { echo "$out" >&2; return 1; }; }
case $ref in *@sha256:*) ;; *) echo "$ref is not pinned by digest" >&2; exit 1 ;; esac
image=${ref%@*}
case ${image##*/} in *:*) image=${image%:*} ;; esac # the tag, not a registry port
quietly cosign sign --yes --recursive --key env://COSIGN_PRIVATE_KEY \
--signing-config cosign-signing.json "$ref"
quietly cosign verify --key cosign.pub --insecure-ignore-tlog "$ref"
raw=$(mktemp) config=$(mktemp)
trap 'rm -f "$raw" "$config"' EXIT
docker buildx imagetools inspect "$ref" --raw >"$raw"
docker buildx imagetools inspect "$ref" --format '{{json .Image}}' >"$config"
platforms=$(python3 -m image_tools.platforms "${ref#*@}" "$raw" "$config")
if [ -n "${EXPECT_PLATFORMS:-}" ]; then
got=$(echo "$platforms" | cut -d' ' -f1 | sort | xargs)
want=$(echo "$EXPECT_PLATFORMS" | tr ' ' '\n' | sort | xargs)
[ "$got" = "$want" ] || { echo "expected $want in $ref, found $got" >&2; exit 1; }
fi
while read -r arch digest; do
for attestation in "$CIS_TYPE cis-$arch.json" "cyclonedx sbom-$arch.cdx.json"; do
read -r type file <<<"$attestation"
quietly cosign attest --yes --key env://COSIGN_PRIVATE_KEY --signing-config cosign-signing.json \
--type "$type" --predicate "$dir/$file" "$image@$digest"
quietly cosign verify-attestation --key cosign.pub --insecure-ignore-tlog \
--type "$type" "$image@$digest"
done
echo "signed and attested: $image@$digest ($arch)"
done <<<"$platforms"