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