feat(compat): sign seeded-ci.json with cosign (Phase 4.1)
All checks were successful
addon-qualify / qualify (push) Successful in 12s

Adds cosign install + sign-blob step before commit. The detached
.sig (base64-encoded ASN.1 DER ECDSA over SHA256(file)) is committed
alongside seeded-ci.json. Tower's loader verifies it pure-Go before
replay; mismatched/missing sig → refuse + log.

cosign.pub is also checked in so the workflow can self-verify before
push (catches key-rotation mismatch early). The same pubkey is
embedded in Tower's binary at compat_bootstrap_pubkey.pem; both
copies must match or replay will fail.
This commit is contained in:
compat-seeder
2026-05-10 16:59:39 +03:00
parent 2dfe7be06c
commit ed0e835863
2 changed files with 48 additions and 7 deletions

View File

@@ -98,13 +98,51 @@ jobs:
print(f'merged {len(rows)} rows; stamp={stamp}') print(f'merged {len(rows)} rows; stamp={stamp}')
PY PY
- name: Install cosign
run: |
set -euo pipefail
if ! command -v cosign >/dev/null 2>&1; then
curl -sSL -o /usr/local/bin/cosign \
https://github.com/sigstore/cosign/releases/download/v2.4.1/cosign-linux-amd64
chmod +x /usr/local/bin/cosign
fi
cosign version | head -3
- name: Sign seeded-ci.json (Phase 4.1)
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COMPAT_SIGNING_KEY }}
COSIGN_PASSWORD: ${{ secrets.COMPAT_SIGNING_PASSWORD }}
run: |
set -euo pipefail
if [ ! -f compat-bootstrap/seeded-ci.json ]; then
echo "ERROR: seeded-ci.json was not produced; nothing to sign"
exit 1
fi
# cosign sign-blob with --key env://VAR reads the private key
# PEM from the named env var; the file/stdout split keeps the
# raw key out of process args (where it'd show up in `ps`).
cosign sign-blob --yes \
--key env://COSIGN_PRIVATE_KEY \
--output-signature compat-bootstrap/seeded-ci.json.sig \
compat-bootstrap/seeded-ci.json
# Sanity: verify with the public copy on disk before we push.
# Catches a key-rotation mismatch while we still can.
# (skips the Sigstore tlog — we're not relying on Rekor)
if [ -f compat-bootstrap/cosign.pub ]; then
cosign verify-blob --insecure-ignore-tlog \
--key compat-bootstrap/cosign.pub \
--signature compat-bootstrap/seeded-ci.json.sig \
compat-bootstrap/seeded-ci.json
fi
echo "signed: $(wc -c < compat-bootstrap/seeded-ci.json.sig) bytes"
- name: Commit + push if content changed - name: Commit + push if content changed
env: env:
GIT_USER_TOKEN: ${{ secrets.COMPAT_PUSH_TOKEN }} GIT_USER_TOKEN: ${{ secrets.COMPAT_PUSH_TOKEN }}
run: | run: |
set -euo pipefail set -euo pipefail
# Discard the per-major scratch files + addon worktrees; only # Discard the per-major scratch files + addon worktrees; only
# the consolidated snapshot is part of the canonical state. # the consolidated snapshot + sig are part of the canonical state.
for major in 18.0 19.0; do git worktree remove --force "addons-$major" || true; done for major in 18.0 19.0; do git worktree remove --force "addons-$major" || true; done
rm -rf compat-bootstrap/per-major rm -rf compat-bootstrap/per-major
@@ -116,12 +154,11 @@ jobs:
exit 1 exit 1
fi fi
# Stage first, then diff against index — this catches both # Stage both the JSON and its sig. The .sig changes whenever
# "tracked file changed" and "new file appeared" cases. The # the JSON changes (different SHA256 → different signature),
# bare `git diff --quiet` form silently passes for untracked # so a content-stable run will produce a no-op diff for both.
# files (no diff = exit 0), which masked the very-first run. git add compat-bootstrap/seeded-ci.json compat-bootstrap/seeded-ci.json.sig
git add compat-bootstrap/seeded-ci.json if git diff --cached --quiet -- compat-bootstrap/seeded-ci.json compat-bootstrap/seeded-ci.json.sig; then
if git diff --cached --quiet -- compat-bootstrap/seeded-ci.json; then
echo "no content change in seeded-ci.json; nothing to commit" echo "no content change in seeded-ci.json; nothing to commit"
exit 0 exit 0
fi fi

View File

@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgbGcCGMzThWEY5aaVK249Q+ZNm1w
BznDxfRvzL9AGdb1vkUngdcVmGXZBwg/rHXSkYJjt4t9Ky9mZkB9pB02BQ==
-----END PUBLIC KEY-----