Skip to content

Support KeystoreConfig::InMemory for hardened validator deploys #155

@steinerkelvin

Description

@steinerkelvin

Support KeystoreConfig::InMemory for hardened validator deploys

Context

The torusform hardened-validator deploy model requires that validator session keys (BABE, GRANDPA, im_online, ...) never exist on disk. Keys are injected at runtime via author_insertKey by the torusform-inject daemon after operator approval (see torusform/docs/key-injection-architecture.md for the full flow).

This requires the node to run with its keystore backed by KeystoreConfig::InMemory rather than the default KeystoreConfig::Path { .. }. Upstream sc_cli::KeystoreParams exposes --keystore-path and --password-* but has no "in-memory only" switch — currently the node always creates an on-disk keystore dir, defeating the no-key-on-disk property even if no keys are pre-loaded.

Relevant references in the torusform repo:

  • specs/validator-nixos-module.md L239 — NixOS systemd unit is written expecting a --keystore-in-memory flag on torus-node.
  • docs/key-injection-architecture.md L253–263 — canonical description: "The node's keystore must be in-memory".
  • specs/inject-daemon.md L258 — notes that InMemory keystore drops keys on restart, which is exactly the boot-flow assumption.

Goal

Make torus-node runnable with an in-memory-only keystore when deployed as a hardened validator, so that:

  1. No keystore directory is ever created on disk (not under --base-path, not elsewhere).
  2. author_insertKey (via the localhost RPC) is the only path that can populate session keys.
  3. On restart, keys are gone until torusform-inject re-injects them.
  4. The existing dev/testnet workflows (cargo xtask run local --alice, etc.) are unaffected.

Sketch

Two main options; pick whichever fits cleanest after a closer read of the current node/src/{cli,command,service}.rs:

(A) Add a --keystore-in-memory CLI flag.

  • Extend the Cli struct in node/src/cli.rs with keystore_in_memory: bool (#[arg(long)]).
  • In node/src/command.rs, where the sc_service::Configuration is built for the run subcommand, override config.keystore = KeystoreConfig::InMemory when the flag is set.
  • sc_service::new_full_parts / new_partial will then use the in-memory backend; rest of the stack is unchanged.

(B) Derive it from role.

  • When config.role.is_authority() AND --validator is active, default keystore to InMemory unconditionally. Keep --keystore-path as an explicit escape hatch for existing workflows.
  • Risk: silently changes the default for anyone running --validator today. Probably wants a feature flag or opt-in env var.

(A) is more conservative and easier to review; (B) is the kind of "secure by default" change that needs separate discussion. Default to (A) unless there's a reason not to.

Acceptance criteria

  • torus-node --validator --keystore-in-memory ... starts and runs normally.
  • Under that invocation, the --base-path contains no keystore/ directory at any point during the node's lifetime (verified by a test or by inspection during an integration run).
  • author_insertKey via localhost RPC succeeds and the injected key is usable for block production / finality.
  • Node restart with the same base-path results in an empty keystore again (no key material recovered from disk).
  • Existing cargo xtask run local --alice / --bob flows still work unchanged (regression check).
  • Unit/integration test covering the InMemory path, even if minimal.

Non-goals

  • The full key-injection flow (gate daemon, Vault unwrap, TLS mTLS, operator 2FA) — that's torusform-inject / torusform-gate territory.
  • Keystore encryption-at-rest or hardware-backed key storage — different threat model, separate issue.
  • Changing behavior for non-validator roles (api, archive).

References

  • torusform/docs/key-injection-architecture.md §6 "Substrate Node Configuration"
  • torusform/specs/validator-nixos-module.md torus.validator module
  • torusform/specs/inject-daemon.md
  • Upstream: sc_cli::KeystoreParams, sc_service::KeystoreConfig

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions