Skip to content

Rotate the signing key

Signing-key rotation opens an overlap window during which both the retiring and the incoming key verify, then retires the old key. Rotation is driven through the plexctl signing rotate and plexctl signing close subcommands implemented in ../../../cmd/plexctl/commands/signing.go, which delegate to the signer's OpenRotation and CloseRotation gRPC RPCs per Domain scope.

Prerequisites

  • Operator access to the signer deployment.
  • The target scope (platform or domain:<uuid>) and the new key ID.
  • Access to a plexctl binary configured to reach the signer (--signer-endpoint or the PLEXSPHERE_SIGNER_ENDPOINT env var).

Steps

Open the rotation

shell
plexctl signing rotate --scope domain:<uuid> --new-key-id <key-id>

The command invokes the OpenRotation RPC and prints the resulting RotationTransition — the (old_key_id, new_key_id, opened_at, closes_at) tuple. The signer now advertises both public keys; closes_at = opened_at + overlap_window (configured at signer bring-up through --overlap-window).

Exit codes follow the shared exitCodeFor catalogue in ../../../cmd/plexctl/app.go:

  • 0 on success.
  • 1 when the signer returns FailedPrecondition — most commonly the stable detail string signing: rotation in progress, meaning a rotation is already open for this scope.
  • 2 when the signer returns InvalidArgument — the stable detail string signing: invariant violation, meaning the request violates a domain invariant (malformed scope, malformed key ID, or a scope the deployment profile refuses).

Sign through the new key

During the overlap every fresh Sign resolves to the new key while existing signatures still verify against the old one. Optionally restart the signer mid-overlap to prove the window is persisted — RotationService.Resume reconciles the open transition row at boot without republishing the signing_key_rotated event.

Close the rotation

shell
plexctl signing close --scope domain:<uuid> --old <old-key-id> --new <new-key-id>

The command invokes the CloseRotation RPC after the overlap. The old key is retired and no longer advertised. Rotation is per-Domain independent — one scope's rotation never touches another's. Exit codes follow the same exitCodeFor mapping as rotate above.

Verification

  • Confirm the JWKS no longer lists the retired key ID and that signatures minted during the overlap still verify against the new active key.
  • A successful rotate emits a signing_key_rotated event on the signed event bus — one outbox row per Node in the scope. See the event-taxonomy row in ../../contexts/mesh/sse.md for the wire-level payload shape.

See also