{ pkgs, config, lib, ... }: with lib; let # to be changed once the package is accepted connector = pkgs.callPackage ./_bitwarden-directory-connector.nix {}; cfg = config.services.bitwarden_connector; nodejs = pkgs.nodejs-18_x; ldap_data = '' { "ssl": ${boolToString cfg.ldap.ssl}, "startTls": ${boolToString cfg.ldap.startTls}, "sslAllowUnauthorized": ${boolToString cfg.ldap.startTls}, "port": ${toString cfg.ldap.port}, "currentUser": false, "ad": ${boolToString cfg.ldap.ad}, "pagedSearch": true, "password": "to_be_replaced", "hostname": "${cfg.ldap.hostname}", "rootPath": "${cfg.ldap.root}", "username": "${cfg.ldap.username}" } ''; sync_data = '' { "removeDisabled": ${boolToString cfg.sync.removeDisabled}, "overwriteExisting": ${boolToString cfg.sync.overwriteExisting}, "largeImport": ${boolToString cfg.sync.largeImport}, "creationDateAttribute": "${cfg.sync.creationDateAttribute}", "memberAttribute": "${cfg.sync.memberAttribute}", "useEmailPrefixSuffix": ${boolToString cfg.sync.emailPrefixSuffix.enable}, ${optionalString cfg.sync.emailPrefixSuffix.enable '' "emailPrefixAttribute": "${cfg.sync.emailPrefixSuffix.prefixAttribute}", "emailSuffix": "${cfg.sync.emailPrefixSuffix.suffix}", ''} "users": ${boolToString cfg.sync.users.enable}, ${optionalString cfg.sync.users.enable '' "userPath": "${cfg.sync.users.path}", "userObjectClass": "${cfg.sync.users.objectClass}", "userEmailAttribute": "${cfg.sync.users.emailAttribute}", "userFilter": "${cfg.sync.users.filter}", ''} "groups": ${boolToString cfg.sync.groups.enable}, ${optionalString cfg.sync.groups.enable '' "groupPath": "${cfg.sync.groups.path}", "groupObjectClass": "${cfg.sync.groups.objectClass}", "groupNameAttribute": "${cfg.sync.groups.nameAttribute}", "groupFilter": "${cfg.sync.groups.filter}", ''} "interval": 5 } ''; sed_string = string: builtins.replaceStrings ["." "/" "\n"] ["\\." "\\/" "\\n"] string; in { imports = []; options.services.bitwarden_connector = { enable = mkEnableOption "Bitwarden Directory Connector"; domain = mkOption { type = types.str; description = lib.mdDoc "The domain the Bitwarden/Vaultwarden is accessable on."; example = "https://vaultwarden.example.com"; }; user = mkOption { type = types.str; description = lib.mdDoc "User to run the program."; default = "bwdc"; }; directory = mkOption { type = types.str; description = lib.mdDoc "Folder to store the config file."; default = "/etc/bitwarden/${cfg.user}"; }; ldap = { ssl = mkOption { type = types.bool; default = false; description = lib.mdDoc "Use SSL."; }; startTls = mkOption { type = types.bool; default = false; description = lib.mdDoc "Use startTls."; }; sslAllowUnauthorized = mkOption { type = types.bool; default = false; description = lib.mdDoc ""; }; ad = mkOption { type = types.bool; default = false; description = lib.mdDoc "Is Active Directory."; }; port = mkOption { type = types.int; default = 389; description = lib.mdDoc "Port LDAP is accessable on"; }; hostname = mkOption { type = types.str; description = lib.mdDoc "The host the LDAP is accessable on."; example = "ldap.example.com"; }; root = mkOption { type = types.str; description = lib.mdDoc "Root path for LDAP"; example = "dc=example,dc=com"; }; username = mkOption { type = types.str; description = lib.mdDoc "The user to authenticate as."; example = "cn=admin,dc=example,dc=com"; }; pw_env = mkOption { type = types.str; description = lib.mdDoc "The ENV var that the ldap password is stored."; default = "LDAP_PW"; }; }; sync = { interval = mkOption { type = types.str; default = "*:0,15,30,45"; description = lib.mdDoc "When to run the connector, cron syntax."; }; removeDisabled = mkOption { type = types.bool; default = true; description = lib.mdDoc "Remove users from bitwarden groups if no longer in the ldap group."; }; overwriteExisting = mkOption { type = types.bool; default = false; description = lib.mdDoc "Remove and re-add users/groups, See https://bitwarden.com/help/user-group-filters/#overwriting-syncs for more details."; }; largeImport = mkOption { type = types.bool; default = false; description = lib.mdDoc "Enable if you ar syncing more than 2000 users/groups."; }; memberAttribute = mkOption { type = types.str; description = lib.mdDoc "Attribute that lists members in a LDAP group."; example = "uniqueMember"; }; creationDateAttribute = mkOption { type = types.str; description = lib.mdDoc "Attribute that lists a users creation date."; example = "whenCreated"; }; emailPrefixSuffix = { enable = mkOption { type = types.bool; default = false; description = lib.mdDoc "If a user has no email address, combine a username prefix with a suffix value to form an email."; }; prefixAttribute = mkOption { type = types.str; description = lib.mdDoc "Attribute that has a users username."; example = "accountName"; }; suffix = mkOption { type = types.str; description = lib.mdDoc "Suffix for the email, normally @example.com."; example = "@example.com"; }; }; users = { enable = mkOption { type = types.bool; default = false; description = lib.mdDoc "Sync users."; }; path = mkOption { type = types.str; description = lib.mdDoc "User directory, relative to root."; example = "ou=users"; }; objectClass = mkOption { type = types.str; description = lib.mdDoc "A class that users will have."; example = "inetOrgPerson"; }; emailAttribute = mkOption { type = types.str; description = lib.mdDoc "Attribute for a users email."; example = "mail"; }; filter = mkOption { type = types.str; description = lib.mdDoc "Filter for users."; example = "(memberOf=cn=sales,ou=groups,dc=example,dc=com)"; }; }; groups = { enable = mkOption { type = types.bool; default = false; description = lib.mdDoc "Sync groups."; }; path = mkOption { type = types.str; description = lib.mdDoc "Group directory, relative to root."; example = "ou=groups"; }; objectClass = mkOption { type = types.str; description = lib.mdDoc "A class that groups will have."; example = "groupOfNames"; }; nameAttribute = mkOption { type = types.str; description = lib.mdDoc "Attribute for a name of group."; example = "cn"; }; filter = mkOption { type = types.str; description = lib.mdDoc "Filter for groups."; example = "(cn=sales)"; }; }; }; env = { description = "Env files to be passed in."; ldap = mkOption rec { type = types.str; description = "Auth for the LDAP, has ${cfg.ldap.pw_env}"; }; bitwarden = mkOption rec { type = types.str; description = "Auth for Bitwarden, has BW_CLIENTID and BW_CLIENTSECRET"; }; }; }; config = mkIf cfg.enable { users.groups."${cfg.user}" = {}; users.users."${cfg.user}" = { createHome = true; isSystemUser = true; home = "${cfg.directory}"; group = "${cfg.user}"; homeMode = "711"; }; systemd = { timers."${cfg.user}" = { description = "Timer for ${cfg.user}"; wantedBy = ["timers.target"]; partOf = ["${cfg.user}.service"]; timerConfig = { OnCalendar = cfg.sync.interval; Unit = "${cfg.user}.service"; Persistent = true; }; }; services."${cfg.user}" = { description = "Main process for Bitwarden Directory Connector"; wantedBy = ["multi-user.target"]; after = ["network-online.target"]; wants = []; environment = { BITWARDENCLI_CONNECTOR_APPDATA_DIR = cfg.directory; BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS = "true"; }; serviceConfig = { Type = "oneshot"; User = "${cfg.user}"; Group = "${cfg.user}"; ExecStartPre = pkgs.writeShellScript "${cfg.user}-config" '' # create the config file ${nodejs}/bin/node ${connector}/build-cli/bwdc.js data-file ${nodejs}/bin/node ${connector}/build-cli/bwdc.js config server ${cfg.domain} # now login to set credentials ${nodejs}/bin/node ${connector}/build-cli/bwdc.js login # set the ldap details sed -i 's/"ldap": null/"ldap": ${sed_string ldap_data}/' ${cfg.directory}/data.json # set the client id orgID=$(echo $BW_CLIENTID | sed 's/organization\.//g') sed -i "s/\"organizationId\": null/\"organizationId\": \"$orgID\"/" ${cfg.directory}/data.json # and sync data sed -i 's/"sync": null/"sync": ${sed_string sync_data}/' ${cfg.directory}/data.json # final config ${nodejs}/bin/node ${connector}/build-cli/bwdc.js config directory 0 ${nodejs}/bin/node ${connector}/build-cli/bwdc.js config ldap.password --secretenv ${cfg.ldap.pw_env} ''; ExecStart = ''${nodejs}/bin/node ${connector}/build-cli/bwdc.js sync''; EnvironmentFile = [ "${cfg.env.ldap}" "${cfg.env.bitwarden}" ]; }; }; }; }; }