#!/usr/bin/env nu # rotate_keys/run.nu — validate, propose, sign, activate, verify, revoke def main [ --workspace: string = "" --key-type: string = "" --new-key-path: string = "" --new-pubkey-path: string = "" --ops-vm-host: string = "" --dry-run ]: nothing -> nothing { let ws = if ($workspace | is-not-empty) { $workspace } else { $env.PLAYBOOK_PARAM_WORKSPACE? | default "" } let ktype = if ($key_type | is-not-empty) { $key_type } else { $env.PLAYBOOK_PARAM_KEY_TYPE? | default "" } let new_key = if ($new_key_path | is-not-empty) { $new_key_path } else { $env.PLAYBOOK_PARAM_NEW_KEY_PATH? | default "" } let new_pubkey = if ($new_pubkey_path | is-not-empty) { $new_pubkey_path } else { $env.PLAYBOOK_PARAM_NEW_PUBKEY_PATH? | default "" } let vm_host = if ($ops_vm_host | is-not-empty) { $ops_vm_host } else { $env.PLAYBOOK_PARAM_OPS_VM_HOST? | default "" } if ($ws | is-empty) { error make { msg: "workspace is required" } } if ($ktype | is-empty) { error make { msg: "key-type is required (ops-controller | keeper | operator)" } } if ($new_key | is-empty) { error make { msg: "new-key-path is required" } } let valid_types = ["ops-controller", "keeper", "operator"] if not ($valid_types | any { |t| $t == $ktype }) { error make { msg: $"Invalid key-type '($ktype)'. Must be one of: ($valid_types | str join ', ')" } } let playbook_dir = $env.PLAYBOOK_DIR? | default ($env.FILE_PWD | path expand) let dry_flag = if $dry_run { "--dry-run" } else { "" } print $"[rotate_keys] workspace=($ws) key-type=($ktype) dry-run=($dry_run)" print "" print "[step 1/7] validating new keypair..." let r1 = do { ^nu ($playbook_dir | path join "steps/validate_new_key.nu") --key-type $ktype --new-key-path $new_key --new-pubkey-path $new_pubkey $dry_flag } | complete if $r1.exit_code != 0 { error make { msg: $"Key validation failed: ($r1.stderr | str trim)" } } print $r1.stdout print "[step 2/7] proposing delegation patch..." let r2 = do { ^nu ($playbook_dir | path join "steps/propose_delegation_patch.nu") --workspace $ws --key-type $ktype --new-pubkey-path $new_pubkey $dry_flag } | complete if $r2.exit_code != 0 { error make { msg: $"Delegation patch proposal failed: ($r2.stderr | str trim)" } } print $r2.stdout print "[step 3/7] awaiting quorum signatures on delegation patch..." let r3 = do { ^nu ($playbook_dir | path join "steps/sign_delegation_patch.nu") --workspace $ws --key-type $ktype $dry_flag } | complete if $r3.exit_code != 0 { error make { msg: $"Delegation patch signing failed: ($r3.stderr | str trim)" } } print $r3.stdout print "[step 4/7] activating new key..." let r4 = do { ^nu ($playbook_dir | path join "steps/activate_new_key.nu") --workspace $ws --key-type $ktype --new-key-path $new_key --ops-vm-host $vm_host $dry_flag } | complete if $r4.exit_code != 0 { print $"ERROR: key activation failed — initiating rollback: ($r4.stderr | str trim)" print "ROLLBACK REQUIRED: restore old key from backup and re-run propose/sign/activate cycle" error make { msg: "activate_new_key failed — manual rollback required" } } print $r4.stdout print "[step 5/7] verifying continuity..." let r5 = do { ^nu ($playbook_dir | path join "steps/verify_continuity.nu") --workspace $ws --key-type $ktype $dry_flag } | complete if $r5.exit_code != 0 { print $" WARNING: continuity verification failed: ($r5.stderr | str trim)" } else { print $r5.stdout } print "[step 6/7] revoking old key..." let r6 = do { ^nu ($playbook_dir | path join "steps/revoke_old_key.nu") --workspace $ws --key-type $ktype $dry_flag } | complete if $r6.exit_code != 0 { print $" WARNING: old key revocation reported errors: ($r6.stderr | str trim)" } else { print $r6.stdout } print "[step 7/7] emitting key-rotation audit event..." let r7 = do { ^nu ($playbook_dir | path join "steps/emit_audit.nu") --workspace $ws --event-type "key_rotation" --key-type $ktype $dry_flag } | complete if $r7.exit_code != 0 { print $" WARNING: audit emit failed: ($r7.stderr | str trim)" } print "" print "[rotate_keys] complete" print $"Verify: new ($ktype) key is active, old key rejected by target service" }