]> Git — Sourcephile - sourcephile-nix.git/blob - nixpkgs/patches/security.pass.diff
wireguard: improve initrd setup
[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..28a0af36ac5
16 --- /dev/null
17 +++ b/nixos/modules/security/pass.nix
18 @@ -0,0 +1,216 @@
19 +{ config, lib, pkgs, ... }:
20 +let
21 + inherit (builtins) dirOf head listToAttrs match split;
22 + inherit (lib) types;
23 + cfg = 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 + default = lib.maybeEnv "PASSWORD_STORE_DIR" ".password-store";
33 + description = ''
34 + Default path to the password-store of the orchestrating system.
35 + '';
36 + # Does not copy the entire password-store into the Nix store,
37 + # only the keys actually used will be.
38 + apply = toString;
39 + };
40 + passphraseFile = lib.mkOption {
41 + type = types.str;
42 + default = "/root/key.pass";
43 + description = ''
44 + The directory on the target system to a file containing
45 + the password of an OpenPGP key in <literal>gnupgHome</literal>,
46 + to which <literal>gpg</literal> secret is encrypted to.
47 + Set it to a temporary directory like <literal>/run/keys/key.pass</literal>
48 + if you don't want it to persist accross reboot.
49 + It can be customized per secret.
50 + '';
51 + };
52 + gnupgHome = lib.mkOption {
53 + type = types.str;
54 + default = "/root/.gnupg";
55 + description = ''
56 + The directory on the target system to the <literal>gnupg</literal> home
57 + used to decrypt the secret.
58 + It can be customized per secret.
59 + '';
60 + };
61 + secrets = lib.mkOption {
62 + description = "Available secrets.";
63 + default = {};
64 + example = {
65 + "/root/.ssh/id_ed25519" = {};
66 + "knot/tsig/example.org/acme.conf" = {
67 + user = "knot";
68 + };
69 + "lego/example.org/rfc2136" = {
70 + pipe = "
71 + cat -
72 + cat <EOF
73 + RFC2136_NAMESERVER=ns.example.org:53
74 + RFC2136_TSIG_ALGORITHM=hmac-sha256.
75 + RFC2136_TSIG_KEY=acme_example_org
76 + RFC2136_PROPAGATION_TIMEOUT=1000
77 + RFC2136_POLLING_INTERVAL=30
78 + RFC2136_SEQUENCE_INTERVAL=30
79 + RFC2136_DNS_TIMEOUT=1000
80 + RFC2136_TTL=1
81 + EOF
82 + ";
83 + };
84 + };
85 + type = types.attrsOf (types.submodule ({name, config, ...}: {
86 + options = {
87 + gpg = lib.mkOption {
88 + type = types.path;
89 + default = builtins.path {
90 + path = cfg.store + "/${name}.gpg";
91 + name = "${escapeUnitName name}.gpg";
92 + };
93 + description = ''
94 + The path to the gnupg-encrypted secret.
95 + It will be copied into the Nix store of the orchestrating and of the target system.
96 + It must be decipherable by an OpenPGP key within <literal>gnupgHome</literal>,
97 + whose passhrase is on the target system into <literal>passphraseFile</literal>.
98 + Defaults to the name of the secret, prefixed by <literal>passwordStore</literal>
99 + and suffixed by <literal>.gpg</literal>.
100 + '';
101 + };
102 + gnupgHome = lib.mkOption {
103 + type = types.str;
104 + default = cfg.gnupgHome;
105 + description = ''
106 + Custom <literal>gnupgHome</literal> for this secret.
107 + '';
108 + };
109 + passphraseFile = lib.mkOption {
110 + type = types.str;
111 + default = cfg.passphraseFile;
112 + description = ''
113 + Custom <literal>passphraseFile</literal> for this secret.
114 + '';
115 + };
116 + mode = lib.mkOption {
117 + type = types.str;
118 + default = "400";
119 + description = ''
120 + Permission mode of the secret <literal>path</literal>.
121 + '';
122 + };
123 + user = lib.mkOption {
124 + type = types.str;
125 + default = "root";
126 + description = ''
127 + Owner of the secret <literal>path</literal>.
128 + '';
129 + };
130 + group = lib.mkOption {
131 + type = types.str;
132 + default = "root";
133 + description = ''
134 + Group of the secret <literal>path</literal>.
135 + '';
136 + };
137 + pipe = lib.mkOption {
138 + type = types.nullOr types.str;
139 + default = null;
140 + apply = x: if x == null then null else pkgs.writeShellScript "pipe" x;
141 + description = ''
142 + Shell script taking the deciphered secret on its standard input
143 + and which must put on its standard output
144 + the actual secret material to be installed.
145 + This allows to decorate the secret with non-secret bits.
146 + '';
147 + };
148 + path = lib.mkOption {
149 + type = types.str;
150 + default = name;
151 + apply = p: if match "^/.*" p == null then "/run/pass-secrets/"+p+"/file" else p;
152 + description = ''
153 + The path on the target system where the secret is installed to.
154 + Any non-absolute path is relative to <filename>/run/pass-secrets</filename>.
155 + Default to the name of the secret.
156 + '';
157 + };
158 + service = lib.mkOption {
159 + type = types.str;
160 + default = "secret-" + escapeUnitName name + ".service";
161 + description = ''
162 + The name of the systemd service.
163 + Useful to put constraints like <literal>after</literal> or <literal>wants</wants>
164 + into services requiring this secret.
165 + '';
166 + };
167 + postStart = lib.mkOption {
168 + type = types.lines;
169 + default = "";
170 + example = "systemctl reload nginx.service";
171 + description = ''
172 + Commands to run after new secrets go live.
173 + Typically the web server and other servers using secrets
174 + need to be reloaded.
175 + '';
176 + };
177 + postStop = lib.mkOption {
178 + type = types.lines;
179 + default = "";
180 + example = ''shred -u "$secret_file"'';
181 + description = ''
182 + Commands to run after stopping the service.
183 + Typically removing a persistent secret.
184 + For convenience the <literal>path</literal> of the secret
185 + is provided in the shell variable <literal>secret_file</literal>.
186 + '';
187 + };
188 + };
189 + }));
190 + };
191 +};
192 +config = lib.mkIf (cfg.secrets != {}) {
193 + systemd.services =
194 + lib.mapAttrs' (target: secret:
195 + lib.nameValuePair (lib.removeSuffix ".service" secret.service) {
196 + description = "Install secret ${secret.path}";
197 + script = ''
198 + set -o pipefail
199 + set -eux
200 + decrypt() {
201 + ${pkgs.gnupg}/bin/gpg --batch --pinentry-mode loopback \
202 + --passphrase-file '${secret.passphraseFile}' \
203 + --decrypt '${secret.gpg}' \
204 + ${lib.optionalString (secret.pipe != null) (" | "+secret.pipe)} |
205 + install -D -m '${secret.mode}' -o '${secret.user}' -g '${secret.group}' /dev/stdin \
206 + '${secret.path}'
207 + }
208 + while ! decrypt; do sleep $((1 + ($RANDOM % 10))); done
209 + '';
210 + postStart = lib.optionalString (secret.postStart != "") ''
211 + set -eux
212 + ${secret.postStart}
213 + '';
214 + postStop = lib.optionalString (secret.postStop != "") ''
215 + secret_file='${secret.path}'
216 + set -eux
217 + ${secret.postStop}
218 + '';
219 + serviceConfig = {
220 + Type = "oneshot";
221 + Environment = "GNUPGHOME=${secret.gnupgHome}";
222 + PrivateTmp = true;
223 + RemainAfterExit = true;
224 + WorkingDirectory = dirOf secret.gnupgHome;
225 + } // lib.optionalAttrs (match "^/.*" target == null) {
226 + RuntimeDirectory = lib.removePrefix "/run/" (dirOf secret.path);
227 + RuntimeDirectoryMode = "711";
228 + RuntimeDirectoryPreserve = false;
229 + };
230 + }
231 + ) cfg.secrets;
232 +};
233 +meta.maintainers = with lib.maintainers; [ julm ];
234 +}