- Scaffolding
- Generate a SSH keypair
- Configuration for the Server Host
- Configuration for the Client Host
- Cleanup
- Conclusion
Let’s see how to grant ourselves ssh access form a client host to a server host using NixOS.
This post assumes you can already deploy to the server host, by means of another ssh key. So this is not about bootstrapping a server. Here, we’ll see how to grant another user access to the host, for example a backup user with reduced access.
Scaffolding
For this post, I’ll be using:
- flakes,
- deploy-rs to deploy on the server host,
- nixos-rebuild to deploy on the client host,
- sops-nix to store the ssh private key encrypted
- and home-manager to configure the client host.
This requires a bit of trial and error to get all these pieces to fit together, so here is a flake that defines the scaffolding for both the client and the server host:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
sops-nix.url = "github:Mic92/sops-nix";
};
outputs = { self, nixpkgs, home-manager, sops-nix }: let
system = "x86_64-linux";
specialArgs = {
serverUser = "vorta";
clientUser = "me";
serverHost = "server";
clientHost = "client";
};
in {
nixosModules.server = {
imports = [
sops-nix.nixosModules.default./server.nix
];
};
nixosConfigurations.server = nixpkgs.lib.nixosSystem {
inherit system specialArgs;
modules = [ self.nixosModules.client ];
};
nixosModules.client = {
imports = [
home-manager.nixosModules.default
sops-nix.nixosModules.default./client.nix
];
};
nixosConfigurations.client = nixpkgs.lib.nixosSystem {
inherit system specialArgs;
modules = [ self.nixosModules.client ];
};
};
}
We use specialArgs
here to pass arguments
to both the client and the server configuration
instead of copying values around.
Deploying to the server host is done with:
#deploy-rs .#server
nix run nixpkgs
# Or, if deploy-rs is installed on your system:
#server deploy .
Deploying to the client host is done with:
-rebuild --flake . switch sudo nixos
Generate a SSH keypair
This step is still manual:
nix shell nixpkgs#openssh --command \
-t ed25519 -f mykey ssh-keygen
Pick no passphrase if you intend this user to get automated access.
This will create two files,
mykey
with the private key
and mykey.pub
with the public key.
We will use both in the later sections.
Configuration for the Server Host
In this example, I create a new vorta
user
to allow access for backups by the VortaBackup software
into the folder /srv/backup/vorta/me
.
This goes in ./server.nix
:
{ config, serverUser, clientUser, ... }:
{
users.users.${serverUser} = {
isSystemUser = true;
# Needed to be able to log in.
useDefaultShell = true;
home = "/srv/backup/${serverUser}/${clientUser}";
homeMode = "770";
createHome = true;
group = "backup";
isSystemUser = true;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAAA..."
];
};
{};
users.groups.backup = }
The users.users.${serverUser}.openssh.authorizedKeys.keys
field
contains the public key from the mykey.pub
file.
If you intend to keep the file laying around, you could instead do:
{serverUser}.openssh.authorizedKeys.keys = [
users.users.$(builtins.readFile ./mykey.pub)
];
Both versions give the same result and it’s really a matter of taste here.
Configuration for the Client Host
Copy the private key from mykey
under some yaml field in your sops file.
I’ll assume it’s under ssh/server/client-me-vorta
.
The convention is ssh/${serverHost}/${clientHost}-${clientUser}-${serverHost}
and is an organized to let me have multiple access per server-client pair.
This goes in ./client.nix
:
{ config, serverUser, clientUser, serverHost, clientHost, ... }:
{
sops.secrets."ssh/${serverHost}/backup" = {
owner = user;
path = "/home/${clientUser}/.ssh/${serverHost}-${serverUser}";
key = "ssh/${serverHost}/${clientHost}-${clientUser}-${serverHost}";
};
home-manager.users.${clientUser} = {
programs.ssh = {
enable = true;
matchBlocks = {
"${serverHost}-${serverUser}" = {
user = serverUser;
hostname = serverHost;
identityFile = config.sops.secrets.
"ssh/${serverHost}/backup".path;
};
};
};
};
}
I use the key
field of sops-nix to give a nickname to the secret,
making it a bit easier to recall later in the home-manager
config.
This is optional.
Cleanup
The mykey
files laying around this way is not good.
Let’s delete them securely with the shred
tool:
nix shell coreutils --command shred mykey
nix shell coreutils --command shred mykey.pub
rm mykey
rm mykey.pub
Omit the mykey.pub
file if you used the readFile
version of the code.
Conclusion
After deploying, we now can access the server with the new user with:
ssh ${serverHost}-${serverUser}
With the example values I chose above, it’s:
ssh server-vorta