Problem
The SeiNode.spec.validator CRD today surfaces only consensus identity (signingKey → priv_validator_key.json) and P2P identity (nodeKey → node_key.json). There is no surface for the operator account keyring (the node_admin / seivaloper… key) that signs governance, staking, and upgrade-proposal transactions.
As validators migrate from EC2 hosts — where the team's seienv tool SSHes in and runs seid tx gov vote --from node_admin against the host's ~/.seid/keyring-file/ — to Kubernetes pods managed by this controller, the operator-account keyring has no home in the pod and no declarative surface.
Impact
- Operators running validators on
sei-k8s-controller cannot perform governance operations (vote, submit-proposal, deposit) from in-cluster tooling.
- Blocks the seictl + sidecar governance-signing workstream (sister issues in
sei-protocol/seictl).
- Keys must otherwise live on an operator's laptop or a separate jumphost, defeating the "keys never leave the pod" goal.
Relevant experts
- kubernetes-specialist — CRD shape, RBAC, mount wiring in
internal/noderesource/
- blockchain-developer — semantics of operator-account vs consensus key separation
- platform-engineer — Secret lifecycle, GitOps integration with SOPS / ESO
Proposed approach
Mirror the existing SigningKeySource / NodeKeySource discriminated-union shape:
- Add
ValidatorSpec.OperatorKeyring *OperatorKeyringSource in api/v1alpha1/validator_types.go. Reserve future siblings (TMKMS, Vault) as comments per the existing pattern.
- First variant:
SecretOperatorKeyringSource with secretName (same-namespace) and optional keyName (the keyring entry name, default node_admin). secretName CEL-immutable via self == oldSelf.
- CRD CEL invariants:
operatorKeyring.secret.secretName MUST differ from both signingKey.secret.secretName and nodeKey.secret.secretName (trust-boundary, parallel to the existing distinct-Secret rule at validator_types.go:7).
operatorKeyring is independently optional — does NOT need to be paired with signingKey / nodeKey. Permits non-signing observers that still want governance-only key surface.
- Mount target: sidecar container only (not the seid main container) at
\$SEI_HOME/keyring-file/, DefaultMode: 0o400, with subPath semantics. Add the volume/mount construction in internal/noderesource/noderesource.go symmetric to signingKeyVolumes / signingKeyMounts.
- New pre-flight validation task
validate-operator-keyring in internal/task/. Gets the Secret, verifies non-empty, parses the keyring index to confirm at least one key entry exists. Terminal vs transient error split as in validate_signing_key.go. Plan-gated before apply-statefulset (internal/planner/planner.go:496-509).
OperatorKeyringReady condition in api/v1alpha1/seinode_types.go symmetric to SigningKeyReady / NodeKeyReady, with Validated|NotReady|Invalid reasons.
- Bootstrap-pod guard: extend (or generalize) the
assertNoSigningKeyOnBootstrapPod check in internal/task/bootstrap_resources.go to also assert no operator-keyring Secret leaks into the bootstrap pod.
- RBAC: existing
secrets:get;list;watch covers it. No new permission.
- Documentation: extend the BYO-validator runbook in
.tide/validator-migration.md to cover operator-keyring import.
Acceptance criteria
Out of scope
- Sidecar consumption of the keyring (tracked in seictl: "Sidecar production keyring backend support")
- TMKMS / Vault / Remote signer variants (placeholder comment only)
- Cross-namespace Secret references (separate workstream if/when needed)
- Operator-keyring rotation flow (Secret is CEL-immutable; rotation is delete-recreate)
References
- Coral session 2026-05-11
- Existing pattern:
api/v1alpha1/validator_types.go — SigningKeySource / NodeKeySource discriminated unions
- Existing pattern:
internal/task/validate_signing_key.go
- Existing pattern:
internal/noderesource/noderesource.go:485-562 — Secret-volume / subPath mount
- Bootstrap guard:
internal/task/bootstrap_resources.go:333-348
- Sister issues in
sei-protocol/seictl: sidecar keyring backend, sign-tx task family, seictl gov CLI, sidecar authn
Sequencing
Phase 1, step 1 of 5 in the seictl/sidecar governance-flow migration off slanders/seienv. Pairs with the sidecar keyring-backend issue (must land before the sign-tx task family can consume it).
Problem
The
SeiNode.spec.validatorCRD today surfaces only consensus identity (signingKey→priv_validator_key.json) and P2P identity (nodeKey→node_key.json). There is no surface for the operator account keyring (thenode_admin/seivaloper…key) that signs governance, staking, and upgrade-proposal transactions.As validators migrate from EC2 hosts — where the team's
seienvtool SSHes in and runsseid tx gov vote --from node_adminagainst the host's~/.seid/keyring-file/— to Kubernetes pods managed by this controller, the operator-account keyring has no home in the pod and no declarative surface.Impact
sei-k8s-controllercannot perform governance operations (vote, submit-proposal, deposit) from in-cluster tooling.sei-protocol/seictl).Relevant experts
internal/noderesource/Proposed approach
Mirror the existing
SigningKeySource/NodeKeySourcediscriminated-union shape:ValidatorSpec.OperatorKeyring *OperatorKeyringSourceinapi/v1alpha1/validator_types.go. Reserve future siblings (TMKMS,Vault) as comments per the existing pattern.SecretOperatorKeyringSourcewithsecretName(same-namespace) and optionalkeyName(the keyring entry name, defaultnode_admin).secretNameCEL-immutable viaself == oldSelf.operatorKeyring.secret.secretNameMUST differ from bothsigningKey.secret.secretNameandnodeKey.secret.secretName(trust-boundary, parallel to the existing distinct-Secret rule atvalidator_types.go:7).operatorKeyringis independently optional — does NOT need to be paired withsigningKey/nodeKey. Permits non-signing observers that still want governance-only key surface.\$SEI_HOME/keyring-file/,DefaultMode: 0o400, with subPath semantics. Add the volume/mount construction ininternal/noderesource/noderesource.gosymmetric tosigningKeyVolumes/signingKeyMounts.validate-operator-keyringininternal/task/. Gets the Secret, verifies non-empty, parses the keyring index to confirm at least one key entry exists. Terminal vs transient error split as invalidate_signing_key.go. Plan-gated beforeapply-statefulset(internal/planner/planner.go:496-509).OperatorKeyringReadycondition inapi/v1alpha1/seinode_types.gosymmetric toSigningKeyReady/NodeKeyReady, withValidated|NotReady|Invalidreasons.assertNoSigningKeyOnBootstrapPodcheck ininternal/task/bootstrap_resources.goto also assert no operator-keyring Secret leaks into the bootstrap pod.secrets:get;list;watchcovers it. No new permission..tide/validator-migration.mdto cover operator-keyring import.Acceptance criteria
validator.operatorKeyringfield added with kubebuilder markers and CEL validationvalidate-operator-keyringtask implemented and unit-testedOperatorKeyringReadycondition transitions correctly throughValidated|NotReady|Invalid\$SEI_HOME/keyring-file/, mode 0400; main seid container does NOT mount itmake manifests generateregenerates CRD YAML and DeepCopy cleanlyoperatorKeyring, confirms sidecar can read the keyring.tide/validator-migration.mdupdatedOut of scope
References
api/v1alpha1/validator_types.go—SigningKeySource/NodeKeySourcediscriminated unionsinternal/task/validate_signing_key.gointernal/noderesource/noderesource.go:485-562— Secret-volume / subPath mountinternal/task/bootstrap_resources.go:333-348sei-protocol/seictl: sidecar keyring backend, sign-tx task family, seictl gov CLI, sidecar authnSequencing
Phase 1, step 1 of 5 in the seictl/sidecar governance-flow migration off
slanders/seienv. Pairs with the sidecar keyring-backend issue (must land before the sign-tx task family can consume it).