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

+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash
# install-cosign.sh [dir]: cosign, pinned, checked against its release checksum.
#
# Bump the version and both checksums together, from the release's
# cosign_checksums.txt.
set -euo pipefail
VERSION=v3.1.3
SHA256_AMD64=4629c757b7618056f8ddd7e2625ae9fdd94c0372a65049520bc7d9df9efc7f71
SHA256_ARM64=c5d324e091826b0d7a78eb16fef316450b4eb9aaec045611c08ba06f5e73220a
dir=${1:-/usr/local/bin}
case $(uname -m) in
x86_64) arch=amd64 sha=$SHA256_AMD64 ;;
aarch64) arch=arm64 sha=$SHA256_ARM64 ;;
*) echo "no cosign checksum for $(uname -m)" >&2; exit 1 ;;
esac
if [ -x "$dir/cosign" ] && echo "$sha $dir/cosign" | sha256sum -c - >/dev/null 2>&1; then
exit 0
fi
tmp=$(mktemp)
curl -fsSLo "$tmp" "https://github.com/sigstore/cosign/releases/download/$VERSION/cosign-linux-$arch"
echo "$sha $tmp" | sha256sum -c - >/dev/null
install -m 0755 "$tmp" "$dir/cosign"
rm -f "$tmp"
echo "cosign $VERSION ($arch) in $dir"
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"
+14
View File
@@ -0,0 +1,14 @@
#!/bin/bash
# verify-base.sh <Dockerfile>...: every image the files build on is signed with
# netOrk's key, checked before the build (NetOrk/netork#1390).
#
# The images are the FROM lines' references (image_tools/dockerfile.py): stages
# of the same file and scratch are none, and one without a digest is refused.
set -euo pipefail
tools=$(cd "$(dirname "$0")/.." && pwd)
[ $# -gt 0 ] || { echo "usage: verify-base.sh <Dockerfile>..." >&2; exit 2; }
files=()
for f in "$@"; do files+=("$(cd "$(dirname "$f")" && pwd)/$(basename "$f")"); done
mapfile -t bases < <(cd "$tools" && python3 -m image_tools.dockerfile "${files[@]}")
[ ${#bases[@]} -gt 0 ] || { echo "no image to verify in $*" >&2; exit 1; }
"$tools/bin/verify-signature.sh" "${bases[@]}"
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
# verify-signature.sh <image>@<digest>...: each one is signed with netOrk's key.
#
# The key is cosign.pub beside this script's repository, the one public half of
# the COSIGN_PRIVATE_KEY_B64 secret every image of NetOrk is signed with. A
# reference without a digest is refused: a tag can move after the check.
set -euo pipefail
cd "$(dirname "$0")/.."
[ $# -gt 0 ] || { echo "usage: verify-signature.sh <image>@<digest>..." >&2; exit 2; }
for ref in "$@"; do
case $ref in *@sha256:*) ;; *) echo "$ref is not pinned by digest" >&2; exit 1 ;; esac
if ! out=$(cosign verify --key cosign.pub --insecure-ignore-tlog "$ref" 2>&1); then
echo "$out" >&2
echo "not signed with netOrk's key: $ref" >&2
exit 1
fi
echo "verified: $ref"
done