{ pkgs, lib, config, ... }:
with lib;
let cfg = config.security.systemd-creds; in
{
options.security.systemd-creds = {
target = mkOption {
type = types.str;
description = ''
Destination address of the target host able to encrypt the credentials.
Used by the default {option}`security.systemd-creds.shell`.
'';
default = "root@${config.networking.hostName}.${config.networking.domain}";
defaultText = mdLiteral "root@\${config.networking.hostName}.\${config.networking.domain}";
example = mdLiteral "''${config.networking.hostName}.wg";
};
decrypt = mkOption {
type = with types; listOf str;
description = mdDoc ''
Command to get the cleartext of a credential.
`credBase` is derived from the path of the credential in `LoadCredentialEncrypted=`,
by removing the `builtins.storeDir` prefix and then one directory level.
'';
apply = concatStringsSep " ";
default = [ "gpg" "--batch" "--decrypt" "\${credBase%.cred}.gpg" ];
example = [ "pass" "\${credBase%.cred}" ];
};
shell = mkOption {
type = with types; listOf str;
description = mdDoc ''
Command to get a shell on the target host.
'';
apply = concatStringsSep " ";
default = [
"ssh"
"-o"
"StrictHostKeyChecking=yes"
"-o"
"ControlMaster=auto"
"-o"
"ControlPersist=16s"
"\"\${SYSTEMD_CREDS_TARGET:-${cfg.target}}\""
];
defaultText = mdLiteral ''
ssh -o StrictHostKeyChecking=yes \
-o ControlMaster=auto \
-o ControlPersist=16s \
\"''${SYSTEMD_CREDS_TARGET:-root@''${config.security.systemd-creds.target}}\"
'';
example = [ "sudo" ];
};
encrypt = mkOption {
type = with types; listOf str;
description = mdDoc ''
Command to run `systemd-creds encrypt` on the target host.
::: {.warning}
Beware that the files `/etc/machine-id`
and `/var/lib/systemd/credential.secret` on the target host,
are both used to encrypt and decrypt when using the `host` key mechanism.
Meaning that reinstalling the system on a new drive
without restoring those two files
will require to reencrypt the credentials.
:::
'';
apply = concatStringsSep " ";
default = [ "systemd-creds" "encrypt" "--name" "\"$credID\"" "--with-key=auto" ];
example = [ "sudo" "systemd-creds" "encrypt" "--with-key=host" ];
};
install = mkOption {
type = with types; listOf str;
apply = concatStringsSep " ";
description = mdDoc ''
Command to install the encrypted credential on the orchestrating host.
'';
default = [ "install" "-D" "-m" "640" "/dev/stdin" "\"$credBase\"" ];
};
reencrypt = mkOption {
type = types.lines;
apply = pkgs.writeShellScriptBin "systemd-creds-encrypt-${replaceStrings ["@"] ["-"] cfg.target}";
description = mdDoc ''
Encrypt credentials referenced in the `LoadCredentialEncrypted=`
of enabled systemd services, by running `systemd-creds` on the {option}`security.systemd-creds.target` host.
Only *non-existing* credential files are considered,
unless the `SYSTEMD_CREDS_FORCE_REENCRYPT` envvar is set to a non-empty value.
Credential directories are not supported.
Example of use:
```console
$ sudo wg genkey | gpg --encrypt --sign --recipient @$USER --output wireguard/wg-intra/privateKey.gpg
$ git add wireguard/wg-intra/privateKey.gpg
```
```nix
{ config, pkgs, lib, inputs, ... }:
{
systemd.services."wireguard-wg-intra".serviceConfig.LoadCredentialEncrypted =
[ "privateKey:''${inputs.self}/wireguard/wg-intra/privateKey.cred" ];
}
```
```console
$ nix run .#nixosConfigurations.''${hostName}.config.security.systemd-creds.reencrypt
$ git add wireguard/wg-intra/privateKey.cred
```
::: {.warning}
To be able to access the relative path of the `.cred` file,
`inputs.self` has to be used in `LoadCredentialEncrypted=`.
Note that `inputs` is a `config._module.args` or `specialArgs`
usually set in your `flake.nix`.
In other words, using `''${wireguard/wg-intra/privatekey}` here,
would not work, because it drops the `wireguard/wg-intra/` part.
:::
'';
};
};
config = {
security.systemd-creds.reencrypt = ''
shopt -s extglob globstar nullglob
set -o pipefail
set -eux
'' + concatMapStringsSep "\n"
(service:
concatMapStringsSep "\n"
(credential:
let
cred = splitString ":" credential;
credID = elemAt cred 0;
credPath = elemAt cred 1;
in
''
credID=${escapeShellArg credID}
credPath=${escapeShellArg credPath}
credBase=''${credPath#${builtins.storeDir}/*/}
if test "''${SYSTEMD_CREDS_FORCE_REENCRYPT:+set}" \
-o ! -s "$credBase"; then
{ ${cfg.decrypt}; } |
{ ${cfg.shell} -- ${cfg.encrypt} - -; } |
{ ${cfg.install}; }
fi
''
)
(toList service.serviceConfig.LoadCredentialEncrypted))
(attrValues
(filterAttrs
(_serviceName: service:
service.enable &&
hasAttr "LoadCredentialEncrypted" service.serviceConfig
)
config.systemd.services
)
);
};
}