CD/CD consolidation
This commit is contained in:
116
devops/tools/mirror/ci-sign.sh
Normal file
116
devops/tools/mirror/ci-sign.sh
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# Allow CI to fall back to a deterministic test key when MIRROR_SIGN_KEY_B64 is unset,
|
||||
# but forbid this on release/tag builds when REQUIRE_PROD_SIGNING=1.
|
||||
# Throwaway dev key (Ed25519) generated 2025-11-23; matches the value documented in
|
||||
# docs/modules/mirror/signing-runbook.md. Safe for non-production smoke only.
|
||||
DEFAULT_TEST_KEY_B64="LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1DNENBUUF3QlFZREsyVndCQ0lFSURqb3pDRVdKVVFUdW1xZ2gyRmZXcVBaemlQbkdaSzRvOFZRTThGYkZCSEcKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo="
|
||||
if [[ -z "${MIRROR_SIGN_KEY_B64:-}" ]]; then
|
||||
if [[ "${REQUIRE_PROD_SIGNING:-0}" == "1" ]]; then
|
||||
echo "[error] MIRROR_SIGN_KEY_B64 is required for production signing; refusing to use test key." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[warn] MIRROR_SIGN_KEY_B64 not set; using embedded test key (non-production) for CI signing" >&2
|
||||
MIRROR_SIGN_KEY_B64="$DEFAULT_TEST_KEY_B64"
|
||||
fi
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
KEYDIR="$ROOT/out/mirror/thin/tuf/keys"
|
||||
mkdir -p "$KEYDIR"
|
||||
KEYFILE="$KEYDIR/ci-ed25519.pem"
|
||||
printf "%s" "$MIRROR_SIGN_KEY_B64" | base64 -d > "$KEYFILE"
|
||||
chmod 600 "$KEYFILE"
|
||||
# Export public key for TUF keyid calculation
|
||||
openssl pkey -in "$KEYFILE" -pubout -out "$KEYDIR/ci-ed25519.pub" >/dev/null 2>&1
|
||||
STAGE=${STAGE:-$ROOT/out/mirror/thin/stage-v1}
|
||||
CREATED=${CREATED:-$(date -u +%Y-%m-%dT%H:%M:%SZ)}
|
||||
TENANT_SCOPE=${TENANT_SCOPE:-tenant-demo}
|
||||
ENV_SCOPE=${ENV_SCOPE:-lab}
|
||||
CHUNK_SIZE=${CHUNK_SIZE:-5242880}
|
||||
CHECKPOINT_FRESHNESS=${CHECKPOINT_FRESHNESS:-86400}
|
||||
OCI=${OCI:-1}
|
||||
SIGN_KEY="$KEYFILE" STAGE="$STAGE" CREATED="$CREATED" TENANT_SCOPE="$TENANT_SCOPE" ENV_SCOPE="$ENV_SCOPE" CHUNK_SIZE="$CHUNK_SIZE" CHECKPOINT_FRESHNESS="$CHECKPOINT_FRESHNESS" OCI="$OCI" "$ROOT/src/Mirror/StellaOps.Mirror.Creator/make-thin-v1.sh"
|
||||
|
||||
# Default to staged time-anchor unless caller overrides
|
||||
TIME_ANCHOR_FILE=${TIME_ANCHOR_FILE:-$ROOT/out/mirror/thin/stage-v1/layers/time-anchor.json}
|
||||
|
||||
# Emit milestone summary with hashes for downstream consumers
|
||||
MANIFEST_PATH="$ROOT/out/mirror/thin/mirror-thin-v1.manifest.json"
|
||||
TAR_PATH="$ROOT/out/mirror/thin/mirror-thin-v1.tar.gz"
|
||||
DSSE_PATH="$ROOT/out/mirror/thin/mirror-thin-v1.manifest.dsse.json"
|
||||
BUNDLE_PATH="$ROOT/out/mirror/thin/mirror-thin-v1.bundle.json"
|
||||
BUNDLE_DSSE_PATH="$ROOT/out/mirror/thin/mirror-thin-v1.bundle.dsse.json"
|
||||
TIME_ANCHOR_DSSE_PATH="$TIME_ANCHOR_FILE.dsse.json"
|
||||
TRANSPORT_PATH="$ROOT/out/mirror/thin/stage-v1/layers/transport-plan.json"
|
||||
REKOR_POLICY_PATH="$ROOT/out/mirror/thin/stage-v1/layers/rekor-policy.json"
|
||||
MIRROR_POLICY_PATH="$ROOT/out/mirror/thin/stage-v1/layers/mirror-policy.json"
|
||||
OFFLINE_POLICY_PATH="$ROOT/out/mirror/thin/stage-v1/layers/offline-kit-policy.json"
|
||||
SUMMARY_PATH="$ROOT/out/mirror/thin/milestone.json"
|
||||
|
||||
sha256() {
|
||||
sha256sum "$1" | awk '{print $1}'
|
||||
}
|
||||
|
||||
# Sign manifest, bundle meta, and time-anchor (if present)
|
||||
python "$ROOT/scripts/mirror/sign_thin_bundle.py" \
|
||||
--key "$KEYFILE" \
|
||||
--manifest "$MANIFEST_PATH" \
|
||||
--tar "$TAR_PATH" \
|
||||
--tuf-dir "$ROOT/out/mirror/thin/tuf" \
|
||||
--bundle "$BUNDLE_PATH" \
|
||||
--time-anchor "$TIME_ANCHOR_FILE"
|
||||
|
||||
# Normalize time-anchor DSSE location for bundle meta/summary
|
||||
if [[ -f "$TIME_ANCHOR_FILE.dsse.json" ]]; then
|
||||
cp "$TIME_ANCHOR_FILE.dsse.json" "$TIME_ANCHOR_DSSE_PATH"
|
||||
fi
|
||||
|
||||
# Refresh bundle meta hashes now that DSSE files exist
|
||||
python - <<'PY'
|
||||
import json, pathlib, hashlib
|
||||
root = pathlib.Path("$ROOT")
|
||||
bundle_path = pathlib.Path("$BUNDLE_PATH")
|
||||
manifest_dsse = pathlib.Path("$DSSE_PATH")
|
||||
bundle_dsse = pathlib.Path("$BUNDLE_DSSE_PATH")
|
||||
time_anchor_dsse = pathlib.Path("$TIME_ANCHOR_DSSE_PATH")
|
||||
|
||||
def sha(path: pathlib.Path) -> str:
|
||||
h = hashlib.sha256()
|
||||
with path.open('rb') as f:
|
||||
for chunk in iter(lambda: f.read(8192), b''):
|
||||
h.update(chunk)
|
||||
return h.hexdigest()
|
||||
|
||||
data = json.loads(bundle_path.read_text())
|
||||
art = data.setdefault('artifacts', {})
|
||||
if manifest_dsse.exists():
|
||||
art.setdefault('manifest_dsse', {})['sha256'] = sha(manifest_dsse)
|
||||
if bundle_dsse.exists():
|
||||
art.setdefault('bundle_dsse', {})['sha256'] = sha(bundle_dsse)
|
||||
if time_anchor_dsse.exists():
|
||||
art.setdefault('time_anchor_dsse', {})['sha256'] = sha(time_anchor_dsse)
|
||||
|
||||
bundle_path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n")
|
||||
sha_path = bundle_path.with_suffix(bundle_path.suffix + '.sha256')
|
||||
sha_path.write_text(f"{sha(bundle_path)} {bundle_path.name}\n")
|
||||
PY
|
||||
|
||||
cat > "$SUMMARY_PATH" <<JSON
|
||||
{
|
||||
"created": "$CREATED",
|
||||
"manifest": {"path": "$(basename "$MANIFEST_PATH")", "sha256": "$(sha256 "$MANIFEST_PATH")"},
|
||||
"tarball": {"path": "$(basename "$TAR_PATH")", "sha256": "$(sha256 "$TAR_PATH")"},
|
||||
"dsse": $( [[ -f "$DSSE_PATH" ]] && echo "{\"path\": \"$(basename "$DSSE_PATH")\", \"sha256\": \"$(sha256 "$DSSE_PATH")\"}" || echo "null" ),
|
||||
"bundle": $( [[ -f "$BUNDLE_PATH" ]] && echo "{\"path\": \"$(basename "$BUNDLE_PATH")\", \"sha256\": \"$(sha256 "$BUNDLE_PATH")\"}" || echo "null" ),
|
||||
"bundle_dsse": $( [[ -f "$BUNDLE_DSSE_PATH" ]] && echo "{\"path\": \"$(basename "$BUNDLE_DSSE_PATH")\", \"sha256\": \"$(sha256 "$BUNDLE_DSSE_PATH")\"}" || echo "null" ),
|
||||
"time_anchor": $( [[ -n "${TIME_ANCHOR_FILE:-}" && -f "$TIME_ANCHOR_FILE" ]] && echo "{\"path\": \"$(basename "$TIME_ANCHOR_FILE")\", \"sha256\": \"$(sha256 "$TIME_ANCHOR_FILE")\"}" || echo "null" ),
|
||||
"time_anchor_dsse": $( [[ -f "$TIME_ANCHOR_DSSE_PATH" ]] && echo "{\"path\": \"$(basename "$TIME_ANCHOR_DSSE_PATH")\", \"sha256\": \"$(sha256 "$TIME_ANCHOR_DSSE_PATH")\"}" || echo "null" )
|
||||
,"policies": {
|
||||
"transport": {"path": "$(basename "$TRANSPORT_PATH")", "sha256": "$(sha256 "$TRANSPORT_PATH")"},
|
||||
"rekor": {"path": "$(basename "$REKOR_POLICY_PATH")", "sha256": "$(sha256 "$REKOR_POLICY_PATH")"},
|
||||
"mirror": {"path": "$(basename "$MIRROR_POLICY_PATH")", "sha256": "$(sha256 "$MIRROR_POLICY_PATH")"},
|
||||
"offline": {"path": "$(basename "$OFFLINE_POLICY_PATH")", "sha256": "$(sha256 "$OFFLINE_POLICY_PATH")"}
|
||||
}
|
||||
}
|
||||
JSON
|
||||
|
||||
echo "Milestone summary written to $SUMMARY_PATH"
|
||||
Reference in New Issue
Block a user