Skip to content

feat(consensus/keys): add keys import and keys export - #1198

Open
leninmehedy wants to merge 1 commit into
01166-consensus-keys-generatefrom
01167-consensus-keys-import-export
Open

leninmehedy wants to merge 1 commit into
01166-consensus-keys-generatefrom
01167-consensus-keys-import-export

Conversation

@leninmehedy

@leninmehedy leninmehedy commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Summary

Third PR in the stacked series for epic #1164 — Consensus Node Key & Secret Management. Stacked on #1197 (base branch 01166-consensus-keys-generate).

Adds typed Kubernetes Secret I/O and the import / export verbs, turning a folder of key material into the per-node secrets the solo-operator consumes (and back).

What's in it

  • kube client — ApplySecret (server-side apply; key bytes stay in memory, no temp file rendered) and GetSecretData.
  • internal/consensus/keys — Import builds the three per-node secrets from a folder; Export writes them back to a folder for backup. Each key secret is named by the key's own date and fingerprint: node<N>-gossip-keys-<date>-<fp8>, node<N>-grpc-tls-keys-<date>-<fp8>, node<N>-admin-pubkey-<fp8>, with neutral, index-free inner-keys. <date> is the certificate's Not before date (so reimporting the same folder after a cluster reset gives the same names) and <fp8> is the start of its SHA-384, taken over the PEM-encoded certificate (the same hash as the roster grpc_certificate_hash). New key material therefore creates new secrets beside the old ones and never replaces them. Each secret carries solo.hiero.org/node, key-type and cert-fingerprint labels plus SHA-384 and not-after annotations (see feat(consensus/keys): name key secrets by key date and fingerprint; stage rotations beside live keys #1282).
  • consensus keys import — --keys-dir, --source folder (vault tracked in feat(consensus/keys): ESO/vault source for 'keys import' #1171), --force, --gossip/--tls/--admin; --node-id required. With no selector, the admin secret is created only when admin.pub exists in --keys-dir; --admin makes it required.
  • consensus keys export — --keys-dir, selectors; --node-id required.

Custody & safety invariants

  • import --secret-suffix <text> replaces the date part; export takes --fingerprint / --secret-suffix to choose one generation, and checks each secret's content against its name, so a renamed or edited secret is refused.
  • import --label key=value (repeatable) puts extra labels on every secret, for example a reference to an external system (example.com/external-ref=ticket-42). Kubernetes label syntax applies and weaver's own labels (solo.hiero.org/node, key-type, cert-fingerprint) are reserved. When a secret's content is already up to date, only its labels are patched: the key data is never sent again, so it cannot be overwritten.
  • The admin key is not generated by solo-provisioner: the operator creates it in an external wallet and saves its public key (hex) as admin.pub in --keys-dir. Only that public key is imported; no admin private key ever reaches the provisioner or the cluster.
  • The admin secret holds the public key as raw bytes (32 for Ed25519, 33 for compressed secp256k1), because solo-operator hex-encodes the secret value itself when it builds genesis. import decodes the hex in admin.pub; export writes it back as hex. Storing the hex text would double-encode the key in genesis-network.json. Operator follow-up: Read the admin public key secret as hex text only and label secp256k1 admin keys in genesis solo-operator#1394 proposes accepting hex text too.
  • The gossip signing secret is the node's network identity, so a re-import is refused unless --force is passed.
  • Export writes admin.pub back; the admin private key stays in the wallet.

Join flow this enables

keys generate (node-id-free) + wallet admin key → submit nodeCreate off-cluster → network assigns the node-id → save the wallet's admin public key as admin.pub → keys import --node-id <assigned> → consensus node install. solo-provisioner never submits the DAB transaction.

Files

File Purpose
internal/kube/client.go ApplySecret (SSA) + GetSecretData
internal/kube/secret_test.go GetSecretData tests
internal/consensus/keys/secrets.go SecretClient iface, SecretNamesFor, Import/Export
internal/consensus/keys/secrets_test.go import/export orchestration tests
cmd/cli/commands/consensus/keys/import.go import subcommand
cmd/cli/commands/consensus/keys/export.go export subcommand
cmd/cli/commands/consensus/keys/keys.go selector/namespace/node-id helpers; wiring; stubbable client
cmd/cli/commands/consensus/keys/import_export_test.go command tests
docs/commands/consensus.md import + export sections

Testing

Validated in the UTM VM:

go test -race -cover ./pkg/consensus/keys/... ./internal/consensus/keys/... ./internal/kube/... ./cmd/cli/commands/consensus/keys/...

All pass. The ApplySecret server-side-apply happy path is exercised via the orchestration tests (the fake dynamic client cannot create through SSA) and by UAT against a real cluster.

Closes #1167.

Update: admin key generation was removed from the stack, so the VM run above predates that change. Unit tests pass on macOS for the touched packages; a VM run is still needed.

@leninmehedy
leninmehedy requested a review from a team as a code owner September 26, 2026 13:34
@swirlds-automation

swirlds-automation commented Sep 26, 2026 •

Copy link
Copy Markdown

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch from 055af5d to ae5854f Compare September 26, 2026 13:48
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch from ae5854f to c16e1fb Compare September 26, 2026 14:06
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch from c16e1fb to 95d08a7 Compare September 26, 2026 14:13
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch from 95d08a7 to 54380e3 Compare September 26, 2026 14:14
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch 2 times, most recently from e3aaeef to 6d577dc Compare September 26, 2026 14:41
@leninmehedy
leninmehedy added this pull request to stack #1201 September 26, 2026 14:44
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch from 6d577dc to 06931e7 Compare September 29, 2026 05:12
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch from 06931e7 to d8d804f Compare October 13, 2026 06:27
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch from d8d804f to a61e70f Compare October 13, 2026 10:33
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch 4 times, most recently from e8a2f34 to de0c173 Compare October 13, 2026 12:52
@leninmehedy
leninmehedy marked this pull request as draft October 20, 2026 06:23
@leninmehedy leninmehedy added the skip-kvm-runner Skip kvm-runner jobs (UAT, integration) in PR checks; for stacked PRs below the top label Oct 21, 2026
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch from de0c173 to 8987832 Compare October 21, 2026 03:37
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch from 8987832 to eae1310 Compare October 21, 2026 04:44
@leninmehedy
leninmehedy force-pushed the 01167-consensus-keys-import-export branch from eae1310 to 62c0dec Compare October 21, 2026 05:19
Add typed Secret I/O and the import/export verbs over #1166:

- kube client: ApplySecret (server-side apply, key bytes kept in memory,
  no temp file) and GetSecretData.
- internal/consensus/keys: Import builds the three per-node secrets
  (node<N>-gossip-keys / -grpc-tls-keys / -admin-pubkey) from a folder;
  Export writes them back for backup. The admin key is created in an external
  wallet; its public key is supplied as admin.pub in the folder and is the
  only admin material imported (created only when admin.pub exists, unless
  --admin is passed); the gossip signing secret is guarded against overwrite
  unless --force (it is the node's network identity).
- commands: `consensus keys import` (--from, --source folder, --force,
  selectors; --node-id required) and `consensus keys export` (--out,
  selectors; --node-id required).

Vault-backed import is tracked separately (#1171).

Refs #1167

Signed-off-by: Lenin Mehedy <lenin.mehedy@hashgraph.com>
@radkomih
radkomih force-pushed the 01167-consensus-keys-import-export branch from 62c0dec to 9c786a7 Compare November 4, 2026 12:24
@radkomih
radkomih marked this pull request as ready for review November 4, 2026 13:07
@radkomih
radkomih requested review from a team as code owners November 4, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-kvm-runner Skip kvm-runner jobs (UAT, integration) in PR checks; for stacked PRs below the top

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(consensus/keys): typed Secret I/O — 'keys import' + 'keys export'

2 participants