P0: gate external backup SSH targets during Nexus host-key rotation #4

Closed
opened 2026-07-13 19:01:18 +01:00 by vnprc-agent · 2 comments

User story: So that I can rotate the host SSH identity without breaking backups, the rotation workflow must prove every required external backup target is ready before old access is retired.

Part of the "Host-key rotation lifecycle & safety" arc.


Priority: P0 / maximum.

Problem

The Nexus host SSH/age identity rotation can retire the old ~/.ssh/host key before external backup-critical SSH targets have been updated to trust the new key.

Managed VMs and Forgejo git-over-SSH are checked by the rotation flow, but external SSH targets used by backups are currently manual follow-up. If those steps are missed, the timer can keep firing while backup jobs fail at SSH authentication or offsite upload.

Goal

Make nexus-host-key treat external backup SSH targets as first-class rotation gates. The script should be as automatic as possible and print exact operator commands for any human-only step.

Required behavior

  • During nexus-host-key stage, print the staged public key and exact instructions to authorize it on required external backup targets before activation.
  • During nexus-host-key activate, verify the staged key works against required external backup targets before swapping ~/.ssh/host.
  • During nexus-host-key retire, refuse or strongly gate retirement unless required external backup targets have been proven with the installed new key.
  • Keep human gates where unavoidable, especially:
    • adding a key through a provider console or recovery shell if old-key SSH no longer works;
    • offsite backup provider support/account recovery if neither SSH key nor password auth works;
    • confirming any external host intentionally abandoned before old-key deletion.
  • Print copy/paste-ready commands for each target. Avoid vague instructions like "update external hosts".

Concrete target classes

At minimum cover:

  • the external service VPS used by the Forgejo backup script to create and pull dumps;
  • the offsite SSH backup destination used for encrypted backup upload.

Prefer deriving these from identity/deployment data or a small declarative allowlist, so future external backup-critical hosts cannot be missed. Do not include managed VMs that are already covered by the VM verification loop.

Suggested implementation shape

  • Add an external-host phase to nexus-host-key.
  • Resolve host, user, port, and identity policy from deployment data instead of hardcoding private values in docs.
  • Generate exact append/test commands for each target, for example:
    • old-key path: append staged public key via SSH using the current/old key;
    • provider-console path: one-line echo 'ssh-ed25519 ...' >> ~/.ssh/authorized_keys fallback;
    • offsite backup path: append to .ssh/authorized_keys when SSH/password access is available, otherwise print provider-support request text with the staged public key.
  • Verify each required target with the staged key before activation using ssh -F /dev/null -i <staged-key> -o IdentitiesOnly=yes -o BatchMode=yes ... true or an equivalent target-specific no-op.
  • Verify each required target with the installed new key before retire.
  • Add a clearly named emergency override, e.g. --accept-unverified-external-host <name>, requiring typed confirmation and recording the exact abandoned target in output.

Acceptance criteria

  • A Nexus host-key rotation cannot reach old-key retirement while required external backup targets still only trust the retired key, unless the operator explicitly accepts a named target as abandoned/unrecoverable.
  • nexus-host-key stage output contains exact setup commands for the external service VPS and offsite backup destination using the staged public key.
  • nexus-host-key activate proves the staged key against required external backup targets, or stops with actionable recovery instructions.
  • nexus-host-key retire proves the installed new key against required external backup targets, or stops with a named human gate.
  • Tests cover success, missing external host auth, unreachable host, and explicit abandon/override behavior.
  • The runbook documents that external backup SSH targets are rotation gates, not optional follow-up reminders.

Validation commands

  • bash tests/nexus-host-key.sh
  • nix flake check
  • On Nexus, during a real or dry-run rotation, prove printed commands and gates cover the external backup targets before old-key deletion.
