dput-ng: fix eval (#364540)
[NixPkgs.git] / nixos / modules / services / security / bitwarden-directory-connector-cli.nix
blob633563a115618186f18e78c1260c018e1e5d677e
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
7 with lib;
8 let
9   cfg = config.services.bitwarden-directory-connector-cli;
12   options.services.bitwarden-directory-connector-cli = {
13     enable = mkEnableOption "Bitwarden Directory Connector";
15     package = mkPackageOption pkgs "bitwarden-directory-connector-cli" { };
17     domain = mkOption {
18       type = types.str;
19       description = "The domain the Bitwarden/Vaultwarden is accessible on.";
20       example = "https://vaultwarden.example.com";
21     };
23     user = mkOption {
24       type = types.str;
25       description = "User to run the program.";
26       default = "bwdc";
27     };
29     interval = mkOption {
30       type = types.str;
31       default = "*:0,15,30,45";
32       description = "The interval when to run the connector. This uses systemd's OnCalendar syntax.";
33     };
35     ldap = mkOption {
36       description = ''
37         Options to configure the LDAP connection.
38         If you used the desktop application to test the configuration you can find the settings by searching for `ldap` in `~/.config/Bitwarden\ Directory\ Connector/data.json`.
39       '';
40       default = { };
41       type = types.submodule (
42         {
43           config,
44           options,
45           ...
46         }:
47         {
48           freeformType = types.attrsOf (pkgs.formats.json { }).type;
50           config.finalJSON = builtins.toJSON (
51             removeAttrs config (
52               filter (x: x == "finalJSON" || !options.${x}.isDefined or false) (attrNames options)
53             )
54           );
56           options = {
57             finalJSON = mkOption {
58               type = (pkgs.formats.json { }).type;
59               internal = true;
60               readOnly = true;
61               visible = false;
62             };
64             ssl = mkOption {
65               type = types.bool;
66               default = false;
67               description = "Whether to use TLS.";
68             };
69             startTls = mkOption {
70               type = types.bool;
71               default = false;
72               description = "Whether to use STARTTLS.";
73             };
75             hostname = mkOption {
76               type = types.str;
77               description = "The host the LDAP is accessible on.";
78               example = "ldap.example.com";
79             };
81             port = mkOption {
82               type = types.port;
83               default = 389;
84               description = "Port LDAP is accessible on.";
85             };
87             ad = mkOption {
88               type = types.bool;
89               default = false;
90               description = "Whether the LDAP Server is an Active Directory.";
91             };
93             pagedSearch = mkOption {
94               type = types.bool;
95               default = false;
96               description = "Whether the LDAP server paginates search results.";
97             };
99             rootPath = mkOption {
100               type = types.str;
101               description = "Root path for LDAP.";
102               example = "dc=example,dc=com";
103             };
105             username = mkOption {
106               type = types.str;
107               description = "The user to authenticate as.";
108               example = "cn=admin,dc=example,dc=com";
109             };
110           };
111         }
112       );
113     };
115     sync = mkOption {
116       description = ''
117         Options to configure what gets synced.
118         If you used the desktop application to test the configuration you can find the settings by searching for `sync` in `~/.config/Bitwarden\ Directory\ Connector/data.json`.
119       '';
120       default = { };
121       type = types.submodule (
122         {
123           config,
124           options,
125           ...
126         }:
127         {
128           freeformType = types.attrsOf (pkgs.formats.json { }).type;
130           config.finalJSON = builtins.toJSON (
131             removeAttrs config (
132               filter (x: x == "finalJSON" || !options.${x}.isDefined or false) (attrNames options)
133             )
134           );
136           options = {
137             finalJSON = mkOption {
138               type = (pkgs.formats.json { }).type;
139               internal = true;
140               readOnly = true;
141               visible = false;
142             };
144             removeDisabled = mkOption {
145               type = types.bool;
146               default = true;
147               description = "Remove users from bitwarden groups if no longer in the ldap group.";
148             };
150             overwriteExisting = mkOption {
151               type = types.bool;
152               default = false;
153               description = "Remove and re-add users/groups, See https://bitwarden.com/help/user-group-filters/#overwriting-syncs for more details.";
154             };
156             largeImport = mkOption {
157               type = types.bool;
158               default = false;
159               description = "Enable if you are syncing more than 2000 users/groups.";
160             };
162             memberAttribute = mkOption {
163               type = types.str;
164               description = "Attribute that lists members in a LDAP group.";
165               example = "uniqueMember";
166             };
168             creationDateAttribute = mkOption {
169               type = types.str;
170               description = "Attribute that lists a user's creation date.";
171               example = "whenCreated";
172             };
174             useEmailPrefixSuffix = mkOption {
175               type = types.bool;
176               default = false;
177               description = "If a user has no email address, combine a username prefix with a suffix value to form an email.";
178             };
179             emailPrefixAttribute = mkOption {
180               type = types.str;
181               description = "The attribute that contains the users username.";
182               example = "accountName";
183             };
184             emailSuffix = mkOption {
185               type = types.str;
186               description = "Suffix for the email, normally @example.com.";
187               example = "@example.com";
188             };
190             users = mkOption {
191               type = types.bool;
192               default = false;
193               description = "Sync users.";
194             };
195             userPath = mkOption {
196               type = types.str;
197               description = "User directory, relative to root.";
198               default = "ou=users";
199             };
200             userObjectClass = mkOption {
201               type = types.str;
202               description = "Class that users must have.";
203               default = "inetOrgPerson";
204             };
205             userEmailAttribute = mkOption {
206               type = types.str;
207               description = "Attribute for a users email.";
208               default = "mail";
209             };
210             userFilter = mkOption {
211               type = types.str;
212               description = "LDAP filter for users.";
213               example = "(memberOf=cn=sales,ou=groups,dc=example,dc=com)";
214               default = "";
215             };
217             groups = mkOption {
218               type = types.bool;
219               default = false;
220               description = "Whether to sync ldap groups into BitWarden.";
221             };
222             groupPath = mkOption {
223               type = types.str;
224               description = "Group directory, relative to root.";
225               default = "ou=groups";
226             };
227             groupObjectClass = mkOption {
228               type = types.str;
229               description = "A class that groups will have.";
230               default = "groupOfNames";
231             };
232             groupNameAttribute = mkOption {
233               type = types.str;
234               description = "Attribute for a name of group.";
235               default = "cn";
236             };
237             groupFilter = mkOption {
238               type = types.str;
239               description = "LDAP filter for groups.";
240               example = "(cn=sales)";
241               default = "";
242             };
243           };
244         }
245       );
246     };
248     secrets = {
249       ldap = mkOption {
250         type = types.str;
251         description = "Path to file that contains LDAP password for user in {option}`ldap.username";
252       };
254       bitwarden = {
255         client_path_id = mkOption {
256           type = types.str;
257           description = "Path to file that contains Client ID.";
258         };
259         client_path_secret = mkOption {
260           type = types.str;
261           description = "Path to file that contains Client Secret.";
262         };
263       };
264     };
265   };
267   config = mkIf cfg.enable {
268     users.groups."${cfg.user}" = { };
269     users.users."${cfg.user}" = {
270       isSystemUser = true;
271       group = cfg.user;
272     };
274     systemd = {
275       timers.bitwarden-directory-connector-cli = {
276         description = "Sync timer for Bitwarden Directory Connector";
277         wantedBy = [ "timers.target" ];
278         after = [ "network-online.target" ];
279         wants = [ "network-online.target" ];
280         timerConfig = {
281           OnCalendar = cfg.interval;
282           Unit = "bitwarden-directory-connector-cli.service";
283           Persistent = true;
284         };
285       };
287       services.bitwarden-directory-connector-cli = {
288         description = "Main process for Bitwarden Directory Connector";
289         path = [ pkgs.jq ];
291         environment = {
292           BITWARDENCLI_CONNECTOR_APPDATA_DIR = "/tmp";
293           BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS = "true";
294         };
296         preStart = ''
297           set -eo pipefail
299           # create the config file
300           ${lib.getExe cfg.package} data-file
301           touch /tmp/data.json.tmp
302           chmod 600 /tmp/data.json{,.tmp}
304           ${lib.getExe cfg.package} config server ${cfg.domain}
306           # now login to set credentials
307           export BW_CLIENTID="$(< ${escapeShellArg cfg.secrets.bitwarden.client_path_id})"
308           export BW_CLIENTSECRET="$(< ${escapeShellArg cfg.secrets.bitwarden.client_path_secret})"
309           ${lib.getExe cfg.package} login
311           jq '.authenticatedAccounts[0] as $account
312             | .[$account].directoryConfigurations.ldap |= $ldap_data
313             | .[$account].directorySettings.organizationId |= $orgID
314             | .[$account].directorySettings.sync |= $sync_data' \
315             --argjson ldap_data ${escapeShellArg cfg.ldap.finalJSON} \
316             --arg orgID "''${BW_CLIENTID//organization.}" \
317             --argjson sync_data ${escapeShellArg cfg.sync.finalJSON} \
318             /tmp/data.json \
319             > /tmp/data.json.tmp
321           mv -f /tmp/data.json.tmp /tmp/data.json
323           # final config
324           ${lib.getExe cfg.package} config directory 0
325           ${lib.getExe cfg.package} config ldap.password --secretfile ${cfg.secrets.ldap}
326         '';
328         serviceConfig = {
329           Type = "oneshot";
330           User = "${cfg.user}";
331           PrivateTmp = true;
332           ExecStart = "${lib.getExe cfg.package} sync";
333         };
334       };
335     };
336   };
338   meta.maintainers = with maintainers; [ Silver-Golden ];