Skip to content

Fix sops-nix host decrypt: use age-converted pubkeys, not raw ssh-ed25519 - #242

Merged
kid merged 2 commits into
mainfrom
fix/sops-nix-ssh-recipient-mismatch
Aug 20, 2026
Merged

Fix sops-nix host decrypt: use age-converted pubkeys, not raw ssh-ed25519#242
kid merged 2 commits into
mainfrom
fix/sops-nix-ssh-recipient-mismatch

Conversation

@kid

@kid kid commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • k3s-sops-operator-seed.service failed on node1 with Error getting data key: 0 successful groups required, got 0 when decrypting secrets/clusters/prd/sops-age-key.sops — even though node1's SSH host key was correctly listed as a recipient, and sops updatekeys/sops rotate reproduced the exact same failure every time (ruling out a one-off bad ciphertext blob).
  • Root cause: sops's own ssh-ed25519 ... recipient support (filippo.io/age/agessh, meant for a human decrypting locally via SOPS_AGE_SSH_PRIVATE_KEY_FILE) and sops-nix's host-side decrypt (Mic92/ssh-to-age, used by sops-install-secrets) derive different X25519 keys from the same ed25519 SSH key. Confirmed reproducibly with a throwaway test keypair: ciphertext encrypted via filippo.io/age/agessh for a recipient never decrypts via the Mic92-derived identity for that same key. sops-nix's own README documents the correct pattern — convert a host's SSH key to its age1... form with ssh-to-age first, and use that string as the recipient, not the raw SSH pubkey.
  • modules/flake/sops-config.nix's clusterMemberHostKeys was inserting a cluster-member host's raw SSH pubkey as an age recipient on secrets/clusters/<cluster>/sops-age-key.sops, which sops-nix on that host can never actually decrypt.
  • provision-host-key.nix now also derives and commits each host's age-converted pubkey (ssh_host_ed25519_key.age-pub), and sops-config.nix uses that instead. Backfilled node1's age-pub, regenerated .sops.yaml, and rotated the cluster secret to the corrected recipient.

Test plan

  • nix flake check --print-build-logs passes
  • Reproduced the encrypt/decrypt mismatch in isolation with a throwaway SSH keypair (filippo.io/age/agessh encrypt -> Mic92/ssh-to-age decrypt fails; not committed, test-only)
  • Deployed to node1 (deploy node1) — no activation errors
  • k3s-sops-operator-seed.service now succeeds on node1: secret/sops-age-prd created

https://claude.ai/code/session_01CezjyaVpC3FVPTUECp7cMR

kid and others added 2 commits August 20, 2026 15:36
…5519

k3s-sops-operator-seed.service failed on node1 with "Error getting data
key: 0 successful groups required, got 0" when decrypting
secrets/clusters/prd/sops-age-key.sops, even though node1's SSH host key
was a correctly listed recipient and sops updatekeys/rotate reproduced
the exact same failure every time.

Root cause: sops's own "ssh-ed25519 ..." recipient support
(filippo.io/age/agessh, meant for a human decrypting locally via
SOPS_AGE_SSH_PRIVATE_KEY_FILE) and sops-nix's host-side decrypt
(Mic92/ssh-to-age, used by sops-install-secrets) derive *different*
X25519 keys from the same ed25519 SSH key. Confirmed reproducibly with a
throwaway test keypair: ciphertext encrypted via filippo.io/age/agessh
for a recipient never decrypts via the Mic92-derived identity for that
same key. sops-nix's own README documents the correct pattern: convert a
host's SSH key to its age1... form with ssh-to-age first, and use that
string as the recipient, not the raw SSH pubkey.

modules/flake/sops-config.nix's clusterMemberHostKeys was inserting a
cluster-member host's raw SSH pubkey as an age recipient on
secrets/clusters/<cluster>/sops-age-key.sops, which sops-nix on that
host can never actually decrypt. provision-host-key.nix now also derives
and commits each host's age-converted pubkey
(ssh_host_ed25519_key.age-pub), and sops-config.nix uses that instead.
Backfilled node1's age-pub, regenerated .sops.yaml, and rotated the
cluster secret to the corrected recipient. Verified on node1:
k3s-sops-operator-seed.service now succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CezjyaVpC3FVPTUECp7cMR
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CezjyaVpC3FVPTUECp7cMR
@kid
kid merged commit 3cfc211 into main Aug 20, 2026
1 check passed
@kid
kid deleted the fix/sops-nix-ssh-recipient-mismatch branch August 20, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant