feat(ci): add scripts for cosign setup, release asset attestation, and verification

This commit is contained in:
Ivan
2026-03-31 00:32:22 +03:00
parent eb0fec57d0
commit e8e5128bc6
5 changed files with 169 additions and 0 deletions

4
cosign.pub Normal file
View File

@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyWx2eajLfc+pflz3cq2XcmopUoO9
9oGHYfmtd+zSod22RkU4YJtIcEBesql7Wb+wjkqBxjpXgdrTB9Tu3dPVZQ==
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,36 @@
#!/bin/sh
# Create SLSA v1 cosign bundle attestations next to each release binary under DIR.
# Requires: cosign on PATH; COSIGN_KEY_PATH to cosign private key PEM; COSIGN_PASSWORD
# if the key is encrypted. Run from repository root so scripts/ci/slsa-predicate.py resolves.
#
# Usage: attest-release-assets.sh <directory>
set -eu
DIR="${1:?directory}"
KEY="${COSIGN_KEY_PATH:?set COSIGN_KEY_PATH}"
if [ ! -f "$KEY" ]; then
echo "attest-release-assets.sh: missing key file $KEY" >&2
exit 1
fi
PRED="$(mktemp "${TMPDIR:-/tmp}/slsa-pred.XXXXXX")"
trap 'rm -f "$PRED"' EXIT INT
python3 scripts/ci/slsa-predicate.py > "$PRED"
find "$DIR" -type f ! -name '*.sha256' ! -name '*.cosign.bundle' | while IFS= read -r f; do
case "$f" in
*/.git/*) continue ;;
esac
echo "attest: $f"
cosign attest-blob --yes \
--key "$KEY" \
--predicate "$PRED" \
--type slsaprovenance1 \
--bundle "${f}.cosign.bundle" \
--tlog-upload=true \
"$f" >/dev/null
done
echo "attest-release-assets.sh: done"

32
scripts/ci/setup-cosign.sh Executable file
View File

@@ -0,0 +1,32 @@
#!/bin/sh
# Install cosign from GitHub releases with SHA256 verification.
# Usage: setup-cosign.sh [version]
set -eu
COSIGN_VERSION="${1:-3.0.5}"
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) ARCH="amd64" ;;
aarch64) ARCH="arm64" ;;
*) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;;
esac
BASE_URL="https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}"
BINARY="cosign-linux-${ARCH}"
CHECKSUMS_URL="${BASE_URL}/cosign_checksums.txt"
curl -fsSL "$CHECKSUMS_URL" -o /tmp/cosign-checksums.txt
curl -fsSL "${BASE_URL}/${BINARY}" -o /tmp/cosign
EXPECTED="$(grep " ${BINARY}\$" /tmp/cosign-checksums.txt | awk '{print $1}')"
ACTUAL="$(sha256sum /tmp/cosign | awk '{print $1}')"
if [ -z "$EXPECTED" ] || [ "$EXPECTED" != "$ACTUAL" ]; then
echo "SHA256 verification failed for ${BINARY}" >&2
rm -f /tmp/cosign /tmp/cosign-checksums.txt
exit 1
fi
sudo install -m 0755 /tmp/cosign /usr/local/bin/cosign
rm -f /tmp/cosign /tmp/cosign-checksums.txt
cosign version

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
"""Emit a SLSA v1 provenance predicate JSON on stdout (stdin unused)."""
from __future__ import annotations
import json
import os
from datetime import datetime, timezone
def _source_uri() -> str:
server = (os.environ.get("GITHUB_SERVER_URL") or os.environ.get("GITEA_SERVER_URL") or "").rstrip("/")
repo = os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or ""
if not server or not repo:
return ""
if server.startswith("https://") or server.startswith("http://"):
return f"git+{server}/{repo}.git"
return f"git+https://{server}/{repo}.git"
def _build_type() -> str:
custom = os.environ.get("PROVENANCE_BUILD_TYPE")
if custom:
return custom
server = (os.environ.get("GITHUB_SERVER_URL") or os.environ.get("GITEA_SERVER_URL") or "").rstrip("/")
repo = os.environ.get("GITHUB_REPOSITORY") or os.environ.get("GITEA_REPOSITORY") or ""
if server and repo:
return f"{server}/{repo}/.gitea/workflows/build.yml"
return "https://slsa.dev/provenance/v1"
def _builder_id() -> str:
custom = os.environ.get("PROVENANCE_BUILDER_ID")
if custom:
return custom
server = (os.environ.get("GITHUB_SERVER_URL") or os.environ.get("GITEA_SERVER_URL") or "").rstrip("/")
if server:
return f"{server}/actions"
return "https://gitea.io/actions/runner"
def main() -> None:
ref = os.environ.get("GITHUB_REF", "")
sha = os.environ.get("GITHUB_SHA", "")
run_id = os.environ.get("GITHUB_RUN_ID", "")
attempt = os.environ.get("GITHUB_RUN_ATTEMPT", "1")
workflow = os.environ.get("GITHUB_WORKFLOW", "")
started = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
internal = {}
if workflow:
internal["workflow"] = workflow
predicate = {
"buildDefinition": {
"buildType": _build_type(),
"externalParameters": {
"source": _source_uri(),
"ref": ref,
"revision": sha,
},
"internalParameters": internal,
"resolvedDependencies": [],
},
"runDetails": {
"builder": {"id": _builder_id()},
"metadata": {
"invocationId": f"{run_id}-{attempt}",
"startedOn": started,
},
},
}
print(json.dumps(predicate, separators=(",", ":")))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Verify a cosign SLSA bundle for a release binary using the repository public key.
# Checks Sigstore Rekor (public log) unless COSIGN_REKOR_URL points elsewhere.
# Usage: verify-release-attestation.sh <blob-file> <bundle-file>
# Env: COSIGN_PUBLIC_KEY (default cosign.pub)
set -eu
BLOB="${1:?blob path}"
BUNDLE="${2:?bundle path}"
PUB="${COSIGN_PUBLIC_KEY:-cosign.pub}"
if [ ! -f "$PUB" ]; then
echo "Missing $PUB (generate a key pair with cosign and commit the .pub file)" >&2
exit 1
fi
exec cosign verify-blob-attestation \
--key "$PUB" \
--bundle "$BUNDLE" \
--type slsaprovenance1 \
"$BLOB"