]> Git — Sourcephile - julm/julm-nix.git/blob - nixos/modules/security/systemd-creds.nix
systemd-creds: improve a bit
[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.
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"
36 "-o"
37 "StrictHostKeyChecking=yes"
38 "-o"
39 "ControlMaster=auto"
40 "-o"
41 "ControlPersist=16s"
42 "\"\${SYSTEMD_CREDS_TARGET:-${cfg.target}}\""
43 ];
44 defaultText = mdLiteral ''
45 ssh -o StrictHostKeyChecking=yes \
46 -o ControlMaster=auto \
47 -o ControlPersist=16s \
48 \"''${SYSTEMD_CREDS_TARGET:-root@''${config.security.systemd-creds.target}}\"
49 '';
50 example = [ "sudo" ];
51 };
52 encrypt = mkOption {
53 type = with types; listOf str;
54 description = mdDoc ''
55 Command to run `systemd-creds encrypt` on the target host.
56
57 ::: {.warning}
58 Beware that the files `/etc/machine-id`
59 and `/var/lib/systemd/credential.secret` on the target host,
60 are both used to encrypt and decrypt when using the `host` key mechanism.
61 Meaning that reinstalling the system on a new drive
62 without restoring those two files
63 will require to reencrypt the credentials.
64 :::
65 '';
66 apply = concatStringsSep " ";
67 default = [ "systemd-creds" "encrypt" "--name" "\"$credID\"" "--with-key=auto" ];
68 example = [ "sudo" "systemd-creds" "encrypt" "--with-key=host" ];
69 };
70 install = mkOption {
71 type = with types; listOf str;
72 apply = concatStringsSep " ";
73 description = mdDoc ''
74 Command to install the encrypted credential on the orchestrating host.
75 '';
76 default = [ "install" "-D" "-m" "640" "/dev/stdin" "\"$credBase\"" ];
77 };
78 script = mkOption {
79 type = types.lines;
80 apply = pkgs.writeShellScriptBin "systemd-creds-encrypt-${replaceStrings ["@"] ["-"] cfg.target}";
81 description = mdDoc ''
82 Encrypt credentials referenced in the `LoadCredentialEncrypted=`
83 of enabled systemd services, by running `systemd-creds` on the {option}`security.systemd-creds.target` host.
84 Only *non-existing* credential files are considered.
85
86 Example of use:
87 ```console
88 $ sudo wg genkey | gpg --encrypt --sign --recipient @$USER --output wireguard/wg-intra/privateKey.gpg
89 $ git add wireguard/wg-intra/privateKey.gpg
90 ```
91
92 ```nix
93 { config, pkgs, lib, inputs, ... }:
94 {
95 systemd.services."wireguard-wg-intra".serviceConfig.LoadCredentialEncrypted =
96 [ "privateKey:''${inputs.self}/wireguard/wg-intra/privateKey.cred" ];
97 }
98 ```
99
100 ```console
101 $ nix run .#nixosConfigurations.''${hostName}.config.security.systemd-creds.script
102 $ git add wireguard/wg-intra/privateKey.cred
103 ```
104
105 ::: {.warning}
106 To be able to access the relative path of the `.cred` file,
107 `inputs.self` has to be used in `LoadCredentialEncrypted`.
108 Note that `inputs` is a `config._module.args` or `specialArgs`
109 usually set in your `flake.nix`.
110 Using `''${wireguard/wg-intra/privatekey}`
111 would not work, because it drops the `wireguard/wg-intra/` part.
112 :::
113 '';
114 };
115 };
116 config = {
117 security.systemd-creds.script = ''
118 shopt -s extglob globstar nullglob
119 set -o pipefail
120 set -eux
121 '' + concatMapStringsSep "\n"
122 (service:
123 concatMapStringsSep "\n"
124 (credential:
125 let
126 cred = splitString ":" credential;
127 credID = elemAt cred 0;
128 credPath = elemAt cred 1;
129 in
130 ''
131 credID=${escapeShellArg credID}
132 credPath=${escapeShellArg credPath}
133 credBase=''${credPath#${storeDir}/*/}
134 if test ! -s "$credBase"; then
135 { ${cfg.decrypt}; } |
136 { ${cfg.shell} -- ${cfg.encrypt} - -; } |
137 { ${cfg.install}; }
138 fi
139 ''
140 )
141 (toList service.serviceConfig.LoadCredentialEncrypted))
142 (attrValues
143 (filterAttrs
144 (_serviceName: service:
145 service.enable &&
146 hasAttr "LoadCredentialEncrypted" service.serviceConfig
147 )
148 config.systemd.services
149 )
150 );
151 };
152 }