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