feat(compat): sign seeded-ci.json with cosign (Phase 4.1)
All checks were successful
addon-qualify / qualify (push) Successful in 12s
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:
@@ -98,13 +98,51 @@ jobs:
|
||||
print(f'merged {len(rows)} rows; stamp={stamp}')
|
||||
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
|
||||
env:
|
||||
GIT_USER_TOKEN: ${{ secrets.COMPAT_PUSH_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# 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
|
||||
rm -rf compat-bootstrap/per-major
|
||||
|
||||
@@ -116,12 +154,11 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stage first, then diff against index — this catches both
|
||||
# "tracked file changed" and "new file appeared" cases. The
|
||||
# bare `git diff --quiet` form silently passes for untracked
|
||||
# files (no diff = exit 0), which masked the very-first run.
|
||||
git add compat-bootstrap/seeded-ci.json
|
||||
if git diff --cached --quiet -- compat-bootstrap/seeded-ci.json; then
|
||||
# Stage both the JSON and its sig. The .sig changes whenever
|
||||
# the JSON changes (different SHA256 → different signature),
|
||||
# so a content-stable run will produce a no-op diff for both.
|
||||
git add compat-bootstrap/seeded-ci.json compat-bootstrap/seeded-ci.json.sig
|
||||
if git diff --cached --quiet -- compat-bootstrap/seeded-ci.json compat-bootstrap/seeded-ci.json.sig; then
|
||||
echo "no content change in seeded-ci.json; nothing to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user