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