nixos/radicle: Update radicle to support systemd creds more completely via ImportCredential

By default it will look for the cred names supported by the upstream package, but alternate cred names can be chosen to produce a rename on the ImportCredential.
This commit is contained in:
Elias Vasylenko
2025-12-20 22:02:16 +00:00
parent 3a813e37cc
commit 914bb2e6db
6 changed files with 59 additions and 27 deletions

View File

@@ -306,3 +306,5 @@ See <https://github.com/NixOS/nixpkgs/issues/481673>.
```
**Do not set this globally!** This makes your setup inherently less secure.
- `services.radicle` now supports importing the private key and passphrase as systemd creds.

View File

@@ -3,12 +3,8 @@
let
inherit (systemdUtils.lib)
assertValueOneOf
automountConfig
checkUnitConfig
makeJobScript
mountConfig
serviceConfig
unitConfig
unitNameType
;

View File

@@ -2,6 +2,7 @@
config,
lib,
pkgs,
utils,
...
}:
let
@@ -15,6 +16,11 @@ let
RAD_HOME = HOME;
};
credentials = {
privateKey = "xyz.radicle.node.secret";
privateKeyPassphrase = "xyz.radicle.node.passphrase";
};
# Convenient wrapper to run `rad` in the namespaces of `radicle-node.service`
rad-system = pkgs.writeShellScriptBin "rad-system" ''
set -o allexport
@@ -131,16 +137,34 @@ in
services.radicle = {
enable = lib.mkEnableOption "Radicle Seed Node";
package = lib.mkPackageOption pkgs "radicle-node" { };
privateKeyFile = lib.mkOption {
# Note that a key encrypted by systemd-creds is not a path but a str.
type = with lib.types; either path str;
privateKey = lib.mkOption {
type = with lib.types; nullOr (either path str);
default = null;
description = ''
Absolute file path to an SSH private key,
An SSH private key (as an absolute file path or Systemd credential name),
usually generated by `rad auth`.
If it contains a colon (`:`) the string before the colon
is taken as the credential name
and the string after as a path encrypted with `systemd-creds`.
If set to the default value of `null`, radicle will import the private key from a credential
named `${credentials.privateKey}`.
If configured as a credential name it will be imported via `ImportCredential=` in the service configuration.
Refer to the systemd-creds documentation for more details <https://systemd.io/CREDENTIALS/>
'';
};
privateKeyPassphrase = lib.mkOption {
type = with lib.types; nullOr str;
default = null;
description = ''
A passphrase for an SSH private key (as a Systemd credential name),
usually provided on generation of the key with `rad auth`.
If set to the default value of `null`, radicle will optionally import the passphrase from a
credential named `${credentials.privateKeyPassphrase}`.
If the passphrase is not set, radicle will prompt for it.
If configured as a credential name it will be imported via `ImportCredential=` in the service configuration.
Refer to the systemd-creds documentation for more details <https://systemd.io/CREDENTIALS/>
'';
};
publicKey = lib.mkOption {
@@ -304,23 +328,28 @@ in
# Give only access to the private key to radicle-node.
{
serviceConfig =
let
keyCred = builtins.split ":" "${cfg.privateKeyFile}";
in
if lib.length keyCred > 1 then
if cfg.privateKey == null then
{
LoadCredentialEncrypted = [ cfg.privateKeyFile ];
# Note that neither %d nor ${CREDENTIALS_DIRECTORY} works in BindReadOnlyPaths=
BindReadOnlyPaths = [
"/run/credentials/radicle-node.service/${lib.head keyCred}:${env.RAD_HOME}/keys/radicle"
];
ImportCredential = [ credentials.privateKey ];
}
else if lib.types.path.check cfg.privateKey then
{
LoadCredential = [ "${credentials.privateKey}:${cfg.privateKey}" ];
}
else
{
LoadCredential = [ "radicle:${cfg.privateKeyFile}" ];
BindReadOnlyPaths = [
"/run/credentials/radicle-node.service/radicle:${env.RAD_HOME}/keys/radicle"
];
ImportCredential = [ "${cfg.privateKey}:${credentials.privateKey}" ];
};
}
{
serviceConfig =
if cfg.privateKeyPassphrase == null then
{
ImportCredential = [ credentials.privateKeyPassphrase ];
}
else
{
ImportCredential = [ "${cfg.privateKeyPassphrase}:${credentials.privateKeyPassphrase}" ];
};
}
];

View File

@@ -19,9 +19,12 @@ in
meta.maintainers = with lib.maintainers; [ defelo ];
nodes.seed = {
virtualisation.credentials = {
"xyz.radicle.node.secret".source = "${seed-ssh-keys.snakeOilEd25519PrivateKey}";
};
services.radicle = {
enable = true;
privateKeyFile = seed-ssh-keys.snakeOilEd25519PrivateKey;
publicKey = seed-ssh-keys.snakeOilEd25519PublicKey;
node.openFirewall = true;
settings = {

View File

@@ -76,9 +76,12 @@ in
{
imports = [ commonHostConfig ];
virtualisation.credentials = {
"xyz.radicle.node.secret".source = "${seed-ssh-keys.snakeOilEd25519PrivateKey}";
};
services.radicle = {
enable = true;
privateKeyFile = seed-ssh-keys.snakeOilEd25519PrivateKey;
publicKey = seed-ssh-keys.snakeOilEd25519PublicKey;
node = {
openFirewall = true;

View File

@@ -1,7 +1,6 @@
{
pkgs,
lib,
stdenv,
...
}: