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