Packaging + provenance for the plugin marketplace. No runtime behavior change yet - verification is available on demand; enforcing it at plugin load/migrate and pulling from a shelf are Phase 2. - signing.py: ed25519 key pairs + provenance. Provenance is a sorted per-file SHA-256 map plus metadata; the detached signature covers the exact serialized provenance bytes, so verifying is re-hash files, re-serialize, check signature. verify() accepts any of several trusted keys (rotation). Uses cryptography (already a dependency). - packaging.py: pack() builds a signed <name>-<version>.shopdbplugin (zip + PROVENANCE.json + PROVENANCE.sig). verify_artifact()/verify_dir() re-hash and check the signature, and flag a tampered file, an unexpected file, a wrong/absent key - all fail closed. - CLI: `flask plugin keygen` (publisher key pair), `flask plugin pack <name> --key` (validates then signs), and `flask plugin validate` extended to a signed artifact by path (--pubkey, else PLUGIN_TRUSTED_KEYS). - config PLUGIN_TRUSTED_KEYS: os.pathsep-separated public-key PEM paths, delivered with the site config, never read from the shelf. .env.example documents it. - docs/PLUGIN-SIGNING.md: curator flow (keygen offline, review, pack, publish, pin keys, rotate). The signature proves an artifact is exactly what a curator signed, not that the code is safe - human review before signing is the control. 11 tests: sign/verify round trip, wrong key, provenance excludes noise, serialize determinism, pack + verify, tamper -> hash mismatch, extra file, no-key fail-closed, verify_dir. 1028 pass, naming green.
76 lines
3.0 KiB
Markdown
76 lines
3.0 KiB
Markdown
# Plugin signing and packaging (curator guide)
|
|
|
|
ADR-013 Phase 1. How a plugin becomes a signed, verifiable artifact and how a
|
|
site trusts it. The signature proves an artifact is EXACTLY what a curator
|
|
reviewed and signed - it does not prove the code is safe. Human review before
|
|
signing is the actual safety control; the signature makes that review's verdict
|
|
tamper-evident all the way to the point of execution.
|
|
|
|
Requires the `cryptography` package (already a dependency).
|
|
|
|
## One-time: create the publisher key pair
|
|
|
|
```
|
|
flask plugin keygen --out ./keys --name curator
|
|
```
|
|
|
|
Writes `keys/curator.key` (PRIVATE) and `keys/curator.pub` (public).
|
|
|
|
- Keep the `.key` OFFLINE with the curator. It is the only thing that can sign a
|
|
trusted artifact. Never put it on the plugin shelf or in the repo.
|
|
- Distribute the `.pub` with each site's deployed config and pin it (below).
|
|
- Rotation: generate a new pair, pin BOTH public keys on sites for an overlap
|
|
window (`verify` accepts any trusted key), then retire the old one.
|
|
|
|
## Per plugin: review, then pack
|
|
|
|
1. Review the plugin's source. This is the security gate - read what it does.
|
|
2. Validate and package in one step:
|
|
|
|
```
|
|
flask plugin pack printers --key ./keys/curator.key --publisher west-jefferson
|
|
```
|
|
|
|
`pack` refuses to sign a directory that does not validate (manifest schema,
|
|
name/dir match, core_version, dependencies on disk). On success it writes
|
|
`printers-<version>.shopdbplugin` - a zip of the plugin plus:
|
|
|
|
- `PROVENANCE.json`: name, version, publisher, created, and a sorted
|
|
`{file: sha256}` map of every packaged file.
|
|
- `PROVENANCE.sig`: a detached ed25519 signature over the exact
|
|
`PROVENANCE.json` bytes.
|
|
|
|
3. Publish the artifact to the shelf (a SharePoint-synced or copied folder).
|
|
Transport is untrusted; the signature is what makes it safe.
|
|
|
|
## Verify an artifact
|
|
|
|
```
|
|
flask plugin validate dist/printers-1.0.0.shopdbplugin --pubkey ./keys/curator.pub
|
|
```
|
|
|
|
Checks, fail-closed: signature against the trusted key(s), every file's hash,
|
|
no unexpected files, manifest schema, and that the plugin's `core_version`
|
|
admits this framework's contract version. Any changed byte in any file fails
|
|
the hash check; a signature from an untrusted key fails the signature check.
|
|
|
|
## Pin trusted keys on a site
|
|
|
|
Set `PLUGIN_TRUSTED_KEYS` to one or more public-key PEM paths, separated by the
|
|
OS path separator (`:` on Linux, `;` on Windows), in the site's environment:
|
|
|
|
```
|
|
PLUGIN_TRUSTED_KEYS=/etc/shopdb/keys/curator.pub:/etc/shopdb/keys/curator-next.pub
|
|
```
|
|
|
|
Keys are read only from this deployed config, never from the shelf - a folder an
|
|
attacker could write must not also carry the keys that authenticate it. With no
|
|
keys set, `validate` on an artifact fails closed (unverifiable).
|
|
|
|
## What is NOT in this phase
|
|
|
|
Verification is available but not yet enforced automatically at plugin load or
|
|
migrate time, and there is no `adopt` command that pulls from a shelf yet. Those
|
|
land in Phase 2 (verify-at-load, verify-at-migrate, the signed shelf index).
|
|
For now, verify artifacts manually with `validate` before installing.
|