**User story:** So that I can rotate the host SSH identity without breaking backups, the rotation workflow must prove every required external backup target is ready before old access is retired. _Part of the "Host-key rotation lifecycle & safety" arc._ --- Priority: P0 / maximum. ## Problem The Nexus host SSH/age identity rotation can retire the old `~/.ssh/host` key before external backup-critical SSH targets have been updated to trust the new key. Managed VMs and Forgejo git-over-SSH are checked by the rotation flow, but external SSH targets used by backups are currently manual follow-up. If those steps are missed, the timer can keep firing while backup jobs fail at SSH authentication or offsite upload. ## Goal Make `nexus-host-key` treat external backup SSH targets as first-class rotation gates. The script should be as automatic as possible and print exact operator commands for any human-only step. ## Required behavior - During `nexus-host-key stage`, print the staged public key and exact instructions to authorize it on required external backup targets before activation. - During `nexus-host-key activate`, verify the staged key works against required external backup targets before swapping `~/.ssh/host`. - During `nexus-host-key retire`, refuse or strongly gate retirement unless required external backup targets have been proven with the installed new key. - Keep human gates where unavoidable, especially: - adding a key through a provider console or recovery shell if old-key SSH no longer works; - offsite backup provider support/account recovery if neither SSH key nor password auth works; - confirming any external host intentionally abandoned before old-key deletion. - Print copy/paste-ready commands for each target. Avoid vague instructions like "update external hosts". ## Concrete target classes At minimum cover: - the external service VPS used by the Forgejo backup script to create and pull dumps; - the offsite SSH backup destination used for encrypted backup upload. Prefer deriving these from identity/deployment data or a small declarative allowlist, so future external backup-critical hosts cannot be missed. Do not include managed VMs that are already covered by the VM verification loop. ## Suggested implementation shape - Add an external-host phase to `nexus-host-key`. - Resolve host, user, port, and identity policy from deployment data instead of hardcoding private values in docs. - Generate exact append/test commands for each target, for example: - old-key path: append staged public key via SSH using the current/old key; - provider-console path: one-line `echo 'ssh-ed25519 ...' >> ~/.ssh/authorized_keys` fallback; - offsite backup path: append to `.ssh/authorized_keys` when SSH/password access is available, otherwise print provider-support request text with the staged public key. - Verify each required target with the staged key before activation using `ssh -F /dev/null -i <staged-key> -o IdentitiesOnly=yes -o BatchMode=yes ... true` or an equivalent target-specific no-op. - Verify each required target with the installed new key before retire. - Add a clearly named emergency override, e.g. `--accept-unverified-external-host <name>`, requiring typed confirmation and recording the exact abandoned target in output. ## Acceptance criteria - A Nexus host-key rotation cannot reach old-key retirement while required external backup targets still only trust the retired key, unless the operator explicitly accepts a named target as abandoned/unrecoverable. - `nexus-host-key stage` output contains exact setup commands for the external service VPS and offsite backup destination using the staged public key. - `nexus-host-key activate` proves the staged key against required external backup targets, or stops with actionable recovery instructions. - `nexus-host-key retire` proves the installed new key against required external backup targets, or stops with a named human gate. - Tests cover success, missing external host auth, unreachable host, and explicit abandon/override behavior. - The runbook documents that external backup SSH targets are rotation gates, not optional follow-up reminders. ## Validation commands - `bash tests/nexus-host-key.sh` - `nix flake check` - On Nexus, during a real or dry-run rotation, prove printed commands and gates cover the external backup targets before old-key deletion.
Author

Design note: this should use a registry, not hardcoded external hosts.

Suggested contract:

  • Keep SSH connection details in the existing identity/deployment SSH host registry.
  • Add a separate external trust-target registry that references SSH host aliases and describes why each target is rotation-critical, how to verify it, and how to print remediation instructions.

Example shape, with placeholder names only:

externalSshTrustTargets = {
  serviceBackupVps = {
    sshHost = "service-backup-vps";
    requiredFor = [ "nexus-host-key-rotation" "service-backup" ];
    authorizedKeysPath = "/root/.ssh/authorized_keys";
    verify = "ssh-true";
    recovery = "provider-console";
  };

  offsiteBackup = {
    sshHost = "offsite-backup";
    requiredFor = [ "nexus-host-key-rotation" "service-backup-offsite" ];
    authorizedKeysPath = ".ssh/authorized_keys";
    verify = "ssh-true";
    recovery = "provider-support";
  };
};

