DreamLake

Host credential rotation

Status: Metadata released; full key lifecycle source-accepted, not released or hosted-accepted. Tracking: Vault #241, master #247. Metadata APIs are available in Python 0.14 and native CLI 0.17; npm 0.17 publication is pending. The backend is verified on staging task 154, source 5c82000. The later client-owned key lifecycle passed real target/jump SSH against an isolated source fixture; that is separate from published-client and hosted enrollment/KMS acceptance. DreamLake does not receive backend SSH delegation through these APIs.

A replacement must keep working access until the new credential is saved and verified. The client will install a dedicated public key alongside existing keys, verify a fresh connection using only the replacement, conditionally replace the host binding, and then remove the exact old managed key. Target and jump-host credentials have separate operations. Password rotation remains required work; its reviewed password-specific design requires independent recovery access and a pre-mutation reservation. Key coexistence must not be assumed for passwords.

Metadata replacement and recovery

Persist a unique operation ID before submitting. Both entries must have distinct immutable IDs and live, current revisions in the same personal vault tenant. The existing binding determines host/enrollment/role/endpoint/kind; replacement cannot change that scope. Account authorization and host enrollment are checked before a new transition. A transaction compares the expected binding and both entry revisions, touches both entries against concurrent write/purge, replaces the binding, and writes a metadata-only receipt. Concurrent replacements of the same binding have one winner.

shell
dreamlake vault supersede --binding-id "$binding_id" \
  --operation-id "$operation_id" \
  --expected-entry-id "$old_entry_id" --expected-entry-revision 1 \
  --replacement-entry-id "$new_entry_id" --replacement-entry-revision 1

dreamlake vault host-operation --operation-id "$operation_id"

HTTP uses POST /v1/vault/host-credentials/:id/supersede and GET /v1/vault/host-credential-operations/:operationId. Exact intent replay returns the original receipt even after later changes; reusing an operation ID for another intent conflicts. A lost response or missing receipt does not prove failure: retain the original ID and arguments, query the receipt or retry exactly. Recovery is owner-only and does not reread secrets or rerun SSH.

VaultHostCredentialSupersession records immutable old/new IDs and revisions, binding scope, creation time and cleanup_pending. It is modeled in Prisma and indexed by tenant/operation and both retained references. The collector transaction checks pending references; both entries remain retained even if the current binding is subsequently unbound. No automatic TTL releases a pending reference. Receipts confirm only the metadata transition, never successful SSH authentication or remote revocation. Their history is not stored in shared Host responses.

Explicit cleanup confirmation (under review)

After the client verifies that only the selected replacement key authenticates, it submits the exact old/new IDs and revisions plus public-key fingerprints, pinned remote identity, and verification timestamps to POST /v1/vault/host-credential-operations/:operationId/confirm-cleanup. This is an explicit client attestation, not server-proven SSH authentication. The backend checks the personal owner and host enrollment, the unreleased binding's exact current replacement, and its live revision in a transaction. A later replacement, release, retirement or changed revision rejects a new confirmation.

An identical retry returns the original confirmation, including after a lost response. Altered proof conflicts. The operation becomes cleanup_confirmed; historical references stop blocking garbage collection, while the current host binding still retains the replacement. Confirmation does not retire a shared old entry, delete ciphertext immediately, or perform any SSH operation. The original supersede POST remains an immutable cleanup_pending receipt; operation GET returns the current lifecycle and its confirmation.

The paired Python API is client.vault.confirm_host_credential_cleanup(operation_id, binding_id=..., expected_entry_id=..., expected_entry_revision=..., replacement_entry_id=..., replacement_entry_revision=..., verification=...). client.vault.host_credential(binding_id) and GET /v1/vault/host-credentials/:id return account-only binding/current-entry metadata for the client pre-removal check. These additions are under review and unreleased.

Remaining remote lifecycle

After metadata replacement, remote cleanup must remove only the exact old managed key and verify old-key rejection while the replacement still succeeds. The explicit confirmation API above is implemented for review; pending operations continue retaining both references until confirmation. Client-owned installation, remote verification and key cleanup are being implemented in paired CLI/Python changes. Password rotation remains required and open. Releasing a binding or retiring a vault entry never revokes remote SSH access.

  • [vault/hosts/rotation-schema] Conditional replacement, durable receipt and retention-aware GC implemented locally.
  • [vault/hosts/rotation-clients] Paired metadata CLI/Python APIs with explicit operation recovery implemented locally.
  • [vault/hosts/rotation-review] Metadata slice reviewed and merged in backend #348, CLI #56 and Python #42.
  • [vault/hosts/rotation-release] Package release, verified rollout and hosted acceptance of this metadata slice.
  • [vault/hosts/rotation-ssh] Client-owned managed-key installation and fresh target/jump verification.
  • [vault/hosts/rotation-cleanup] Exact remote cleanup and explicit retention-release confirmation.
  • [vault/hosts/rotation-password] Password rotation with no-lockout and uncertain-outcome recovery.

Validation includes real HTTP/Mongo CAS and tenant checks, aged-purge retention after unbind, concurrent operations, and fresh CLI/Python processes recovering a dropped committed response. Remote acceptance must additionally prove old access survives install/save/probe failure, unrelated authorized keys remain unchanged, reused host identities fail closed, and old keys stop working only after replacement verification. Existing host saving evidence proves storage and restored SSH, not rotation.