]> Git — Sourcephile - julm/julm-nix.git/blob - nixos/modules/security/systemd-creds.nix
creds: improve a bit when reinstalling
[julm/julm-nix.git] / nixos / modules / security / systemd-creds.nix
1 { pkgs, lib, config, ... }:
2 with builtins;
3 with lib;
4 let cfg = config.security.systemd-creds; in
5 {
6 options.security.systemd-creds = {
7 target = mkOption {
8 type = types.str;
9 description = ''
10 Destination address of the target host able to encrypt the credentials.
11 Used by the default {option}`security.systemd-creds.shell`.
12 '';
13 default = "root@${config.networking.hostName}.${config.networking.domain}";
14 defaultText = mdLiteral "root@\${config.networking.hostName}.\${config.networking.domain}";
15 example = mdLiteral "''${config.networking.hostName}.wg";
16 };
17 decrypt = mkOption {
18 type = with types; listOf str;
19 description = mdDoc ''
20 Command to get the cleartext of a credential.
21 `credBase` is derived from the path of the credential in `LoadCredentialEncrypted=`,
22 by removing the `storeDir` prefix and then one directory level if any.
23 '';
24 apply = concatStringsSep " ";
25 default = [ "gpg" "--batch" "--decrypt" "\${credBase%.cred}.gpg" ];
26 example = [ "pass" "\${credBase%.cred}" ];
27 };
28 shell = mkOption {
29 type = with types; listOf str;
30 description = mdDoc ''
31 Command to get a shell on the target host.
32 '';
33 apply = concatStringsSep " ";
34 default = [
35 "ssh" "-o" "StrictHostKeyChecking=yes"
36 "-o" "ControlMaster=auto"
37 "-o" "ControlPersist=16s"
38 "\"\${SYSTEMD_CREDS_TARGET:-${cfg.target}}\""
39 ];
40 defaultText = mdLiteral ''
41 ssh -o StrictHostKeyChecking=yes \
42 -o ControlMaster=auto \
43 -o ControlPersist=16s \
44 \"''${SYSTEMD_CREDS_TARGET:-root@''${config.security.systemd-creds.target}}\"
45 '';
46 example = [ "sudo" ];
47 };
48 encrypt = mkOption {
49 type = with types; listOf str;
50 description = mdDoc ''
51 Command to run `systemd-creds encrypt` on the target host.
52
53 ::: {.warning}
54 Beware that the files `/etc/machine-id`
55 and `/var/lib/systemd/credential.secret` on the target host,
56 are both used to encrypt and decrypt when using the `host` key mechanism.
57 Meaning that reinstalling the system on a new drive
58 without restoring those two files
59 will require to reencrypt the credentials.
60 :::
61 '';
62 apply = concatStringsSep " ";
63 default = [ "systemd-creds" "encrypt" "--name" "\"$credID\"" "--with-key=auto" ];
64 example = ["sudo" "systemd-creds" "encrypt" "--with-key=host"];
65 };
66 install = mkOption {
67 type = with types; listOf str;
68 apply = concatStringsSep " ";
69 description = mdDoc ''
70 Command to install the encrypted credential on the orchestrating host.
71 '';
72 default = [ "install" "-D" "-m" "640" "/dev/stdin" "\"$credBase\"" ];
73 };
74 script = mkOption {
75 type = types.lines;
76 apply = pkgs.writeShellScriptBin "systemd-creds-encrypt-${replaceStrings ["@"] ["-"] cfg.target}";
77 description = mdDoc ''
78 Encrypt credentials referenced in the `LoadCredentialEncrypted=`
79 of enabled systemd services, by running `systemd-creds` on the {option}`security.systemd-creds.target` host.
80 Only *existing* and *empty* credential files, are considered.
81 Recursively if the credential path is a directory.
82 Note that when using flakes, the sandboxing requires those empty files to be added to Git beforehand.
83
84 Example of use:
85 ```console
86 $ sudo wg genkey | gpg --encrypt --sign --recipient @$USER wireguard/wg-intra/privateKey.cred --output wireguard/wg-intra/privateKey.gpg
87 $ tee </dev/null >wireguard/wg-intra/privateKey.cred
88 $ git add wireguard/wg-intra/privateKey.cred
89 ```
90
91 ```nix
92 systemd.services."wireguard-wg-intra".serviceConfig.LoadCredentialEncrypted =
93 [ "privateKey:''${wireguard/wg-intra/privateKey.cred}" ];
94 ```
95
96 ```console
97 $ nix run .#nixosConfigurations.''${hostName}.config.security.systemd-creds.script
98 $ git add wireguard/wg-intra/privateKey.cred
99 ```
100 '';
101 };
102 };
103 config = {
104 security.systemd-creds.script = ''
105 shopt -s extglob globstar nullglob
106 set -o pipefail
107 set -eux
108 '' + concatMapStringsSep "\n"
109 (service:
110 concatMapStringsSep "\n"
111 (credential: let
112 cred = splitString ":" credential;
113 credID = elemAt cred 0;
114 credPath = elemAt cred 1;
115 in ''
116 credID=${escapeShellArg credID}
117 credPath=${escapeShellArg credPath}
118 credBase=''${credPath#${storeDir}/*/}
119 if test -f "$credBase"; then
120 if test ! -s "$credBase"; then
121 { ${cfg.decrypt}; } |
122 { ${cfg.shell} -- ${cfg.encrypt} - -; } |
123 { ${cfg.install}; }
124 fi
125 elif test -d "$credBase"; then
126 for credBase in "$credBase"/**(.); do
127 credBase=''${credBase#${storeDir}/*-}
128 if test ! -s "$credBase"; then
129 { ${cfg.decrypt}; } |
130 { ${cfg.shell} -- ${cfg.encrypt} - -; } |
131 { ${cfg.install}; }
132 fi
133 done
134 fi
135 ''
136 )
137 service.serviceConfig.LoadCredentialEncrypted)
138 (attrValues
139 (filterAttrs (serviceName: service:
140 service.enable &&
141 hasAttr "LoadCredentialEncrypted" service.serviceConfig
142 ) config.systemd.services
143 )
144 );
145 };
146 }