{ pkgs, lib, config, ... }:
-with builtins;
with lib;
let cfg = config.security.systemd-creds; in
{
description = mdDoc ''
Command to get the cleartext of a credential.
`credBase` is derived from the path of the credential in `LoadCredentialEncrypted=`,
- by removing the `storeDir` prefix and then one directory level if any,
- and then the `.cred` suffix if any.
+ by removing the `builtins.storeDir` prefix and then one directory level.
'';
apply = concatStringsSep " ";
- default = [ "gpg" "--batch" "--decrypt" "\${credBase}.gpg" ];
- example = [ "pass" "\${credBase}" ];
+ default = [ "gpg" "--batch" "--decrypt" "\${credBase%.cred}.gpg" ];
+ example = [ "pass" "\${credBase%.cred}" ];
};
shell = mkOption {
type = with types; listOf str;
'';
apply = concatStringsSep " ";
default = [
- "ssh" "-o" "StrictHostKeyChecking=yes"
- "-o" "ControlMaster=auto"
- "-o" "ControlPersist=16s"
+ "ssh"
+ "-o"
+ "StrictHostKeyChecking=yes"
+ "-o"
+ "ControlMaster=auto"
+ "-o"
+ "ControlPersist=16s"
"\"\${SYSTEMD_CREDS_TARGET:-${cfg.target}}\""
];
defaultText = mdLiteral ''
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"];
+ example = [ "sudo" "systemd-creds" "encrypt" "--with-key=host" ];
};
install = mkOption {
type = with types; listOf str;
description = mdDoc ''
Command to install the encrypted credential on the orchestrating host.
'';
- default = [ "install" "-D" "-m" "640" "/dev/stdin" "\"$credBase\".cred" ];
+ default = [ "install" "-D" "-m" "640" "/dev/stdin" "\"$credBase\"" ];
};
- script = mkOption {
+ 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 *existing* and *empty* credential files, are considered.
- Recursively if the credential path is a directory.
- Note that when using flakes, the sandboxing requires those empty files to be added to Git beforehand.
+ 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 wireguard/wg-intra/privateKey.cred --output wireguard/wg-intra/privateKey.gpg
- $ tee </dev/null >wireguard/wg-intra/privateKey.cred
- $ git add wireguard/wg-intra/privateKey.cred
+ $ 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:''${wireguard/wg-intra/privateKey.cred}" ];
+ [ "privateKey:''${inputs.self}/wireguard/wg-intra/privateKey.cred" ];
+ }
```
```console
- $ nix run .#nixosConfigurations.''${hostName}.config.security.systemd-creds.script
+ $ 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.script = ''
+ security.systemd-creds.reencrypt = ''
shopt -s extglob globstar nullglob
set -o pipefail
set -eux
- '' + concatMapStringsSep "\n"
- (service:
- concatMapStringsSep "\n"
- (credential: let
+ '' + concatMapStringsSep "\n"
+ (service:
+ concatMapStringsSep "\n"
+ (credential:
+ let
cred = splitString ":" credential;
credID = elemAt cred 0;
credPath = elemAt cred 1;
- in ''
+ in
+ ''
credID=${escapeShellArg credID}
credPath=${escapeShellArg credPath}
- if test -f "$credPath" -a ! -s "$credPath"; then
- credBase=''${credPath#${storeDir}/}
- credBase=''${credBase#*/}
- credBase=''${credBase%.cred}
+ credBase=''${credPath#${builtins.storeDir}/*/}
+ if test "''${SYSTEMD_CREDS_FORCE_REENCRYPT:+set}" \
+ -o ! -s "$credBase" \
+ -o -e "''${credBase%.cred}.gpg" -a "$credBase" -ot "''${credBase%.cred}.gpg"; then
{ ${cfg.decrypt}; } |
{ ${cfg.shell} -- ${cfg.encrypt} - -; } |
{ ${cfg.install}; }
- elif test -d "$credPath"; then
- for credPath in "$credPath"/**(.); do
- if test ! -s "$credPath"; then
- credBase=''${credPath#${storeDir}/}
- credBase=''${credBase#*-}
- credBase=''${credBase%.cred}
- { ${cfg.decrypt}; } |
- { ${cfg.shell} -- ${cfg.encrypt} - -; } |
- { ${cfg.install}; }
- fi
- done
fi
''
- )
- service.serviceConfig.LoadCredentialEncrypted)
- (attrValues
- (filterAttrs (serviceName: service:
+ )
+ (toList service.serviceConfig.LoadCredentialEncrypted))
+ (attrValues
+ (filterAttrs
+ (_serviceName: service:
service.enable &&
- hasAttr "LoadCredentialEncrypted" service.serviceConfig
- ) config.systemd.services
+ hasAttr "LoadCredentialEncrypted" service.serviceConfig
)
- );
+ config.systemd.services
+ )
+ );
};
}