From dd2afc2cfb38e7a47744b1c081efda148f85f20d Mon Sep 17 00:00:00 2001 From: Jeltz Date: Sat, 22 Feb 2025 12:47:58 +0100 Subject: [PATCH] indico: wip: add LDAP support Signed-off-by: Jeltz --- modules/indico.nix | 161 ++++++++++++++++++++++++++++++++++------ pkgs/indico/default.nix | 11 +-- 2 files changed, 143 insertions(+), 29 deletions(-) diff --git a/modules/indico.nix b/modules/indico.nix index 2431535..f4939ef 100644 --- a/modules/indico.nix +++ b/modules/indico.nix @@ -14,7 +14,9 @@ let pythonEnv = pkgs.python3.withPackages (ps: [ (ps.toPythonModule indico) ps.gunicorn - ]); + ] + ++ lib.optionals (cfg.ldap != null) + indico.optional-dependencies.ldap); redisSocket = config.services.redis.servers.${cfg.redis.name}.unixSocket; indicoSocket = "/run/indico/indico.sock"; baseDir = "${pythonEnv}/${pythonEnv.sitePackages}/indico"; @@ -36,7 +38,23 @@ let format = "%(levelname)s %(request_id)s %(user_id)s %(name)s %(message)s"; }; }; - configFile = pythonFmt.generate "indico.conf" { + ldapConfig = { + uri = cfg.ldap.uri; + bind_dn = cfg.ldap.bindDN; + bind_password = cfg.ldap.bindPassword; + timeout = 30; + verify_cert = true; + page_size = 1500; + uid = cfg.ldap.uid; + user_base = cfg.ldap.userBaseDN; + user_filter = cfg.ldap.userFilter; + gid = cfg.ldap.gid; + group_base = cfg.ldap.groupBaseDN; + group_filter = cfg.ldap.groupFilter; + member_of_attr = cfg.ldap.memberOf; + ad_group_style = false; + }; + configFile = pythonFmt.generate "indico.conf" ({ SQLALCHEMY_DATABASE_URI = cfg.database; CACHE_DIR = "${cfg.stateDir}/cache"; TEMP_DIR = "${cfg.stateDir}/tmp"; @@ -64,9 +82,27 @@ let SMTP_LOGIN = cfg.email.smtp.login; SMTP_PASSWORD = cfg.email.smtp.password; SMTP_USE_TLS = cfg.email.smtp.useTLS; - }; -in -{ + } // lib.optionalAttrs (cfg.ldap != null) { + AUTH_PROVIDERS = { + ldap = { + type = "ldap"; + title = "LDAP"; + ldap = ldapConfig; + default = true; + }; + }; + IDENTITY_PROVIDERS = { + ldap = { + type = "ldap"; + title = "LDAP"; + ldap = ldapConfig; + mapping = cfg.ldap.mapping; + trusted_email = cfg.ldap.trustedEmail; + synced_fields = cfg.ldap.syncedFields; + }; + }; + }); +in { # TODO cProfile; indico standalone command is *very* slow # (~30s just to print the help) @@ -235,16 +271,101 @@ in "postgresql://@/${cfg.user}?host=/run/postgresql"; description = "Database URL."; }; + + ldap = lib.mkOption { + type = lib.types.nullOr (lib.types.submodule { + options = { + uri = lib.mkOption { + type = lib.types.str; + description = "LDAP server URI."; + }; + + bindDN = lib.mkOption { + type = lib.types.str; + description = "LDAP server bind DN."; + }; + + bindPassword = lib.mkOption { + type = lib.types.str; + description = "LDAP server bind password."; + }; + + uid = lib.mkOption { + type = lib.types.str; + default = "uid"; + description = "LDAP UID attribute."; + }; + + userBaseDN = lib.mkOption { + type = lib.types.str; + description = "LDAP users base DN."; + }; + + userFilter = lib.mkOption { + type = lib.types.str; + description = "LDAP users filter."; + }; + + gid = lib.mkOption { + type = lib.types.str; + default = "gid"; + description = "LDAP GID attribute."; + }; + + groupBaseDN = lib.mkOption { + type = lib.types.str; + description = "LDAP groups base DN."; + }; + + groupFilter = lib.mkOption { + type = lib.types.str; + description = "LDAP groups filter."; + }; + + memberOf = lib.mkOption { + type = lib.types.str; + default = "memberOf"; + description = "LDAP memberOf attribute."; + }; + + mapping = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { + first_name = "givenName"; + last_name = "sn"; + email = "mail"; + affiliation = "company"; + phone = "telephoneNumber"; + }; + description = "Mapping between local and LDAP fields."; + }; + + trustedEmail = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether LDAP emails are to be trusted."; + }; + + syncedFields = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "first_name" "last_name" ]; + description = "Fields to sync with LDAP server."; + }; + }; + }); + default = null; + description = "LDAP authentication and identity provider configuration."; + }; }; config = lib.mkIf cfg.enable { users.users = { - ${cfg.user} = { - isSystemUser = true; - group = cfg.group; - }; + ${cfg.user} = { + isSystemUser = true; + group = cfg.group; + }; } // (lib.optionalAttrs cfg.nginx.enable { - ${config.services.nginx.user}.extraGroups = [ cfg.group ]; + ${config.services.nginx.user}.extraGroups = [ cfg.group ]; }); users.groups.${cfg.group} = { }; @@ -254,12 +375,10 @@ in package = pkgs.postgresql_16; # TODO add setting # TODO db name != cfg.user ensureDatabases = [ cfg.user ]; - ensureUsers = [ - { - name = cfg.user; - ensureDBOwnership = true; - } - ]; + ensureUsers = [{ + name = cfg.user; + ensureDBOwnership = true; + }]; }; environment.systemPackages = @@ -268,7 +387,7 @@ in #!${pkgs.runtimeShell} sudo=exec if [[ "$USER" != "indico" ]]; then - sudo='exec /run/wrappers/bin/sudo -u indico -E INDICO_CONFIG' + sudo='exec /run/wrappers/bin/sudo -u indico --preserve-env=INDICO_CONFIG' fi export INDICO_CONFIG=${configFile} $sudo ${lib.getExe' pythonEnv "indico"} "$@" @@ -311,9 +430,7 @@ in { indico-db = lib.recursiveUpdate common { description = "Indico database preparation and upgrade"; - after = [ - "postgresql.service" - ]; + after = [ "postgresql.service" ]; serviceConfig.Type = "oneshot"; # Source: pretalx module ; passer par un service oneshot script = '' @@ -356,13 +473,13 @@ in "indico-worker.service" "indico-db.service" ]; - # TODO bind on a TCP socket when cfg.nginx.enable == false? + # TODO bind on a TCP socket when cfg.nginx.enable == false? serviceConfig.ExecStart = '' ${lib.getExe' pythonEnv "gunicorn"} \ --bind unix:${indicoSocket} \ --name=indico \ indico.web.wsgi - ''; + ''; }; }; diff --git a/pkgs/indico/default.nix b/pkgs/indico/default.nix index a80b249..a0279f6 100644 --- a/pkgs/indico/default.nix +++ b/pkgs/indico/default.nix @@ -69,7 +69,6 @@ python.pkgs.buildPythonApplication rec { INDICO_NO_GIT = "true"; INDICO_MAP_VERSION = src.outputHash; - # TODO bin/maintenance/build-{wheel,assets}.py build-system = [ python.pkgs.hatchling python.pkgs.hatch-requirements-txt @@ -97,7 +96,7 @@ python.pkgs.buildPythonApplication rec { # FIXME *why* is it required? makeCacheWritable = true; - npmFlags = [ "--verbose" ]; + # npmFlags = [ "--verbose" ]; patches = [ ./remove_marshmallow_enum.patch @@ -137,11 +136,9 @@ python.pkgs.buildPythonApplication rec { # TODO TODO ./bin/maintenance/build-assets.py ''; - # TODO build-assets.py # build_urls_map.py tries to import indico.web.flask.app # which has not been installed yet at this stage preBuild = '' - ls -lah PYTHONPATH="$(pwd):$PYTHONPATH" ${lib.getExe python} bin/maintenance/build-assets.py indico --clean ''; @@ -233,9 +230,9 @@ python.pkgs.buildPythonApplication rec { ++ sentry-sdk_1.optional-dependencies.pure_eval ++ wtforms.optional-dependencies.email; - # passthru = { - # inherit python; - # }; + optional-dependencies = { + ldap = [ python.pkgs.python-ldap ]; + }; meta = { description = "Full-featured conferency lifecycle management and meeting/lecture scheduling tool";