provisioning-catalog/playbooks/rotate_keys/playbook.ncl

96 lines
3.8 KiB
Text

let pb = import "schemas/lib/playbook.ncl" in
pb.make_playbook {
id = "rotate_keys",
name = "Key Rotation",
description = "Rotates ops-controller key, keeper key, or operator key. For ops-controller: generates new key, proposes delegation patch on state-<workspace> signed by quorum, activates new key, verifies continuity. Keeper and operator key rotation follow the same quorum-sign pattern.",
version = 1,
preconditions = [
"Identify which key type to rotate: ops-controller | keeper | operator",
"Ensure quorum of existing signers is available to sign the delegation patch",
"For ops-controller rotation: audit mirror must be paused or confirmed resilient to a 30-60s gap",
"Generate the new key offline before running this playbook",
],
params = [
{ name = "workspace", description = "Target workspace (e.g. libre-wuji)", required = true, default_val = "" },
{ name = "key_type", description = "Which key to rotate: ops-controller | keeper | operator", required = true, default_val = "" },
{ name = "new_key_path", description = "Path to the new Ed25519 private key PEM", required = true, default_val = "" },
{ name = "new_pubkey_path", description = "Path to the new Ed25519 public key PEM", required = true, default_val = "" },
{ name = "ops_vm_host", description = "ops-vm SSH host (required for keeper rotation)", required = false, default_val = "" },
],
steps = [
{
id = "validate_new_key",
name = "Validate new key is a valid Ed25519 keypair",
script = "steps/validate_new_key.nu",
dry_run_arg = "--dry-run",
on_error = 'Stop,
depends_on = [],
},
{
id = "propose_delegation_patch",
name = "Propose delegation patch on state-<workspace> repo",
script = "steps/propose_delegation_patch.nu",
dry_run_arg = "--dry-run",
on_error = 'Stop,
depends_on = ["validate_new_key"],
},
{
id = "sign_delegation_patch",
name = "Await quorum signatures on delegation patch",
script = "steps/sign_delegation_patch.nu",
dry_run_arg = "--dry-run",
on_error = 'Stop,
depends_on = ["propose_delegation_patch"],
},
{
id = "activate_new_key",
name = "Activate new key (deploy to target service)",
script = "steps/activate_new_key.nu",
dry_run_arg = "--dry-run",
on_error = 'Rollback,
depends_on = ["sign_delegation_patch"],
},
{
id = "verify_continuity",
name = "Verify audit/signing continuity with new key",
script = "steps/verify_continuity.nu",
dry_run_arg = "--dry-run",
on_error = 'Continue,
depends_on = ["activate_new_key"],
},
{
id = "revoke_old_key",
name = "Revoke old key from delegation set",
script = "steps/revoke_old_key.nu",
dry_run_arg = "--dry-run",
on_error = 'Continue,
depends_on = ["verify_continuity"],
},
{
id = "emit_audit",
name = "Emit key-rotation audit event",
script = "steps/emit_audit.nu",
params = { event_type = "key_rotation" },
dry_run_arg = "--dry-run",
on_error = 'Continue,
depends_on = ["revoke_old_key"],
},
],
rollback_strategy = 'manual,
success_criteria = [
"New key is active and accepted by the target service",
"Delegation patch is merged in state-<workspace> Radicle repo",
"Old key is removed from delegation set",
"ops history shows key-rotation audit event",
"No interruption in audit trail during rotation",
],
emit_audit = true,
adr_refs = ["adr-037", "adr-038"],
}