Can/how do you manage ssh authorized keys with sops-nix?

sops-nix only allows for you to grab the path of the key files for security reasons, but
users.users.<user>.openssh.authorizedKeys.keyFiles = [config.sops.secrets.ssh-<machine>.path ]

causes
error: access to absolute path '/run/secrets/<ssh config for machine>' is forbidden in pure eval mode (use '--impure' to override) on build.

I assume there has to be a better way to extract keys from sops-nix for authorizedKeys, but googling around and searching the forum hasn’t yet found much in terms of solutions for this particular use.

4 Likes

Still no luck figuring this out.

It’s not a huge concern to me right now, so I’m going back to pubkeys sitting in my git repo for now until I get the motivation to dive this again. I’ll report back if I find anything useful.

I think the problem is that files in users.users.<name>.openssh.authorizedKeys.keyFiles are read at build time. Even if sops-nix already placed the secrets somewhere it most like results in something impure to include the plain secrets from that location.

I use the following workaround. Already forgot where I got the snippet/idea/whatever from.

  system.activationScripts."zz-<user>-authorizedKeys".text = ''    
    mkdir -p "/etc/ssh/authorized_keys.d";    
    cp "${config.sops.secrets."ssh-<machine>".path}" "/etc/ssh/authorized_keys.d/<user>";
    chmod +r "/etc/ssh/authorized_keys.d/<user>"
  '';

It hooks into the activation process and copies the plain secrets to /etc/ssh/authorized_keys.d. Sops must be able to decode the secret at boot time for this to work. I usually also encrypt the sops file containing the authorized_keys content with the age key derived from the ssh host key. So sops uses that when booting the system.

This workaround may conflict with other stuff I’m not using so I’m not aware of.

Hope this helps.

I’m wondering if this is actually a bug. You’ll notice that the nixos wiki actually has an example using this function:

https://nixos.wiki/wiki/SSH_public_key_authentication

users.users."user".openssh.authorizedKeys.keyFiles = [
  /etc/nixos/ssh/authorized_keys
];

However if I actually try putting that into my flake I get the same absolute path is forbidden, use --impure to override.

That being said, I can’t find any bug reports on nixpkgs about this to confirm.

That’s not a bug, absolute paths are disallowed with flakes (or any environment using pure eval).
You probably meant to use a string.

The reason you don’t want to use a string here is because it will end up in the nix store and any public repo your config may be. I’m aware it’s only a public key and it’s not a big deal but for the sake of modularity and thoroughness, it’s nice to store these personal tokens in sops and keep them out of your config.

Edit:
Adding to my confusion

users.users.<name>.hashedPasswordFile = config.sops.secrets.password-hash.path;

is perfectly valid in a flake without the --impure flag while

users.users.<name>.openssh.authorizedKeys.keyFiles = [ config.sops.secrets.public-key.path ];

isn’t.

In any case, is an activation script or systemd unit really the best solution for this?

You have it backwards, a path would get copied to the store. (At least when interpolated.)
Strings wouldn’t.

That’s not going to happen unless you commit the file lol

Okay it looks like ...authorizedKeys.keyFiles is actually trying to read the public keys via readFile:

That won’t work with sops-nix as the decryption for sops-nix secrets are done at runtime (into /run/secrets).

1 Like

I use
openssh.authorizedKeys.keyFiles = ["${flake-inputs.self}/keys/users/user_nix-builder_ed25519.pub"];

I think I stole flake-inputs from

So the path is read at build time. SOPS is only executed on the target host after the build result was applied.
Above snippet adds the content of the file to /etc/ssh/authorized_keys.d/nix-builder on the target host.
Because the public key is used, there shouldn’t be a need to use SOPS for this.