diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index b20d1902c5c9..2c8db7efde8a 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -306,3 +306,5 @@ See . ``` **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. diff --git a/nixos/lib/systemd-unit-options.nix b/nixos/lib/systemd-unit-options.nix index 5e85a9c2a1d9..8538c8519c53 100644 --- a/nixos/lib/systemd-unit-options.nix +++ b/nixos/lib/systemd-unit-options.nix @@ -3,12 +3,8 @@ let inherit (systemdUtils.lib) assertValueOneOf - automountConfig checkUnitConfig makeJobScript - mountConfig - serviceConfig - unitConfig unitNameType ; diff --git a/nixos/modules/services/misc/radicle.nix b/nixos/modules/services/misc/radicle.nix index 7979bd451b27..feb5eb1d7bb4 100644 --- a/nixos/modules/services/misc/radicle.nix +++ b/nixos/modules/services/misc/radicle.nix @@ -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 + ''; + }; + 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 ''; }; 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}" ]; }; } ]; diff --git a/nixos/tests/radicle-ci-broker.nix b/nixos/tests/radicle-ci-broker.nix index 6fb0953daac8..19f48aa33fe0 100644 --- a/nixos/tests/radicle-ci-broker.nix +++ b/nixos/tests/radicle-ci-broker.nix @@ -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 = { diff --git a/nixos/tests/radicle.nix b/nixos/tests/radicle.nix index e802be63e83d..74ee577a1911 100644 --- a/nixos/tests/radicle.nix +++ b/nixos/tests/radicle.nix @@ -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; diff --git a/pkgs/test/systemd/nixos/default.nix b/pkgs/test/systemd/nixos/default.nix index 1590fd1a47fc..ff3fcd211307 100644 --- a/pkgs/test/systemd/nixos/default.nix +++ b/pkgs/test/systemd/nixos/default.nix @@ -1,7 +1,6 @@ { pkgs, lib, - stdenv, ... }: