In my previous post I described how to set up an SSH certificate authority using a YubiKey.
I’ve been experimenting with NixOS again recently — having had several failed attempts before due to the utterly impenetrable documentation — but I’m curious enough to spend a bit more effort on it.
Even though I’m only running it in test VMs right now I still want to get a good feel for running it on a server, so I want to be able to log in using a signed certificate.
Initially I did it the “easy” way by adding a cert-authority
key for my user in configuration.nix
:
{ config, pkgs, ... }:
{
users.users.jamesog = {
# Other user options omitted
openssh.authorizedKeys.keys = [
''cert-authority,principals="jamesog" ssh-rsa ...''
];
};
}
But ideally I wanted to set it up system-wide, which requires putting the CA public key in a file and adding TrustedUserCAKeys
to sshd_config
.
I hoped NixOS would have a knob for this already, but searching the options for services.openssh revealed nothing for that, but it does have services.openssh.extraConfig
to put any arbitrary configuration. That’ll do, I suppose.
I wasn’t sure how to get the CA key into a file at first. Some digging made me come across pkgs.writeText for building derivations. Even though it’s in pkgs
, which I wasn’t sure about, I figured that it’s already passed to configuration.nix
so maybe it’ll work. I tried this:
{ config, pkgs, ... }:
let
trustedCAUserKeys = pkgs.writeText "/etc/ssh/ca.pub"
''
ssh-rsa ...
'';
in
{
services.openssh.extraConfig =
''
TrustedUserCAKeys ${trustedCAUserKeys}
'';
}
But this was very unhappy. It turns out — and this should have been obvious from how it’s used in environment.packages
— that this pkgs
is just a list, so it doesn’t have writeText
.
After some more digging I came across environment.etc whose description says:
Set of files that have to be linked in /etc.
Well, that’s exactly what I want in this case. I don’t know what I’d do if I wanted a file outside of /etc
though. And it’s kind of weird it’s called environment
. Let’s give this a go then.
{ config, pkgs, ... }:
{
environment.etc = {
"ssh/ca.pub".text = ''
ssh-rsa ...
'';
};
services.openssh.extraConfig =
''
TrustedUserCAKeys ${environment.etc."ssh/ca.pub"}
'';
}
This didn’t quite work due to the variable expansion I tried to use in extraConfig
. I was hoping environment.etc
would somewhere inject the full path to the file it created like writeText
does. But then I realised I don’t need to: I’m literally telling environment.etc
that I want a file in /etc
and the path. I can just hard-code it:
{ config, pkgs, ... }:
{
environment.etc = {
"ssh/ca.pub".text = ''
ssh-rsa ...
'';
};
services.openssh.extraConfig =
''
TrustedUserCAKeys /etc/ssh/ca.pub
'';
# Note that nixos-rebuild will complain if no users have a password nor authorizedKeys set, so add a backup key
users.users.jamesog = {
# Other user options omitted
openssh.authorizedKeys.keys = [
"ecdsa-sha2-nistp256 ..."
];
};
}
Note the last block: nixos-rebuild
will refuse to build if no users have a password or SSH keys defined as it doesn’t want to leave you with a system you can’t log in to. Fair enough. There’s probably some option I could tweak to bypass that, but it seemed just as safe to set at least one normal SSH public key on my user as a backup in case I nerf the sshd
config.
Now it works. Nice!