nexus-host-key should consume the registry generically:

  • resolve each target's SSH alias through deployment identity data;
  • print setup commands using the staged public key and the target's authorizedKeysPath;
  • verify the staged key before activation;
  • verify the installed new key before retire;
  • require a typed per-target override if a target is intentionally abandoned or unrecoverable.

This keeps the public nexus tool general while allowing private deployments to declare their own backup-critical external trust targets.

Design note: this should use a registry, not hardcoded external hosts. Suggested contract: - Keep SSH connection details in the existing identity/deployment SSH host registry. - Add a separate external trust-target registry that references SSH host aliases and describes why each target is rotation-critical, how to verify it, and how to print remediation instructions. Example shape, with placeholder names only: ```nix externalSshTrustTargets = { serviceBackupVps = { sshHost = "service-backup-vps"; requiredFor = [ "nexus-host-key-rotation" "service-backup" ]; authorizedKeysPath = "/root/.ssh/authorized_keys"; verify = "ssh-true"; recovery = "provider-console"; }; offsiteBackup = { sshHost = "offsite-backup"; requiredFor = [ "nexus-host-key-rotation" "service-backup-offsite" ]; authorizedKeysPath = ".ssh/authorized_keys"; verify = "ssh-true"; recovery = "provider-support"; }; }; ``` `nexus-host-key` should consume the registry generically: - resolve each target's SSH alias through deployment identity data; - print setup commands using the staged public key and the target's `authorizedKeysPath`; - verify the staged key before activation; - verify the installed new key before retire; - require a typed per-target override if a target is intentionally abandoned or unrecoverable. This keeps the public `nexus` tool general while allowing private deployments to declare their own backup-critical external trust targets.
Member

Drafted a dev plan for this: https://forge.anarch.diy/allod/strategy/src/branch/master/dev-plans/nexus-external-backup-ssh-rotation-gates.md (in allod/strategy, dev-plans/nexus-external-backup-ssh-rotation-gates.md).

Shape, following the registry design note above: the externalSshTrustTargets registry lives in secrets/identity.nix referencing existing sshHosts aliases, with synthetic entries in the public template; generic gate logic (stage prints per-target setup commands, activate proves the staged key before the ~/.ssh/host swap, retire proves the installed key first), the --accept-unverified-external-host override, tests, and runbook land in nexus. Two PRs — allod/secrets (Refs allod/nexus#4) and allod/nexus (Closes allod/nexus#4, no-ops on an empty registry so landing order is not load-bearing).

Proving the gates against the real service-backup VPS and offsite destination stays a human-only validation step. A review-prompt pass on the plan is next.

Drafted a dev plan for this: https://forge.anarch.diy/allod/strategy/src/branch/master/dev-plans/nexus-external-backup-ssh-rotation-gates.md (in `allod/strategy`, `dev-plans/nexus-external-backup-ssh-rotation-gates.md`). Shape, following the registry design note above: the `externalSshTrustTargets` registry lives in `secrets/identity.nix` referencing existing `sshHosts` aliases, with synthetic entries in the public template; generic gate logic (stage prints per-target setup commands, activate proves the staged key before the `~/.ssh/host` swap, retire proves the installed key first), the `--accept-unverified-external-host` override, tests, and runbook land in `nexus`. Two PRs — `allod/secrets` (`Refs allod/nexus#4`) and `allod/nexus` (`Closes allod/nexus#4`, no-ops on an empty registry so landing order is not load-bearing). Proving the gates against the real service-backup VPS and offsite destination stays a human-only validation step. A review-prompt pass on the plan is next.
vnprc closed this issue 2026-07-16 20:38:58 +01:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
allod/nexus#4
No description provided.