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" { };
19 description = "The domain the Bitwarden/Vaultwarden is accessible on.";
20 example = "https://vaultwarden.example.com";
25 description = "User to run the program.";
31 default = "*:0,15,30,45";
32 description = "The interval when to run the connector. This uses systemd's OnCalendar syntax.";
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`.
41 type = types.submodule (
48 freeformType = types.attrsOf (pkgs.formats.json { }).type;
50 config.finalJSON = builtins.toJSON (
52 filter (x: x == "finalJSON" || !options.${x}.isDefined or false) (attrNames options)
57 finalJSON = mkOption {
58 type = (pkgs.formats.json { }).type;
67 description = "Whether to use TLS.";
72 description = "Whether to use STARTTLS.";
77 description = "The host the LDAP is accessible on.";
78 example = "ldap.example.com";
84 description = "Port LDAP is accessible on.";
90 description = "Whether the LDAP Server is an Active Directory.";
93 pagedSearch = mkOption {
96 description = "Whether the LDAP server paginates search results.";
101 description = "Root path for LDAP.";
102 example = "dc=example,dc=com";
105 username = mkOption {
107 description = "The user to authenticate as.";
108 example = "cn=admin,dc=example,dc=com";
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`.
121 type = types.submodule (
128 freeformType = types.attrsOf (pkgs.formats.json { }).type;
130 config.finalJSON = builtins.toJSON (
132 filter (x: x == "finalJSON" || !options.${x}.isDefined or false) (attrNames options)
137 finalJSON = mkOption {
138 type = (pkgs.formats.json { }).type;
144 removeDisabled = mkOption {
147 description = "Remove users from bitwarden groups if no longer in the ldap group.";
150 overwriteExisting = mkOption {
153 description = "Remove and re-add users/groups, See https://bitwarden.com/help/user-group-filters/#overwriting-syncs for more details.";
156 largeImport = mkOption {
159 description = "Enable if you are syncing more than 2000 users/groups.";
162 memberAttribute = mkOption {
164 description = "Attribute that lists members in a LDAP group.";
165 example = "uniqueMember";
168 creationDateAttribute = mkOption {
170 description = "Attribute that lists a user's creation date.";
171 example = "whenCreated";
174 useEmailPrefixSuffix = mkOption {
177 description = "If a user has no email address, combine a username prefix with a suffix value to form an email.";
179 emailPrefixAttribute = mkOption {
181 description = "The attribute that contains the users username.";
182 example = "accountName";
184 emailSuffix = mkOption {
186 description = "Suffix for the email, normally @example.com.";
187 example = "@example.com";
193 description = "Sync users.";
195 userPath = mkOption {
197 description = "User directory, relative to root.";
198 default = "ou=users";
200 userObjectClass = mkOption {
202 description = "Class that users must have.";
203 default = "inetOrgPerson";
205 userEmailAttribute = mkOption {
207 description = "Attribute for a users email.";
210 userFilter = mkOption {
212 description = "LDAP filter for users.";
213 example = "(memberOf=cn=sales,ou=groups,dc=example,dc=com)";
220 description = "Whether to sync ldap groups into BitWarden.";
222 groupPath = mkOption {
224 description = "Group directory, relative to root.";
225 default = "ou=groups";
227 groupObjectClass = mkOption {
229 description = "A class that groups will have.";
230 default = "groupOfNames";
232 groupNameAttribute = mkOption {
234 description = "Attribute for a name of group.";
237 groupFilter = mkOption {
239 description = "LDAP filter for groups.";
240 example = "(cn=sales)";
251 description = "Path to file that contains LDAP password for user in {option}`ldap.username";
255 client_path_id = mkOption {
257 description = "Path to file that contains Client ID.";
259 client_path_secret = mkOption {
261 description = "Path to file that contains Client Secret.";
267 config = mkIf cfg.enable {
268 users.groups."${cfg.user}" = { };
269 users.users."${cfg.user}" = {
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" ];
281 OnCalendar = cfg.interval;
282 Unit = "bitwarden-directory-connector-cli.service";
287 services.bitwarden-directory-connector-cli = {
288 description = "Main process for Bitwarden Directory Connector";
292 BITWARDENCLI_CONNECTOR_APPDATA_DIR = "/tmp";
293 BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS = "true";
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} \
321 mv -f /tmp/data.json.tmp /tmp/data.json
324 ${lib.getExe cfg.package} config directory 0
325 ${lib.getExe cfg.package} config ldap.password --secretfile ${cfg.secrets.ldap}
330 User = "${cfg.user}";
332 ExecStart = "${lib.getExe cfg.package} sync";
338 meta.maintainers = with maintainers; [ Silver-Golden ];