Homelab: Secrets management
Since the introduction of the certificates,
each script was ending with some old-fashioned (i.e. ssh
/scp
-based commands) as follows:
TARGET_DIR=/etc/nixos/certificates/servers/
TARGET_LOGIN=black@192.168.0.4
I won't say that old things are always bad, but there are many shortcomings:
- My server's IP is hard-coded
- My server file hierarchy is implicitly coupled
- My script handles (poorly) nginx
- My script is stateful and does not handle well failures (no retry/restart/rollback)
All of these making it hard to maintain/reuse.
If I really wanted to make this clean from here, I would use something like ansible (I mean JetPorch), or Terraform, which I'm more use to.
I could simply reference the files in my configuration.nix
file, but it would
end-up in my git history, which is not a big deal as it is a local git repository,
the real concern come from my nix store
, which will store the certificate and make
them available to everyone on my machine, in clear-text, without permission requirements.
Hopefully, there's a solution called agenix for that.
It relies on age, which is a pgp successor, which aims to provide a simple way to cipher/decipher files.
Lastly, there's also agebox which helps to manage multiple ciphered files/directories.
Let's start by creating a key:
Then I save the public key:
I could generate it from an SSH key, but I'm used to having my SSH keys dedicated to SSH usage.
Then, I use agebox
to convert all my secrets (certificates and keys):
Which suffixes all the files with .agebox
.
Then I upload keys
to Barracuda.
Finally, the nix part.
First, we have to declare all files:
age = {
identityPaths = [ "/etc/nixos/secrets/age00.txt" ];
secrets =
let
nginx = localPath: { file = localPath; mode = "400"; owner = "nginx"; group = "nginx"; };
in
{
resticClientCA = nginx ./certificates/client/generated/restic/ca/ca.pem.agebox;
resticClientCRL = nginx ./certificates/client/generated/restic/ca/crl/crl.pem.agebox;
withingsClientCA = nginx ./certificates/client/generated/withings/ca/ca.pem.agebox;
withingsClientCRL = nginx ./certificates/client/generated/withings/ca/crl/crl.pem.agebox;
};
};
Quick note here: by default everything is done to use SSH keys, I have to explicitly set the private part of the age key.
Lastly, we can reference the declared secrets:
virtualHosts = {
"restic.barracuda.local" = {
# ...
extraConfig = ''
ssl_client_certificate config.age.secrets.withingsClientCA.path;
ssl_crl config.age.secrets.withingsClientCRL.path;
ssl_verify_client on;
'';
};
};
Here we are, now, our scripts aren't tied to Barracuda, IP, file hierarchy or
deployment failure mishandling.
There's more, aside of /etc/nixos/secrets
, we don't even need a specific file
organisation, which means less headaches for me.