]> Git — Sourcephile - julm/julm-nix.git/blob - nixos/modules/security/systemd-creds.nix
nix: format with nixfmt-rfc-style
[julm/julm-nix.git] / nixos / modules / security / systemd-creds.nix
1 {
2 pkgs,
3 lib,
4 config,
5 ...
6 }:
7 with lib;
8 let
9 cfg = config.security.systemd-creds;
10 in
11 {
12 options.security.systemd-creds = {
13 target = mkOption {
14 type = types.str;
15 description = ''
16 Destination address of the target host able to encrypt the credentials.
17 Used by the default {option}`security.systemd-creds.shell`.
18 '';
19 default = "root@${config.networking.hostName}.${config.networking.domain}";
20 defaultText = mdLiteral "root@\${config.networking.hostName}.\${config.networking.domain}";
21 example = mdLiteral "''${config.networking.hostName}.wg";
22 };
23 decrypt = mkOption {
24 type = with types; listOf str;
25 description = mdDoc ''
26 Command to get the cleartext of a credential.
27 `credBase` is derived from the path of the credential in `LoadCredentialEncrypted=`,
28 by removing the `builtins.storeDir` prefix and then one directory level.
29 '';
30 apply = concatStringsSep " ";
31 default = [
32 "gpg"
33 "--batch"
34 "--decrypt"
35 "\${credBase%.cred}.gpg"
36 ];
37 example = [
38 "pass"
39 "\${credBase%.cred}"
40 ];
41 };
42 shell = mkOption {
43 type = with types; listOf str;
44 description = mdDoc ''
45 Command to get a shell on the target host.
46 '';
47 apply = concatStringsSep " ";
48 default = [
49 "ssh"
50 "-o"
51 "StrictHostKeyChecking=yes"
52 "-o"
53 "ControlMaster=auto"
54 "-o"
55 "ControlPersist=16s"
56 "\"\${SYSTEMD_CREDS_TARGET:-${cfg.target}}\""
57 ];
58 defaultText = mdLiteral ''
59 ssh -o StrictHostKeyChecking=yes \
60 -o ControlMaster=auto \
61 -o ControlPersist=16s \
62 \"''${SYSTEMD_CREDS_TARGET:-root@''${config.security.systemd-creds.target}}\"
63 '';
64 example = [ "sudo" ];
65 };
66 encrypt = mkOption {
67 type = with types; listOf str;
68 description = mdDoc ''
69 Command to run `systemd-creds encrypt` on the target host.
70
71 ::: {.warning}
72 Beware that the files `/etc/machine-id`
73 and `/var/lib/systemd/credential.secret` on the target host,
74 are both used to encrypt and decrypt when using the `host` key mechanism.
75 Meaning that reinstalling the system on a new drive
76 without restoring those two files
77 will require to reencrypt the credentials.
78 :::
79 '';
80 apply = concatStringsSep " ";
81 default = [
82 "systemd-creds"
83 "encrypt"
84 "--name"
85 "\"$credID\""
86 "--with-key=auto"
87 ];
88 example = [
89 "sudo"
90 "systemd-creds"
91 "encrypt"
92 "--with-key=host"
93 ];
94 };
95 install = mkOption {
96 type = with types; listOf str;
97 apply = concatStringsSep " ";
98 description = mdDoc ''
99 Command to install the encrypted credential on the orchestrating host.
100 '';
101 default = [
102 "install"
103 "-D"
104 "-m"
105 "640"
106 "/dev/stdin"
107 "\"$credBase\""
108 ];
109 };
110 reencrypt = mkOption {
111 type = types.lines;
112 apply = pkgs.writeShellScriptBin "systemd-creds-encrypt-${
113 replaceStrings [ "@" ] [ "-" ] cfg.target
114 }";
115 description = mdDoc ''
116 Encrypt credentials referenced in the `LoadCredentialEncrypted=`
117 of enabled systemd services, by running `systemd-creds` on the {option}`security.systemd-creds.target` host.
118 Only *non-existing* credential files are considered,
119 unless the `SYSTEMD_CREDS_FORCE_REENCRYPT` envvar is set to a non-empty value.
120 Credential directories are not supported.
121
122 Example of use:
123 ```console
124 $ sudo wg genkey | gpg --encrypt --sign --recipient @$USER --output wireguard/wg-intra/privateKey.gpg
125 $ git add wireguard/wg-intra/privateKey.gpg
126 ```
127
128 ```nix
129 { config, pkgs, lib, inputs, ... }:
130 {
131 systemd.services."wireguard-wg-intra".serviceConfig.LoadCredentialEncrypted =
132 [ "privateKey:''${inputs.self}/wireguard/wg-intra/privateKey.cred" ];
133 }
134 ```
135
136 ```console
137 $ nix run .#nixosConfigurations.''${hostName}.config.security.systemd-creds.reencrypt
138 $ git add wireguard/wg-intra/privateKey.cred
139 ```
140
141 ::: {.warning}
142 To be able to access the relative path of the `.cred` file,
143 `inputs.self` has to be used in `LoadCredentialEncrypted=`.
144 Note that `inputs` is a `config._module.args` or `specialArgs`
145 usually set in your `flake.nix`.
146 In other words, using `''${wireguard/wg-intra/privatekey}` here,
147 would not work, because it drops the `wireguard/wg-intra/` part.
148 :::
149 '';
150 };
151 };
152 config = {
153 security.systemd-creds.reencrypt =
154 ''
155 shopt -s extglob globstar nullglob
156 set -o pipefail
157 set -eux
158 ''
159 +
160 concatMapStringsSep "\n"
161 (
162 service:
163 concatMapStringsSep "\n" (
164 credential:
165 let
166 cred = splitString ":" credential;
167 credID = elemAt cred 0;
168 credPath = elemAt cred 1;
169 in
170 ''
171 credID=${escapeShellArg credID}
172 credPath=${escapeShellArg credPath}
173 credBase=''${credPath#${builtins.storeDir}/*/}
174 if test "''${SYSTEMD_CREDS_FORCE_REENCRYPT:+set}" \
175 -o ! -s "$credBase" \
176 -o -e "''${credBase%.cred}.gpg" -a "$credBase" -ot "''${credBase%.cred}.gpg"; then
177 { ${cfg.decrypt}; } |
178 { ${cfg.shell} -- ${cfg.encrypt} - -; } |
179 { ${cfg.install}; }
180 fi
181 ''
182 ) (toList service.serviceConfig.LoadCredentialEncrypted)
183 )
184 (
185 attrValues (
186 filterAttrs (
187 _serviceName: service: service.enable && hasAttr "LoadCredentialEncrypted" service.serviceConfig
188 ) config.systemd.services
189 )
190 );
191 };
192 }