]> Git — Sourcephile - sourcephile-nix.git/blob - nixpkgs/patches/security.pass.diff
nix: simplify the sending root's OpenPGP key
[sourcephile-nix.git] / nixpkgs / patches / security.pass.diff
1 diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
2 index f361163ca63..5e306de7dad 100644
3 --- a/nixos/modules/module-list.nix
4 +++ b/nixos/modules/module-list.nix
5 @@ -193,6 +193,7 @@
6 ./security/lock-kernel-modules.nix
7 ./security/misc.nix
8 ./security/oath.nix
9 + ./security/pass.nix
10 ./security/pam.nix
11 ./security/pam_usb.nix
12 ./security/pam_mount.nix
13 diff --git a/nixos/modules/security/pass.nix b/nixos/modules/security/pass.nix
14 new file mode 100644
15 index 00000000000..bf72d06e1ea
16 --- /dev/null
17 +++ b/nixos/modules/security/pass.nix
18 @@ -0,0 +1,179 @@
19 +{ pkgs, lib, config, ... }:
20 +let
21 + inherit (builtins) dirOf head listToAttrs match split;
22 + inherit (lib) types;
23 + inherit (config.security) pass;
24 + escapeUnitName = name:
25 + lib.concatMapStrings (s: if lib.isList s then "-" else s)
26 + (split "[^a-zA-Z0-9_.\\-]+" name);
27 +in
28 +{
29 +options.security.pass = {
30 + store = lib.mkOption {
31 + type = types.path;
32 + description = ''
33 + Default path to the password-store of the orchestrating system.
34 + '';
35 + # Does not copy the entire password-store into the Nix store,
36 + # only the keys actually used will be.
37 + apply = toString;
38 + };
39 + secrets = lib.mkOption {
40 + default = {};
41 + type = types.attrsOf (types.submodule ({name, config, ...}: {
42 + options = {
43 + gpg = lib.mkOption {
44 + type = types.path;
45 + default = builtins.path {
46 + path = pass.store + "/${name}.gpg";
47 + name = "${escapeUnitName name}.gpg";
48 + };
49 + description = ''
50 + The path to the gnupg-encrypted secret.
51 + It will be copied into the Nix store of the orchestrating and of the target system.
52 + It must be decipherable by an OpenPGP key within <literal>gnupgHome</literal>,
53 + whose passhrase is on the target system into <literal>passphraseFile</literal>.
54 + Defaults to the name of the secret, prefixed by <literal>passwordStore</literal>
55 + and suffixed by <literal>.gpg</literal>.
56 + '';
57 + };
58 + gnupgHome = lib.mkOption {
59 + type = types.str;
60 + default = "/root/.gnupg";
61 + description = ''
62 + The directory on the target system to the <literal>gnupg</literal> home
63 + used to decrypt the secret.
64 + '';
65 + };
66 + passphraseFile = lib.mkOption {
67 + type = types.str;
68 + default = "/root/key.pass";
69 + description = ''
70 + The directory on the target system to a file containing
71 + the password of an OpenPGP key in <literal>gnupgHome</literal>,
72 + to which <literal>gpg</literal> secret is encrypted to.
73 + '';
74 + };
75 + mode = lib.mkOption {
76 + type = types.str;
77 + default = "400";
78 + description = ''
79 + Permission mode of the secret <literal>path</literal>.
80 + '';
81 + };
82 + user = lib.mkOption {
83 + type = types.str;
84 + default = "root";
85 + description = ''
86 + Owner of the secret <literal>path</literal>.
87 + '';
88 + };
89 + group = lib.mkOption {
90 + type = types.str;
91 + default = "root";
92 + description = ''
93 + Group of the secret <literal>path</literal>.
94 + '';
95 + };
96 + pipe = lib.mkOption {
97 + type = types.nullOr types.str;
98 + default = null;
99 + description = ''
100 + Shell command taking the deciphered secret on its standard input
101 + and which must put on its standard output
102 + the actual material to be installed.
103 + This allows to decorate the secret with non-secret bits.
104 + '';
105 + };
106 + path = lib.mkOption {
107 + type = types.str;
108 + default = name;
109 + apply = p: if match "^/.*" p == null then "/run/pass-secrets/"+p+"/file" else p;
110 + description = ''
111 + The path on the target system where the secret is installed to.
112 + Any non-absolute path is relative to <filename>/run/pass-secrets</filename>.
113 + Default to the name of the secret.
114 + '';
115 + };
116 + service = lib.mkOption {
117 + type = types.str;
118 + default = "secret-" + escapeUnitName name + ".service";
119 + description = ''
120 + The name of the systemd service.
121 + Useful to put constraints like <literal>after</literal> or <literal>wants</wants>
122 + into services requiring this secret.
123 + '';
124 + };
125 + postStart = lib.mkOption {
126 + type = types.lines;
127 + default = "";
128 + example = "systemctl reload nginx.service";
129 + description = ''
130 + Commands to run after new secrets go live.
131 + Typically the web server and other servers using secrets
132 + need to be reloaded.
133 + '';
134 + };
135 + postStop = lib.mkOption {
136 + type = types.lines;
137 + default = "";
138 + example = ''shred -u "$secret_file"'';
139 + description = ''
140 + Commands to run after stopping the service.
141 + Typically removing a persistent secret.
142 + '';
143 + };
144 + };
145 + }));
146 + };
147 +};
148 +config = lib.mkIf (pass.secrets != {}) {
149 + systemd.services =
150 + lib.mapAttrs' (target: secret:
151 + lib.nameValuePair (lib.removeSuffix ".service" secret.service) {
152 + description = "Install secret ${secret.path}";
153 + script = ''
154 + set -o pipefail
155 + set -eux
156 + decrypt() {
157 + {
158 + ${pkgs.gnupg}/bin/gpg --batch --pinentry-mode loopback \
159 + --passphrase-file '${secret.passphraseFile}' \
160 + --decrypt '${secret.gpg}' \
161 + ${lib.optionalString (secret.pipe != null) (" | "+secret.pipe)}
162 + } |
163 + install -D -m '${secret.mode}' -o '${secret.user}' -g '${secret.group}' /dev/stdin \
164 + '${secret.path}'
165 + }
166 + while ! decrypt; do sleep $((1 + ($RANDOM % 10))); done
167 + '';
168 + postStart = lib.optionalString (secret.postStart != "") ''
169 + set -eux
170 + ${secret.postStart}
171 + '';
172 + postStop = lib.optionalString (secret.postStop != "") ''
173 + secret_file='${secret.path}'
174 + set -eux
175 + ${secret.postStop}
176 + '';
177 + restartIfChanged = true;
178 + restartTriggers = [
179 + secret.passphraseFile
180 + secret.gpg
181 + ];
182 + serviceConfig = {
183 + Type = "oneshot";
184 + Environment = "GNUPGHOME=${secret.gnupgHome}";
185 + PrivateTmp = true;
186 + RemainAfterExit = true;
187 + WorkingDirectory = dirOf secret.gnupgHome;
188 + } // lib.optionalAttrs (match "^/.*" target == null) {
189 + RuntimeDirectory = lib.removePrefix "/run/" (dirOf secret.path);
190 + RuntimeDirectoryMode = "711";
191 + RuntimeDirectoryPreserve = false;
192 + };
193 + }
194 + ) pass.secrets;
195 +};
196 +meta.maintainers = with lib.maintainers; [ julm ];
197 +}