diff --git a/applications/bitwarden/_bitwarden_sync_module.nix b/applications/bitwarden/_bitwarden_sync_module.nix index 75a977e..2193603 100644 --- a/applications/bitwarden/_bitwarden_sync_module.nix +++ b/applications/bitwarden/_bitwarden_sync_module.nix @@ -7,49 +7,26 @@ with lib; let cfg = config.services.bitwarden_directory_connector; - ldap_data = builtins.toJSON { - ssl = cfg.ldap.ssl; - startTls = cfg.ldap.startTls; - sslAllowUnauthorized = cfg.ldap.sslAllowUnauthorized; - port = cfg.ldap.port; - currentUser = false; - ad = cfg.ldap.ad; - pagedSearch = true; - password = "to_be_replaced"; - hostname = cfg.ldap.hostname; - rootPath = cfg.ldap.root; - username = cfg.ldap.username; - }; + ldap_data = builtins.toJSON cfg.ldap; + sync_data = builtins.toJSON cfg.sync; - sync_data = - builtins.toJSON - ({ - removeDisabled = cfg.sync.removeDisabled; - overwriteExisting = cfg.sync.overwriteExisting; - largeImport = cfg.sync.largeImport; - creationDateAttribute = cfg.sync.creationDateAttribute; - memberAttribute = cfg.sync.memberAttribute; - interval = 5; - useEmailPrefixSuffix = cfg.sync.emailPrefixSuffix.enable; - users = cfg.sync.users.enable; - groups = cfg.sync.groups.enable; - } - // optionalAttrs cfg.sync.emailPrefixSuffix.enable { - emailPrefixAttribute = cfg.sync.emailPrefixSuffix.prefixAttribute; - emailSuffix = cfg.sync.emailPrefixSuffix.suffix; - } - // optionalAttrs cfg.sync.users.enable { - userPath = cfg.sync.users.path; - userObjectClass = cfg.sync.users.objectClass; - userEmailAttribute = cfg.sync.users.emailAttribute; - userFilter = cfg.sync.users.filter; - } - // optionalAttrs cfg.sync.groups.enable { - groupPath = cfg.sync.groups.path; - groupObjectClass = cfg.sync.groups.objectClass; - groupNameAttribute = cfg.sync.groups.nameAttribute; - groupFilter = cfg.sync.groups.filter; - }); + # coping directly from nix https://github.com/NixOS/nixpkgs/blob/da4024d0ead5d7820f6bd15147d3fe2a0c0cec73/nixos/modules/config/nix.nix#L62C1-L76C49 + semanticConfType = with types; let + confAtom = + nullOr + (oneOf [ + bool + int + float + str + path + package + ]) + // { + description = "Nix config atom (null, bool, int, float, str, path or package)"; + }; + in + attrsOf (either confAtom (listOf confAtom)); json_string = string: builtins.replaceStrings ["\""] ["\\\""] string; in { @@ -90,161 +67,184 @@ in { default = "/etc/bitwarden/bwdc"; }; - 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"; - }; + pw_env = mkOption { + type = types.str; + description = lib.mdDoc "The ENV var that the ldap password is stored."; + default = "LDAP_PW"; + }; + interval = mkOption { + type = types.str; + default = "*:0,15,30,45"; + description = lib.mdDoc "When to run the connector, OnCalendar syntax."; + }; - root = mkOption { - type = types.str; - description = lib.mdDoc "Root path for LDAP"; - example = "dc=example,dc=com"; - }; + ldap = mkOption { + description = lib.mdDoc "Options to configurate LDAP."; + type = types.submodule { + freeformType = semanticConfType; - 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"; + options = { + 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 ""; + }; + port = mkOption { + type = types.int; + default = 389; + description = lib.mdDoc "Port LDAP is accessable on"; + }; + currentUser = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Unknown what this does."; + }; + ad = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Is Active Directory."; + }; + pagedSearch = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "The LDAP server paginates search results."; + }; + + hostname = mkOption { + type = types.str; + description = lib.mdDoc "The host the LDAP is accessable on."; + example = "ldap.example.com"; + }; + + rootPath = 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"; + }; + }; }; }; - sync = { - interval = mkOption { - type = types.str; - default = "*:0,15,30,45"; - description = lib.mdDoc "When to run the connector, OnCalendar 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 are syncing more than 2000 users/groups."; - }; + sync = mkOption { + description = lib.mdDoc "Options to configurate what gets synced."; + type = types.submodule { + freeformType = semanticConfType; - memberAttribute = mkOption { - type = types.str; - description = lib.mdDoc "Attribute that lists members in a LDAP group."; - example = "uniqueMember"; - }; + options = { + 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 are syncing more than 2000 users/groups."; + }; - creationDateAttribute = mkOption { - type = types.str; - description = lib.mdDoc "Attribute that lists a users creation date."; - example = "whenCreated"; - }; + memberAttribute = mkOption { + type = types.str; + description = lib.mdDoc "Attribute that lists members in a LDAP group."; + example = "uniqueMember"; + }; - 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"; - }; - }; + creationDateAttribute = mkOption { + type = types.str; + description = lib.mdDoc "Attribute that lists a users creation date."; + example = "whenCreated"; + }; - 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)"; + useEmailPrefixSuffix = 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."; + }; + emailPrefixAttribute = mkOption { + type = types.str; + description = lib.mdDoc "Attribute that has a users username."; + default = "accountName"; + }; + emailSuffix = mkOption { + type = types.str; + description = lib.mdDoc "Suffix for the email, normally @example.com."; + default = "@example.com"; + }; + + users = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Sync users."; + }; + userPath = mkOption { + type = types.str; + description = lib.mdDoc "User directory, relative to root."; + default = "ou=users"; + }; + userObjectClass = mkOption { + type = types.str; + description = lib.mdDoc "A class that users will have."; + default = "inetOrgPerson"; + }; + userEmailAttribute = mkOption { + type = types.str; + description = lib.mdDoc "Attribute for a users email."; + default = "mail"; + }; + userFilter = mkOption { + type = types.str; + description = lib.mdDoc "Filter for users."; + example = "(memberOf=cn=sales,ou=groups,dc=example,dc=com)"; + default = ""; + }; + + groups = mkOption { + type = types.bool; + default = false; + description = lib.mdDoc "Sync groups."; + }; + groupPath = mkOption { + type = types.str; + description = lib.mdDoc "Group directory, relative to root."; + default = "ou=groups"; + }; + groupObjectClass = mkOption { + type = types.str; + description = lib.mdDoc "A class that groups will have."; + default = "groupOfNames"; + }; + groupNameAttribute = mkOption { + type = types.str; + description = lib.mdDoc "Attribute for a name of group."; + default = "cn"; + }; + groupFilter = mkOption { + type = types.str; + description = lib.mdDoc "Filter for groups."; + example = "(cn=sales)"; + default = ""; + }; }; }; }; @@ -252,7 +252,7 @@ in { env = { ldap = mkOption rec { type = types.str; - description = "Auth for the LDAP, has value defined in {option}`ldap.pw_env"; + description = "Auth for the LDAP, has value defined in {option}`pw_env"; }; bitwarden = mkOption rec { type = types.str; @@ -278,7 +278,7 @@ in { wantedBy = ["timers.target"]; partOf = ["bitwarden_directory_connector.service"]; timerConfig = { - OnCalendar = cfg.sync.interval; + OnCalendar = cfg.interval; Unit = "bitwarden_directory_connector.service"; Persistent = true; }; @@ -309,27 +309,21 @@ in { # now login to set credentials ${cfg.package}/bin/${cfg.binary_name} login - # set the ldap details - account=$(jq '.authenticatedAccounts[0]?' ${cfg.directory}/data.json) - jq ".[$account].directoryConfigurations.ldap |= ${json_string ldap_data}" ${cfg.directory}/data.json > ${cfg.directory}/data1.json + jq '.authenticatedAccounts[0] as $account + | .[$account].directoryConfigurations.ldap |= $ldap_data + | .[$account].directorySettings.organizationId |= $orgID + | .[$account].directorySettings.sync |= $sync_data' \ + --argjson ldap_data ${escapeShellArg ldap_data} \ + --arg orgID "''${BW_CLIENTID//organization.}" \ + --argjson sync_data ${escapeShellArg sync_data} \ + ${escapeShellArg cfg.directory}/data.json \ + > ${escapeShellArg cfg.directory}/data.json.tmp - # remove the original - rm -f ${cfg.directory}/data.json - - # set the client id - orgID=$(echo $BW_CLIENTID | sed 's/organization\.//g') - jq ".[$account].directorySettings.organizationId |= \"$orgID\" " ${cfg.directory}/data1.json > ${cfg.directory}/data2.json - - # and sync data - jq ".[$account].directorySettings.sync |= ${json_string sync_data}" ${cfg.directory}/data2.json > ${cfg.directory}/data.json + mv -f -- ${escapeShellArg cfg.directory}/data.json.tmp ${escapeShellArg cfg.directory}/data.json # final config ${cfg.package}/bin/${cfg.binary_name} config directory 0 - ${cfg.package}/bin/${cfg.binary_name} config ldap.password --secretenv ${cfg.ldap.pw_env} - - # cleanup temp files - rm -f ${cfg.directory}/data1.json - rm -f ${cfg.directory}/data2.json + ${cfg.package}/bin/${cfg.binary_name} config ldap.password --secretenv ${cfg.pw_env} ''; ExecStart = ''${cfg.package}/bin/${cfg.binary_name} sync''; diff --git a/applications/bitwarden/bitwarden_sync.nix b/applications/bitwarden/bitwarden_sync.nix index fb013e0..983904c 100644 --- a/applications/bitwarden/bitwarden_sync.nix +++ b/applications/bitwarden/bitwarden_sync.nix @@ -22,6 +22,8 @@ in { package = pkgs.callPackage ./_bitwarden-directory-connector.nix {}; + pw_env = "LDAP_ADMIN_PW"; + ldap = { ssl = false; startTls = false; @@ -29,9 +31,8 @@ in { ad = false; port = 389; hostname = "account.skynet.ie"; - root = "dc=skynet,dc=ie"; + rootPath = "dc=skynet,dc=ie"; username = "cn=admin,dc=skynet,dc=ie"; - pw_env = "LDAP_ADMIN_PW"; }; sync = { @@ -40,21 +41,17 @@ in { largeImport = false; memberAttribute = "member"; creationDateAttribute = "skCreated"; - emailPrefixSuffix.enable = false; - users = { - enable = true; - path = "ou=users"; - objectClass = "inetOrgPerson"; - emailAttribute = "skMail"; - filter = "(|(memberOf=cn=skynet-committee,ou=groups,dc=skynet,dc=ie)(memberOf=cn=skynet-admins,ou=groups,dc=skynet,dc=ie))"; - }; - groups = { - enable = true; - path = "ou=groups"; - objectClass = "groupOfNames"; - nameAttribute = "cn"; - filter = ""; - }; + + users = true; + userPath = "ou=users"; + userObjectClass = "inetOrgPerson"; + userEmailAttribute = "skMail"; + userFilter = "(|(memberOf=cn=skynet-committee,ou=groups,dc=skynet,dc=ie)(memberOf=cn=skynet-admins,ou=groups,dc=skynet,dc=ie))"; + + groups = true; + groupPath = "ou=groups"; + groupObjectClass = "groupOfNames"; + groupNameAttribute = "cn"; }; env = {