]> Git — Sourcephile - sourcephile-nix.git/blob - nixpkgs/patches/security.gnupg.diff
nix: move to flake.nix
[sourcephile-nix.git] / nixpkgs / patches / security.gnupg.diff
1 diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
2 index f361163ca63..8c199f0fc25 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/gnupg.nix
10 ./security/pam.nix
11 ./security/pam_usb.nix
12 ./security/pam_mount.nix
13 diff --git a/nixos/modules/security/gnupg.nix b/nixos/modules/security/gnupg.nix
14 new file mode 100644
15 index 00000000000..aa29425a530
16 --- /dev/null
17 +++ b/nixos/modules/security/gnupg.nix
18 @@ -0,0 +1,259 @@
19 +{ config, lib, pkgs, ... }:
20 +let
21 + inherit (builtins) attrNames dirOf length match split;
22 + inherit (lib) types;
23 + cfg = config.security.gnupg;
24 + gnupgHome = "/var/lib/gnupg";
25 + # Escape as required by: https://www.freedesktop.org/software/systemd/man/systemd.unit.html
26 + escapeUnitName = name:
27 + lib.concatMapStrings (s: if lib.isList s then "-" else s)
28 + (split "[^a-zA-Z0-9_.\\-]+" name);
29 +in
30 +{
31 +options.security.gnupg = {
32 + store = lib.mkOption {
33 + type = types.path;
34 + default = "/root/.password-store";
35 + description = ''
36 + Default base path for the <literal>gpg</literal> option
37 + of the <link linkend="opt-security.gnupg.secrets">secrets</link>.
38 + Note that you may set it up with something like:
39 + <literal>builtins.getEnv "PASSWORD_STORE_DIR" + "/machines/example"</literal>.
40 + '';
41 + # Does not copy the entire password-store into the Nix store,
42 + # only the keys actually used will be.
43 + apply = toString;
44 + };
45 + secrets = lib.mkOption {
46 + description = "Available secrets.";
47 + default = {};
48 + example = {
49 + "/root/.ssh/id_ed25519" = {};
50 + "knot/tsig/example.org/acme.conf" = {
51 + user = "knot";
52 + };
53 + "lego/example.org/rfc2136" = {
54 + pipe = "
55 + cat -
56 + cat <EOF
57 + RFC2136_NAMESERVER=ns.example.org:53
58 + RFC2136_TSIG_ALGORITHM=hmac-sha256.
59 + RFC2136_TSIG_KEY=acme_example_org
60 + RFC2136_PROPAGATION_TIMEOUT=1000
61 + RFC2136_POLLING_INTERVAL=30
62 + RFC2136_SEQUENCE_INTERVAL=30
63 + RFC2136_DNS_TIMEOUT=1000
64 + RFC2136_TTL=1
65 + EOF
66 + ";
67 + };
68 + };
69 + type = types.attrsOf (types.submodule ({name, config, ...}: {
70 + options = {
71 + gpg = lib.mkOption {
72 + type = types.path;
73 + default = cfg.store + "/${name}.gpg";
74 + description = ''
75 + The path to the GnuPG-encrypted secret.
76 + It will be copied into the Nix store of the orchestrating and of the target system.
77 + It must be decipherable by an OpenPGP key within the GnuPG home <filename>/var/lib/gnupg/</filename>.
78 + Defaults to the name of the secret,
79 + prefixed by the path of the <link linkend="opt-security.gnupg.store">store</link>
80 + and suffixed by <filename>.gpg</filename>.
81 + '';
82 + };
83 + mode = lib.mkOption {
84 + type = types.str;
85 + default = "400";
86 + description = ''
87 + Permission mode of the secret <literal>path</literal>.
88 + '';
89 + };
90 + user = lib.mkOption {
91 + type = types.str;
92 + default = "root";
93 + description = ''
94 + Owner of the secret <literal>path</literal>.
95 + '';
96 + };
97 + group = lib.mkOption {
98 + type = types.str;
99 + default = "root";
100 + description = ''
101 + Group of the secret <literal>path</literal>.
102 + '';
103 + };
104 + pipe = lib.mkOption {
105 + type = types.nullOr types.str;
106 + default = null;
107 + apply = x: if x == null then null else pkgs.writeShellScript "pipe" x;
108 + description = ''
109 + Shell script taking the deciphered secret on its standard input
110 + and which must put on its standard output
111 + the actual secret material to be installed.
112 + This allows to decorate the secret with non-secret bits.
113 + '';
114 + };
115 + path = lib.mkOption {
116 + type = types.str;
117 + default = name;
118 + apply = p: if match "^/.*" p == null then "/run/keys/gnupg/"+p+"/file" else p;
119 + description = ''
120 + The path on the target system where the secret is installed to.
121 + Default to the name of the secret,
122 + prefixed by <filename>/run/keys/gnupg/</filename>
123 + and suffixed by <filename>/file</filename>,
124 + if non-absolute.
125 + '';
126 + };
127 + service = lib.mkOption {
128 + type = types.str;
129 + default = "secret-" + escapeUnitName name + ".service";
130 + description = ''
131 + The name of the systemd service.
132 + Useful to put constraints like <literal>after</literal> or <literal>wants</wants>
133 + into services requiring this secret.
134 + '';
135 + };
136 + postStart = lib.mkOption {
137 + type = types.lines;
138 + default = "";
139 + example = "systemctl reload nginx.service";
140 + description = ''
141 + Commands to run after new secrets go live.
142 + Typically the web server and other servers using secrets
143 + need to be reloaded.
144 + '';
145 + };
146 + postStop = lib.mkOption {
147 + type = types.lines;
148 + default = "";
149 + example = ''shred -u "$secret_file"'';
150 + description = ''
151 + Commands to run after stopping the service.
152 + Typically removing a persistent secret.
153 + For convenience the <literal>path</literal> of the secret
154 + is provided in the shell variable <literal>secret_file</literal>.
155 + '';
156 + };
157 + };
158 + }));
159 + };
160 + enableGpgAgent = lib.mkEnableOption ''gpg-agent for decrypting secrets.
161 +
162 + Otherwise, you'll have to forward an <literal>agent-extra-socket</literal>:
163 + <screen>
164 + <prompt>$ </prompt>ssh -nNT root@example.org -o StreamLocalBindUnlink=yes -R /var/lib/gnupg/S.gpg-agent:$(gpgconf --list-dirs agent-extra-socket)
165 + </screen>
166 + '' // {default = true; example = false;};
167 + gpgAgentFlags = lib.mkOption {
168 + type = types.listOf types.str;
169 + default = [
170 + "--default-cache-ttl" "600"
171 + "--max-cache-ttl" "7200"
172 + ];
173 + description = ''
174 + Extra flags passed to the <literal>gpg-agent</literal>
175 + used to decrypt secrets.
176 + '';
177 + };
178 +};
179 +config = lib.mkIf (cfg.secrets != {}) {
180 + # Because /run/user/0 is wiped out by pam_systemd when root logouts,
181 + # systemd.services.gpg-agent cannot put its socket in
182 + # the path expected by gpg: /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts
183 + # derived from ${gnupgHome}, since this removal would kill gpg-agent.
184 + #
185 + # Unfortunately, for reaching such a persistent gpg-agent,
186 + # GPG_AGENT_INFO can no longer be used as it is ignored with gpg >= 2.1,
187 + # hence three different hacks are done here to make gpg connect
188 + # to ${gnupgHome}/S.gpg-agent depending on the concern:
189 + # - For gpg-agent, --supervised mode is used to pass it a socket
190 + # in the persistent directory gnupgHome.
191 + # - For the root user, on its login pam_systemd is mounting a fresh tmpfs on /run/user/0
192 + # so wrong perms are set on /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts
193 + # when /run/user/0 is mounted, by overriding user-runtime-dir@.service
194 + # - For secret decrypting services, /run/user/0/gnupg
195 + # is emptied and kept empty by privately mounting
196 + # an empty directory on it, using BindReadOnlyPaths=
197 + systemd.sockets."gpg-agent" = lib.optionalAttrs cfg.enableGpgAgent {
198 + description = "Socket for gpg-agent";
199 + wantedBy = ["sockets.target"];
200 + socketConfig.ListenStream = "${gnupgHome}/S.gpg-agent";
201 + socketConfig.SocketMode = "0600";
202 + };
203 + environment.systemPackages = [ pkgs.gnupg ];
204 + systemd.packages = [
205 + # Here, passing by systemd.packages is kind of a hack to be able to
206 + # write this file which is neither writable using environment.etc
207 + # (because environment.etc."systemd/system".source is set)
208 + # nor using systemd.services (because systemd.services."user-runtime-dir@0"
209 + # does not exist, and should not to keep using systemd's upstream template
210 + # and systemd.services."user-runtime-dir@").
211 + (pkgs.writeTextDir "etc/systemd/system/user-runtime-dir@0.service.d/override.conf" ''
212 + [Service]
213 + ExecStartPost=${pkgs.writeShellScript "redirect-gpg-agent-run-socket" ''
214 + install -D -d -m 640 /run/user/0/gnupg/d.6qoenf9br6fajbkknuz1i6ts
215 + ''}
216 + '')
217 + ];
218 + systemd.services =
219 + lib.optionalAttrs cfg.enableGpgAgent {
220 + gpg-agent = {
221 + description = "gpg-agent for decrypting GnuPG-protected secrets";
222 + requires = ["gpg-agent.socket"];
223 + serviceConfig = {
224 + Type = "simple";
225 + ExecStart = ''${pkgs.gnupg}/bin/gpg-agent \
226 + --supervised \
227 + --homedir '${gnupgHome}' \
228 + --allow-loopback-pinentry \
229 + --allow-preset-passphrase \
230 + ${lib.escapeShellArgs cfg.gpgAgentFlags}
231 + '';
232 + Restart = "on-failure";
233 + RestartSec = 5;
234 + StateDirectory = ["gnupg"];
235 + StateDirectoryMode = "700";
236 + };
237 + };
238 + } //
239 + lib.mapAttrs' (target: secret:
240 + lib.nameValuePair (lib.removeSuffix ".service" secret.service) {
241 + description = "Install secret ${secret.path}";
242 + after = ["gpg-agent.service"];
243 + wants = ["gpg-agent.service"];
244 + script = ''
245 + set -o pipefail
246 + set -eux
247 + decrypt() {
248 + ${pkgs.gnupg}/bin/gpg --homedir '${gnupgHome}' --no-autostart --batch --decrypt ${lib.escapeShellArg secret.gpg} |
249 + ${lib.optionalString (secret.pipe != null) (secret.pipe+" |")} \
250 + install -D -m '${secret.mode}' -o '${secret.user}' -g '${secret.group}' /dev/stdin ${lib.escapeShellArg secret.path}
251 + }
252 + while ! decrypt; do sleep $((1 + ($RANDOM % ${toString (length (attrNames cfg.secrets))}))); done
253 + '';
254 + postStart = lib.optionalString (secret.postStart != "") ''
255 + set -eux
256 + ${secret.postStart}
257 + '';
258 + postStop = lib.optionalString (secret.postStop != "") ''
259 + secret_file='${secret.path}'
260 + set -eux
261 + ${secret.postStop}
262 + '';
263 + serviceConfig = {
264 + Type = "oneshot";
265 + RemainAfterExit = true;
266 + PrivateTmp = true;
267 + InaccessiblePaths = [ "/run/user" ];
268 + } // lib.optionalAttrs (match "^/.*" target == null) {
269 + RuntimeDirectory = lib.removePrefix "/run/" (dirOf secret.path);
270 + RuntimeDirectoryMode = "711";
271 + RuntimeDirectoryPreserve = false;
272 + };
273 + }
274 + ) cfg.secrets;
275 +};
276 +meta.maintainers = with lib.maintainers; [ julm ];
277 +}
278 diff --git a/pkgs/tools/security/gnupg/22.nix b/pkgs/tools/security/gnupg/22.nix
279 index 7c095cffa31..f7f35d659c4 100644
280 --- a/pkgs/tools/security/gnupg/22.nix
281 +++ b/pkgs/tools/security/gnupg/22.nix
282 @@ -69,6 +69,9 @@ stdenv.mkDerivation rec {
283
284 # add gpg2 symlink to make sure git does not break when signing commits
285 ln -s $out/bin/gpg $out/bin/gpg2
286 +
287 + # Make libexec tools available in PATH
288 + ln -s -t $out/bin $out/libexec/*
289 '';
290
291 meta = with stdenv.lib; {