ci: sign plugins-index.json in-repo, drop worker signing dependency
Pair with the worker-side simplification on the librefang PR — the worker is now a pure transport (no key material, no signing) and this repo's CI takes over signature production. scripts/sign-plugins-index.mjs reads REGISTRY_PRIVATE_KEY from a GitHub Actions secret, signs plugins-index.json with Ed25519, and writes plugins-index.json.sig alongside it. Aborts loudly when the secret is missing so a misconfigured CI can't silently ship an unsigned payload. The workflow now runs build → sign → commit (.json + .sig) → push → poke worker /refresh. The worker fetches the committed .json + .sig verbatim and stores both — the daemon then verifies against the embedded pubkey it ships with. Closes PR review CRITICAL #1: the worker is no longer a sign-anything oracle reachable via REGISTRY_REFRESH_TOKEN. Trust root is now this repo's branch protection + Actions secret scope, not a token any CI job that can talk to stats.librefang.ai can use to mint signatures. Note: the keypair was rotated as part of this change (PR not yet merged so no daemon TOFU pins exist). New pubkey: ClGa0Ucap8NdrKAy1rw9Tt6A9I8eg4zJ53+xIuKMuq0= The plugins-index.json.sig committed here is signed with the matching new private key, in lockstep with the daemon EMBEDDED_REGISTRY_PUBKEY constant and all three worker [vars] entries.
This commit is contained in:
1 parent
ff6f3f2b25
commit
74745f1f20
3 files changed
+79
-1
No files matched your search
@@ -45,11 +45,16 @@ jobs:
|
|||||||
node scripts/build-plugins-index.mjs
|
node scripts/build-plugins-index.mjs
|
||||||
node scripts/build-registry-index.mjs
|
node scripts/build-registry-index.mjs
|
||||||
|
|
||||||
|
- name: Sign plugins-index.json
|
||||||
|
env:
|
||||||
|
REGISTRY_PRIVATE_KEY: ${{ secrets.REGISTRY_PRIVATE_KEY }}
|
||||||
|
run: node scripts/sign-plugins-index.mjs
|
||||||
|
|
||||||
- name: Commit regenerated indexes if changed
|
- name: Commit regenerated indexes if changed
|
||||||
run: |
|
run: |
|
||||||
git config user.name "github-actions[bot]"
|
git config user.name "github-actions[bot]"
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
git add plugins-index.json registry-index.json
|
git add plugins-index.json plugins-index.json.sig registry-index.json
|
||||||
if git diff --cached --quiet; then
|
if git diff --cached --quiet; then
|
||||||
echo "indexes already up-to-date"
|
echo "indexes already up-to-date"
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
efERqfz7oGh0AoBrnVKocOUjOUR5h3a8vkIcvqd+HP+k3Rr99diJsExKwXAogfz4TpTWPn0rqLjVgQpP8MxXBw==
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Sign plugins-index.json with the registry's Ed25519 private key and
|
||||||
|
// commit the .sig alongside the JSON. Pair with build-plugins-index.mjs
|
||||||
|
// (which produces the bytes) — this script's only job is to attach a
|
||||||
|
// detached signature.
|
||||||
|
//
|
||||||
|
// Run by .github/workflows/refresh-cache.yml. Reads the PKCS#8-base64
|
||||||
|
// private key from the REGISTRY_PRIVATE_KEY env var (set as a GitHub
|
||||||
|
// Actions repo secret); aborts loudly if the secret is missing or
|
||||||
|
// malformed so a misconfigured CI run can't silently ship an unsigned
|
||||||
|
// index.
|
||||||
|
//
|
||||||
|
// PR #4600 review CRITICAL #1: signing now happens here, not in the
|
||||||
|
// Cloudflare worker. The worker never holds the private key — it just
|
||||||
|
// transports the bytes + signature this script produces. That ties the
|
||||||
|
// trust root to the registry repo's branch protection + Actions secret
|
||||||
|
// scope, instead of the worker being a sign-anything oracle reachable
|
||||||
|
// via REGISTRY_REFRESH_TOKEN.
|
||||||
|
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import crypto from 'node:crypto'
|
||||||
|
|
||||||
|
const repoRoot = path.resolve(new URL('.', import.meta.url).pathname, '..')
|
||||||
|
const indexPath = path.join(repoRoot, 'plugins-index.json')
|
||||||
|
const sigPath = path.join(repoRoot, 'plugins-index.json.sig')
|
||||||
|
|
||||||
|
const pkcs8B64 = (process.env.REGISTRY_PRIVATE_KEY || '').trim()
|
||||||
|
if (!pkcs8B64) {
|
||||||
|
console.error(
|
||||||
|
'REGISTRY_PRIVATE_KEY is not set — refusing to ship unsigned index. ' +
|
||||||
|
'Add the PKCS#8-base64 Ed25519 private key as a GitHub Actions secret ' +
|
||||||
|
'on this repo (gh secret set REGISTRY_PRIVATE_KEY).',
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(indexPath)) {
|
||||||
|
console.error(
|
||||||
|
`plugins-index.json missing at ${indexPath} — run scripts/build-plugins-index.mjs first.`,
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pkcs8Der = Buffer.from(pkcs8B64.replace(/\s+/g, ''), 'base64')
|
||||||
|
let key
|
||||||
|
try {
|
||||||
|
key = crypto.createPrivateKey({ key: pkcs8Der, format: 'der', type: 'pkcs8' })
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`REGISTRY_PRIVATE_KEY is not a valid PKCS#8-DER Ed25519 key: ${e.message}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
if (key.asymmetricKeyType !== 'ed25519') {
|
||||||
|
console.error(
|
||||||
|
`REGISTRY_PRIVATE_KEY is a ${key.asymmetricKeyType} key — must be ed25519`,
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexBytes = fs.readFileSync(indexPath)
|
||||||
|
const sigBytes = crypto.sign(null, indexBytes, key)
|
||||||
|
const sigB64 = sigBytes.toString('base64')
|
||||||
|
|
||||||
|
const prev = fs.existsSync(sigPath) ? fs.readFileSync(sigPath, 'utf8').trim() : null
|
||||||
|
if (prev === sigB64) {
|
||||||
|
console.log(`plugins-index.json.sig unchanged (${sigBytes.length} bytes)`)
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
fs.writeFileSync(sigPath, sigB64 + '\n')
|
||||||
|
console.log(
|
||||||
|
`plugins-index.json.sig written: ${sigBytes.length} bytes signature ` +
|
||||||
|
`over ${indexBytes.length} bytes of index`,
|
||||||
|
)
|
||||||
Reference in new issue
Block a user