Runbook: Config-pack signing key¶
Config packs are JSON manifests signed with an Ed25519 key. The engine verifies
the signature against a trusted public key before merging a pack into a
local mask-rules.yaml. The engine ships no embedded key — the trust anchor is
provisioned at release time and injected at runtime via the
PRIVACI_PACK_PUBLIC_KEY environment variable. This runbook covers generating,
shipping, and rotating that key.
Why there is no built-in key¶
A public key hardcoded in the source would let anyone holding the matching
private key forge packs the engine trusts. Keeping the anchor out of source and
injecting it at runtime means a leaked release artifact cannot be used to sign
malicious packs, and the key can be rotated without a code change.
scripts/check_pack_key.py fails CI/release if any 32-byte key literal appears
in src/privaci/packs/keys.py.
Generate the keypair¶
python - <<'PY'
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
private = Ed25519PrivateKey.generate()
public = private.public_key()
priv_bytes = private.private_bytes(
serialization.Encoding.Raw,
serialization.PrivateFormat.Raw,
serialization.NoEncryption(),
)
pub_bytes = public.public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
print("PRIVATE_KEY_HEX:", priv_bytes.hex()) # store in release secrets only
print("PUBLIC_KEY_HEX :", pub_bytes.hex()) # publish in release notes
PY
Current published key¶
| Released | Public key (hex) |
|---|---|
v0.1.0-beta.1+ |
cd965cb6dadcecefd508ae84a000684f431490c3d3ddae006ad5f89bf2c25978 |
Operators set this as PRIVACI_PACK_PUBLIC_KEY. Update this table on each
rotation (see Rotate the key).
Provision for a release¶
Generate a keypair with the helper script (or the inline snippet below):
python scripts/generate_pack_signing_key.py
- Store
PRIVATE_KEY_HEXin the release secret store (GitHub Actions secretPACK_SIGNING_PRIVATE_KEY). Never commit it. - Publish
PUBLIC_KEY_HEXin the GitHub release notes and in operator docs. - Configure runtimes to export the public key:
export PRIVACI_PACK_PUBLIC_KEY="<PUBLIC_KEY_HEX>"
For containers, set it as an environment variable in the deployment (Helm
values.yaml, ECS task definition, etc.). It is not a secret.
Sign a pack manifest¶
The signing pipeline computes the signature over the canonical JSON (all fields
except signature, sort_keys=True, compact separators — see
privaci.packs.verify.canonical_payload) and base64-encodes it into the
signature field.
Verify behavior¶
| Situation | Result |
|---|---|
PRIVACI_PACK_PUBLIC_KEY unset or invalid |
install-pack exits 3, no files modified |
| Key set, signature invalid/tampered | Exits 3, no files modified |
| Key set, signature valid | Merge proceeds after preview/confirmation |
Rotate the key¶
- Generate a new keypair (above).
- Re-sign all current packs with the new private key.
- Publish the new public key and update runtime
PRIVACI_PACK_PUBLIC_KEY. - Retire the old private key from the secret store.
Because the anchor is injected, rotation needs no engine release.