From 0c7a8d51b06c5d4690d020ac641f7b76781a0c2e Mon Sep 17 00:00:00 2001 From: Alexander Tomokhov Date: Fri, 24 Jan 2025 16:27:48 +0400 Subject: [PATCH] fix gitea,nextcloud,roundcube: evaluate without auth module --- sp-modules/gitea/module.nix | 493 ++++++++++++++++---------------- sp-modules/nextcloud/module.nix | 308 ++++++++++---------- sp-modules/roundcube/module.nix | 143 ++++----- 3 files changed, 490 insertions(+), 454 deletions(-) diff --git a/sp-modules/gitea/module.nix b/sp-modules/gitea/module.nix index d944885..82e57ab 100644 --- a/sp-modules/gitea/module.nix +++ b/sp-modules/gitea/module.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, options, pkgs, ... }: let sp = config.selfprivacy; stateDir = @@ -14,7 +14,7 @@ let "gitea-light" "gitea-dark" ]; - is-auth-enabled = config.selfprivacy.modules.auth.enable or false; + is-auth-enabled = sp.modules.auth.enable or false; oauth-client-id = "forgejo"; auth-passthru = config.passthru.selfprivacy.auth; oauth2-provider-name = auth-passthru.oauth2-provider-name; @@ -189,250 +189,261 @@ in }; }; - config = lib.mkIf cfg.enable { - fileSystems = lib.mkIf sp.useBinds { - "/var/lib/gitea" = { - device = "/volumes/${cfg.location}/gitea"; - options = [ "bind" ]; - }; - }; - services.gitea.enable = false; - services.forgejo = { - enable = true; - package = pkgs.forgejo; - inherit stateDir; - user = "gitea"; - group = "gitea"; - database = { - type = "sqlite3"; - host = "127.0.0.1"; - name = "gitea"; - user = "gitea"; - path = "${stateDir}/data/gitea.db"; - createDatabase = true; - }; - # ssh = { - # enable = true; - # clonePort = 22; - # }; - lfs = { - enable = cfg.enableLfs; - contentDir = "${stateDir}/lfs"; - }; - repositoryRoot = "${stateDir}/repositories"; - # cookieSecure = true; - settings = { - DEFAULT = { - APP_NAME = "${cfg.appName}"; - }; - server = { - DOMAIN = "${cfg.subdomain}.${sp.domain}"; - ROOT_URL = "https://${cfg.subdomain}.${sp.domain}/"; - HTTP_ADDR = "0.0.0.0"; - HTTP_PORT = 3000; - }; - mailer = { - ENABLED = false; - }; - ui = { - DEFAULT_THEME = cfg.defaultTheme; - SHOW_USER_EMAIL = false; - }; - picture = { - DISABLE_GRAVATAR = true; - }; - admin = { - ENABLE_KANBAN_BOARD = true; - }; - repository = { - FORCE_PRIVATE = cfg.forcePrivate; - }; - session = { - COOKIE_SECURE = true; - }; - log = { - ROOT_PATH = "${stateDir}/log"; - LEVEL = if cfg.debug then "Warn" else "Trace"; - }; - service = { - DISABLE_REGISTRATION = cfg.disableRegistration; - REQUIRE_SIGNIN_VIEW = cfg.requireSigninView; - }; - } // lib.attrsets.optionalAttrs is-auth-enabled { - auth.DISABLE_LOGIN_FORM = true; - service = { - DISABLE_REGISTRATION = cfg.disableRegistration; - REQUIRE_SIGNIN_VIEW = cfg.requireSigninView; - ALLOW_ONLY_EXTERNAL_REGISTRATION = true; - SHOW_REGISTRATION_BUTTON = false; - ENABLE_BASIC_AUTHENTICATION = false; - }; - - # disallow explore page and access to private repositories, but allow public - "service.explore".REQUIRE_SIGNIN_VIEW = true; - - # TODO control via selfprivacy parameter - # "service.explore".DISABLE_USERS_PAGE = true; - - oauth2_client = { - REDIRECT_URI = redirect-uri; - ACCOUNT_LINKING = "auto"; - ENABLE_AUTO_REGISTRATION = true; - OPENID_CONNECT_SCOPES = "email openid profile"; - }; - # doesn't work if LDAP auth source is not active! - "cron.sync_external_users" = { - ENABLED = true; - RUN_AT_START = true; - NOTICE_ON_SUCCESS = true; + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + fileSystems = lib.mkIf sp.useBinds { + "/var/lib/gitea" = { + device = "/volumes/${cfg.location}/gitea"; + options = [ "bind" ]; }; }; - }; - - users.users.gitea = { - home = "${stateDir}"; - useDefaultShell = true; - group = "gitea"; - isSystemUser = true; - }; - users.groups.gitea = { }; - services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = { - useACMEHost = sp.domain; - forceSSL = true; - extraConfig = '' - add_header Strict-Transport-Security $hsts_header; - #add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always; - add_header 'Referrer-Policy' 'origin-when-cross-origin'; - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - add_header X-XSS-Protection "1; mode=block"; - proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict"; - '' + lib.strings.optionalString is-auth-enabled '' - rewrite ^/user/login$ /user/oauth2/${oauth2-provider-name} last; - # FIXME is it needed? - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - ''; - locations = { - "/" = { - proxyPass = "http://127.0.0.1:3000"; - }; - }; - }; - systemd = { + services.gitea.enable = false; services.forgejo = { - unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/gitea"; - serviceConfig = { - Slice = "gitea.slice"; + enable = true; + package = pkgs.forgejo; + inherit stateDir; + user = "gitea"; + group = "gitea"; + database = { + type = "sqlite3"; + host = "127.0.0.1"; + name = "gitea"; + user = "gitea"; + path = "${stateDir}/data/gitea.db"; + createDatabase = true; }; - preStart = - let - exe = lib.getExe config.services.forgejo.package; - # FIXME skip-tls-verify, bind-password - ldapConfigArgs = '' - --name LDAP \ - --active \ - --security-protocol LDAPS \ - --skip-tls-verify \ - --host '${auth-passthru.ldap-host}' \ - --port '${toString auth-passthru.ldap-port}' \ - --user-search-base '${auth-passthru.ldap-base-dn}' \ - --user-filter '(&(class=person)(memberof=${users-group})(name=%s))' \ - --admin-filter '(&(class=person)(memberof=${admins-group}))' \ - --username-attribute name \ - --firstname-attribute name \ - --surname-attribute displayname \ - --email-attribute mail \ - --public-ssh-key-attribute sshPublicKey \ - --bind-dn 'dn=token' \ - --bind-password "$(cat ${kanidm-service-account-token-fp})" \ - --synchronize-users + # ssh = { + # enable = true; + # clonePort = 22; + # }; + lfs = { + enable = cfg.enableLfs; + contentDir = "${stateDir}/lfs"; + }; + repositoryRoot = "${stateDir}/repositories"; + # cookieSecure = true; + settings = { + DEFAULT = { + APP_NAME = "${cfg.appName}"; + }; + server = { + DOMAIN = "${cfg.subdomain}.${sp.domain}"; + ROOT_URL = "https://${cfg.subdomain}.${sp.domain}/"; + HTTP_ADDR = "0.0.0.0"; + HTTP_PORT = 3000; + }; + mailer = { + ENABLED = false; + }; + ui = { + DEFAULT_THEME = cfg.defaultTheme; + SHOW_USER_EMAIL = false; + }; + picture = { + DISABLE_GRAVATAR = true; + }; + admin = { + ENABLE_KANBAN_BOARD = true; + }; + repository = { + FORCE_PRIVATE = cfg.forcePrivate; + }; + session = { + COOKIE_SECURE = true; + }; + log = { + ROOT_PATH = "${stateDir}/log"; + LEVEL = if cfg.debug then "Warn" else "Trace"; + }; + service = { + DISABLE_REGISTRATION = cfg.disableRegistration; + REQUIRE_SIGNIN_VIEW = cfg.requireSigninView; + }; + }; + }; + + users.users.gitea = { + home = "${stateDir}"; + useDefaultShell = true; + group = "gitea"; + isSystemUser = true; + }; + users.groups.gitea = { }; + services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = { + useACMEHost = sp.domain; + forceSSL = true; + extraConfig = '' + add_header Strict-Transport-Security $hsts_header; + #add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always; + add_header 'Referrer-Policy' 'origin-when-cross-origin'; + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict"; + ''; + locations = { + "/" = { + proxyPass = "http://127.0.0.1:3000"; + }; + }; + }; + systemd = { + services.forgejo = { + unitConfig.RequiresMountsFor = lib.mkIf sp.useBinds "/volumes/${cfg.location}/gitea"; + serviceConfig = { + Slice = "gitea.slice"; + }; + }; + slices.gitea = { + description = "Forgejo service slice"; + }; + }; + } + # the following part is active only when "auth" module is enabled + (lib.attrsets.optionalAttrs + (options.selfprivacy.modules ? "auth") + (lib.mkIf is-auth-enabled { + services.forgejo.settings = { + auth.DISABLE_LOGIN_FORM = true; + service = { + DISABLE_REGISTRATION = cfg.disableRegistration; + REQUIRE_SIGNIN_VIEW = cfg.requireSigninView; + ALLOW_ONLY_EXTERNAL_REGISTRATION = true; + SHOW_REGISTRATION_BUTTON = false; + ENABLE_BASIC_AUTHENTICATION = false; + }; + + # disallow explore page and access to private repositories, but allow public + "service.explore".REQUIRE_SIGNIN_VIEW = true; + + # TODO control via selfprivacy parameter + # "service.explore".DISABLE_USERS_PAGE = true; + + oauth2_client = { + REDIRECT_URI = redirect-uri; + ACCOUNT_LINKING = "auto"; + ENABLE_AUTO_REGISTRATION = true; + OPENID_CONNECT_SCOPES = "email openid profile"; + }; + # doesn't work if LDAP auth source is not active! + "cron.sync_external_users" = { + ENABLED = true; + RUN_AT_START = true; + NOTICE_ON_SUCCESS = true; + }; + }; + systemd.services.forgejo = { + preStart = + let + exe = lib.getExe config.services.forgejo.package; + # FIXME skip-tls-verify, bind-password + ldapConfigArgs = '' + --name LDAP \ + --active \ + --security-protocol LDAPS \ + --skip-tls-verify \ + --host '${auth-passthru.ldap-host}' \ + --port '${toString auth-passthru.ldap-port}' \ + --user-search-base '${auth-passthru.ldap-base-dn}' \ + --user-filter '(&(class=person)(memberof=${users-group})(name=%s))' \ + --admin-filter '(&(class=person)(memberof=${admins-group}))' \ + --username-attribute name \ + --firstname-attribute name \ + --surname-attribute displayname \ + --email-attribute mail \ + --public-ssh-key-attribute sshPublicKey \ + --bind-dn 'dn=token' \ + --bind-password "$(cat ${kanidm-service-account-token-fp})" \ + --synchronize-users + ''; + oauthConfigArgs = '' + --name "${oauth2-provider-name}" \ + --provider openidConnect \ + --key forgejo \ + --secret "$(<${kanidm-oauth-client-secret-fp})" \ + --group-claim-name groups \ + --admin-group admins \ + --auto-discover-url '${auth-passthru.oauth2-discovery-url oauth-client-id}' + ''; + in + lib.mkAfter '' + set -o xtrace + + # Check if LDAP is already configured + ldap_line="$(${exe} admin auth list | grep LDAP | head -n 1)" + + if [[ -n "$ldap_line" ]]; then + # update ldap config + id="$(echo "$ldap_line" | ${pkgs.gawk}/bin/awk '{print $1}')" + ${exe} admin auth update-ldap --id "$id" ${ldapConfigArgs} + else + # initially configure ldap + ${exe} admin auth add-ldap ${ldapConfigArgs} + fi + + oauth_line="$(${exe} admin auth list | grep "${oauth2-provider-name}" | head -n 1)" + if [[ -n "$oauth_line" ]]; then + id="$(echo "$oauth_line" | ${pkgs.gawk}/bin/awk '{print $1}')" + ${exe} admin auth update-oauth --id "$id" ${oauthConfigArgs} + else + ${exe} admin auth add-oauth ${oauthConfigArgs} + fi ''; - oauthConfigArgs = '' - --name "${oauth2-provider-name}" \ - --provider openidConnect \ - --key forgejo \ - --secret "$(<${kanidm-oauth-client-secret-fp})" \ - --group-claim-name groups \ - --admin-group admins \ - --auto-discover-url '${auth-passthru.oauth2-discovery-url oauth-client-id}' - ''; - in - lib.mkIf is-auth-enabled (lib.mkAfter '' - set -o xtrace - - # Check if LDAP is already configured - ldap_line="$(${exe} admin auth list | grep LDAP | head -n 1)" - - if [[ -n "$ldap_line" ]]; then - # update ldap config - id="$(echo "$ldap_line" | ${pkgs.gawk}/bin/awk '{print $1}')" - ${exe} admin auth update-ldap --id "$id" ${ldapConfigArgs} - else - # initially configure ldap - ${exe} admin auth add-ldap ${ldapConfigArgs} - fi - - oauth_line="$(${exe} admin auth list | grep "${oauth2-provider-name}" | head -n 1)" - if [[ -n "$oauth_line" ]]; then - id="$(echo "$oauth_line" | ${pkgs.gawk}/bin/awk '{print $1}')" - ${exe} admin auth update-oauth --id "$id" ${oauthConfigArgs} - else - ${exe} admin auth add-oauth ${oauthConfigArgs} - fi - '' - ); - # TODO consider passing oauth consumer service to auth module instead - requires = lib.mkIf is-auth-enabled - [ auth-passthru.oauth2-systemd-service ]; - }; - slices.gitea = { - description = "Forgejo service slice"; - }; - }; - - # for ExecStartPost script to have access to /run/keys/* - users.groups.keys.members = - lib.mkIf is-auth-enabled [ config.services.forgejo.group ]; - - systemd.services.kanidm.serviceConfig.ExecStartPre = - lib.mkIf is-auth-enabled [ - ("-+" + kanidmExecStartPreScriptRoot) - ("-" + kanidmExecStartPreScript) - ]; - systemd.services.kanidm.serviceConfig.ExecStartPost = - lib.mkIf is-auth-enabled - (lib.mkAfter [ ("-" + kanidmExecStartPostScript) ]); - services.kanidm.provision = lib.mkIf is-auth-enabled { - groups = { - "${admins-group}".members = [ "sp.admins" ]; - "${users-group}".members = [ admins-group ]; - }; - systems.oauth2.forgejo = { - displayName = "Forgejo"; - originUrl = redirect-uri; - originLanding = "https://${cfg.subdomain}.${sp.domain}/"; - basicSecretFile = kanidm-oauth-client-secret-fp; - # when true, name is passed to a service instead of name@domain - preferShortUsername = true; - allowInsecureClientDisablePkce = true; # FIXME is it needed? - scopeMaps = { - "${users-group}" = [ - "email" - "openid" - "profile" - ]; + # TODO consider passing oauth consumer service to auth module instead + requires = [ auth-passthru.oauth2-systemd-service ]; }; - removeOrphanedClaimMaps = true; - # NOTE https://github.com/oddlama/kanidm-provision/issues/15 - # add more scopes when a user is a member of specific group - # currently not possible due to https://github.com/kanidm/kanidm/issues/2882#issuecomment-2564490144 - # supplementaryScopeMaps."${admins-group}" = - # [ "read:admin" "write:admin" ]; - claimMaps.groups = { - joinType = "array"; - valuesByGroup.${admins-group} = [ "admins" ]; + + # for ExecStartPost script to have access to /run/keys/* + users.groups.keys.members = [ config.services.forgejo.group ]; + + systemd.services.kanidm.serviceConfig.ExecStartPre = [ + ("-+" + kanidmExecStartPreScriptRoot) + ("-" + kanidmExecStartPreScript) + ]; + systemd.services.kanidm.serviceConfig.ExecStartPost = + lib.mkAfter [ ("-" + kanidmExecStartPostScript) ]; + + services.nginx.virtualHosts."${cfg.subdomain}.${sp.domain}" = { + extraConfig = lib.mkAfter '' + rewrite ^/user/login$ /user/oauth2/${oauth2-provider-name} last; + # FIXME is it needed? + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + ''; }; - }; - }; - }; + + services.kanidm.provision = { + groups = { + "${admins-group}".members = [ "sp.admins" ]; + "${users-group}".members = [ admins-group ]; + }; + systems.oauth2.forgejo = { + displayName = "Forgejo"; + originUrl = redirect-uri; + originLanding = "https://${cfg.subdomain}.${sp.domain}/"; + basicSecretFile = kanidm-oauth-client-secret-fp; + # when true, name is passed to a service instead of name@domain + preferShortUsername = true; + allowInsecureClientDisablePkce = true; # FIXME is it needed? + scopeMaps = { + "${users-group}" = [ + "email" + "openid" + "profile" + ]; + }; + removeOrphanedClaimMaps = true; + # NOTE https://github.com/oddlama/kanidm-provision/issues/15 + # add more scopes when a user is a member of specific group + # currently not possible due to https://github.com/kanidm/kanidm/issues/2882#issuecomment-2564490144 + # supplementaryScopeMaps."${admins-group}" = + # [ "read:admin" "write:admin" ]; + claimMaps.groups = { + joinType = "array"; + valuesByGroup.${admins-group} = [ "admins" ]; + }; + }; + }; + }) + ) + ]); } diff --git a/sp-modules/nextcloud/module.nix b/sp-modules/nextcloud/module.nix index 6cfc4f5..f8f9499 100644 --- a/sp-modules/nextcloud/module.nix +++ b/sp-modules/nextcloud/module.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, options, pkgs, ... }: let inherit (import ./common.nix config) admin-pass-filepath @@ -11,7 +11,7 @@ let hostName = "${cfg.subdomain}.${sp.domain}"; auth-passthru = config.passthru.selfprivacy.auth; - is-auth-enabled = config.selfprivacy.modules.auth.enable or false; + is-auth-enabled = sp.modules.nextcloud.enableSso; cfg = sp.modules.nextcloud; ldap_scheme_and_host = "ldaps://${auth-passthru.ldap-host}"; @@ -104,6 +104,15 @@ in type = "enable"; }; }; + enableSso = (lib.mkOption { + default = false; + type = lib.types.bool; + description = "Enable SSO for Nextcloud"; + }) // { + meta = { + type = "enable"; + }; + }; location = (lib.mkOption { type = lib.types.str; description = "Nextcloud location"; @@ -140,36 +149,139 @@ in }; }; - config = lib.mkIf sp.modules.nextcloud.enable { - fileSystems = lib.mkIf sp.useBinds { - "/var/lib/nextcloud" = { - device = "/volumes/${cfg.location}/nextcloud"; - options = [ - "bind" - "x-systemd.required-by=nextcloud-setup.service" - "x-systemd.required-by=nextcloud-secrets.service" - "x-systemd.before=nextcloud-setup.service" - "x-systemd.before=nextcloud-secrets.service" - ]; + # config = lib.mkIf sp.modules.nextcloud.enable + config = lib.mkIf sp.modules.nextcloud.enable (lib.mkMerge [ + { + fileSystems = lib.mkIf sp.useBinds { + "/var/lib/nextcloud" = { + device = "/volumes/${cfg.location}/nextcloud"; + options = [ + "bind" + "x-systemd.required-by=nextcloud-setup.service" + "x-systemd.required-by=nextcloud-secrets.service" + "x-systemd.before=nextcloud-setup.service" + "x-systemd.before=nextcloud-secrets.service" + ]; + }; }; - }; - # for ExecStartPost script to have access to /run/keys/* - users.groups.keys.members = - lib.mkIf is-auth-enabled [ nextcloud-setup-group ]; + # for ExecStartPost script to have access to /run/keys/* + users.groups.keys.members = + lib.mkIf is-auth-enabled [ nextcloud-setup-group ]; - # not needed, due to turnOffCertCheck=1 in used_ldap - # users.groups.${config.security.acme.certs.${domain}.group}.members = - # [ config.services.phpfpm.pools.nextcloud.user ]; + # not needed, due to turnOffCertCheck=1 in used_ldap + # users.groups.${config.security.acme.certs.${domain}.group}.members = + # [ config.services.phpfpm.pools.nextcloud.user ]; - systemd = { - services = { - phpfpm-nextcloud.serviceConfig.Slice = lib.mkForce "nextcloud.slice"; - nextcloud-setup = { - serviceConfig.Slice = "nextcloud.slice"; - serviceConfig.Group = config.services.phpfpm.pools.nextcloud.group; - path = lib.mkIf is-auth-enabled [ pkgs.jq ]; - script = lib.mkIf is-auth-enabled '' + systemd = { + services = { + phpfpm-nextcloud.serviceConfig.Slice = lib.mkForce "nextcloud.slice"; + nextcloud-setup = { + serviceConfig.Slice = "nextcloud.slice"; + serviceConfig.Group = config.services.phpfpm.pools.nextcloud.group; + }; + kanidm.serviceConfig.ExecStartPre = lib.mkIf is-auth-enabled + (lib.mkAfter [ + ("-+" + kanidmExecStartPreScriptRoot) + ("-" + kanidmExecStartPreScript) + ]); + kanidm.serviceConfig.ExecStartPost = lib.mkIf is-auth-enabled + (lib.mkAfter [ ("-" + kanidmExecStartPostScript) ]); + nextcloud-cron.serviceConfig.Slice = "nextcloud.slice"; + nextcloud-update-db.serviceConfig.Slice = "nextcloud.slice"; + nextcloud-update-plugins.serviceConfig.Slice = "nextcloud.slice"; + nextcloud-secrets = { + before = [ "nextcloud-setup.service" ]; + requiredBy = [ "nextcloud-setup.service" ]; + serviceConfig.Type = "oneshot"; + path = with pkgs; [ coreutils jq ]; + script = '' + databasePassword=$(jq -re '.modules.nextcloud.databasePassword' ${secrets-filepath}) + adminPassword=$(jq -re '.modules.nextcloud.adminPassword' ${secrets-filepath}) + + install -C -m 0440 -o nextcloud -g nextcloud -DT \ + <(printf "%s\n" "$databasePassword") \ + ${db-pass-filepath} + + install -C -m 0440 -o nextcloud -g nextcloud -DT \ + <(printf "%s\n" "$adminPassword") \ + ${admin-pass-filepath} + ''; + }; + }; + slices.nextcloud = { + description = "Nextcloud service slice"; + }; + }; + services.nextcloud = { + enable = true; + package = pkgs.nextcloud29; + inherit hostName; + + # Use HTTPS for links + https = true; + + # auto-update Nextcloud Apps + autoUpdateApps.enable = true; + # set what time makes sense for you + autoUpdateApps.startAt = "05:00:00"; + + phpOptions.display_errors = "Off"; + + settings = { + # further forces Nextcloud to use HTTPS + overwriteprotocol = "https"; + } // lib.attrsets.optionalAttrs is-auth-enabled { + loglevel = 0; + # log_type = "file"; + social_login_auto_redirect = false; + + allow_local_remote_servers = true; + allow_user_to_change_display_name = false; + lost_password_link = "disabled"; + allow_multiple_user_backends = false; + + user_oidc = { + single_logout = true; + use_pkce = true; + auto_provision = true; + soft_auto_provision = true; + disable_account_creation = false; + }; + }; + + config = { + dbtype = "sqlite"; + dbuser = "nextcloud"; + dbname = "nextcloud"; + dbpassFile = db-pass-filepath; + # TODO review whether admin user is needed at all - admin group works + adminpassFile = admin-pass-filepath; + adminuser = "admin"; + }; + + secretFile = lib.mkIf is-auth-enabled nextcloud-secret-file; + }; + services.nginx.virtualHosts.${hostName} = { + useACMEHost = sp.domain; + forceSSL = true; + #locations."/".extraConfig = lib.mkIf is-auth-enabled '' + # # FIXME does not work + # rewrite ^/login$ /apps/user_oidc/login/1 last; + #''; + # show an error instead of a blank page on Nextcloud PHP/FastCGI error + locations."~ \\.php(?:$|/)".extraConfig = '' + error_page 500 502 503 504 ${pkgs.nginx}/html/50x.html; + ''; + }; + } + # the following part is active only when "auth" module is enabled + (lib.attrsets.optionalAttrs + (options.selfprivacy.modules ? "auth") + (lib.mkIf is-auth-enabled { + systemd.services.nextcloud-setup = { + path = [ pkgs.jq ]; + script = '' set -o errexit set -o nounset ${lib.strings.optionalString cfg.debug "set -o xtrace"} @@ -266,123 +378,29 @@ in -vvv ''; # TODO consider passing oauth consumer service to auth module instead - requires = lib.mkIf is-auth-enabled - [ auth-passthru.oauth2-systemd-service ]; + requires = [ auth-passthru.oauth2-systemd-service ]; }; - kanidm.serviceConfig.ExecStartPre = lib.mkIf is-auth-enabled - (lib.mkAfter [ - ("-+" + kanidmExecStartPreScriptRoot) - ("-" + kanidmExecStartPreScript) - ]); - kanidm.serviceConfig.ExecStartPost = lib.mkIf is-auth-enabled - (lib.mkAfter [ ("-" + kanidmExecStartPostScript) ]); - nextcloud-cron.serviceConfig.Slice = "nextcloud.slice"; - nextcloud-update-db.serviceConfig.Slice = "nextcloud.slice"; - nextcloud-update-plugins.serviceConfig.Slice = "nextcloud.slice"; - nextcloud-secrets = { - before = [ "nextcloud-setup.service" ]; - requiredBy = [ "nextcloud-setup.service" ]; - serviceConfig.Type = "oneshot"; - path = with pkgs; [ coreutils jq ]; - script = '' - databasePassword=$(jq -re '.modules.nextcloud.databasePassword' ${secrets-filepath}) - adminPassword=$(jq -re '.modules.nextcloud.adminPassword' ${secrets-filepath}) - - install -C -m 0440 -o nextcloud -g nextcloud -DT \ - <(printf "%s\n" "$databasePassword") \ - ${db-pass-filepath} - - install -C -m 0440 -o nextcloud -g nextcloud -DT \ - <(printf "%s\n" "$adminPassword") \ - ${admin-pass-filepath} - ''; + services.kanidm.provision = { + groups = { + "${admins-group}".members = [ "sp.admins" ]; + "${users-group}".members = [ admins-group ]; + }; + systems.oauth2.${oauth-client-id} = { + displayName = "Nextcloud"; + originUrl = "https://${cfg.subdomain}.${domain}/apps/user_oidc/code"; + originLanding = "https://${cfg.subdomain}.${domain}/"; + basicSecretFile = kanidm-oauth-client-secret-fp; + # when true, name is passed to a service instead of name@domain + preferShortUsername = true; + allowInsecureClientDisablePkce = false; + scopeMaps.${users-group} = [ "email" "openid" "profile" ]; + removeOrphanedClaimMaps = true; + claimMaps.groups = { + joinType = "array"; + valuesByGroup.${admins-group} = [ "admin" ]; + }; + }; }; - }; - slices.nextcloud = { - description = "Nextcloud service slice"; - }; - }; - services.nextcloud = { - enable = true; - package = pkgs.nextcloud29; - inherit hostName; - - # Use HTTPS for links - https = true; - - # auto-update Nextcloud Apps - autoUpdateApps.enable = true; - # set what time makes sense for you - autoUpdateApps.startAt = "05:00:00"; - - phpOptions.display_errors = "Off"; - - settings = { - # further forces Nextcloud to use HTTPS - overwriteprotocol = "https"; - } // lib.attrsets.optionalAttrs is-auth-enabled { - loglevel = 0; - # log_type = "file"; - social_login_auto_redirect = false; - - allow_local_remote_servers = true; - allow_user_to_change_display_name = false; - lost_password_link = "disabled"; - allow_multiple_user_backends = false; - - user_oidc = { - single_logout = true; - use_pkce = true; - auto_provision = true; - soft_auto_provision = true; - disable_account_creation = false; - }; - }; - - config = { - dbtype = "sqlite"; - dbuser = "nextcloud"; - dbname = "nextcloud"; - dbpassFile = db-pass-filepath; - # TODO review whether admin user is needed at all - admin group works - adminpassFile = admin-pass-filepath; - adminuser = "admin"; - }; - - secretFile = lib.mkIf is-auth-enabled nextcloud-secret-file; - }; - services.nginx.virtualHosts.${hostName} = { - useACMEHost = sp.domain; - forceSSL = true; - #locations."/".extraConfig = lib.mkIf is-auth-enabled '' - # # FIXME does not work - # rewrite ^/login$ /apps/user_oidc/login/1 last; - #''; - # show an error instead of a blank page on Nextcloud PHP/FastCGI error - locations."~ \\.php(?:$|/)".extraConfig = '' - error_page 500 502 503 504 ${pkgs.nginx}/html/50x.html; - ''; - }; - services.kanidm.provision = lib.mkIf is-auth-enabled { - groups = { - "${admins-group}".members = [ "sp.admins" ]; - "${users-group}".members = [ admins-group ]; - }; - systems.oauth2.${oauth-client-id} = { - displayName = "Nextcloud"; - originUrl = "https://${cfg.subdomain}.${domain}/apps/user_oidc/code"; - originLanding = "https://${cfg.subdomain}.${domain}/"; - basicSecretFile = kanidm-oauth-client-secret-fp; - # when true, name is passed to a service instead of name@domain - preferShortUsername = true; - allowInsecureClientDisablePkce = false; - scopeMaps.${users-group} = [ "email" "openid" "profile" ]; - removeOrphanedClaimMaps = true; - claimMaps.groups = { - joinType = "array"; - valuesByGroup.${admins-group} = [ "admin" ]; - }; - }; - }; - }; + })) + ]); } diff --git a/sp-modules/roundcube/module.nix b/sp-modules/roundcube/module.nix index 7a389d8..9ed07d4 100644 --- a/sp-modules/roundcube/module.nix +++ b/sp-modules/roundcube/module.nix @@ -1,4 +1,4 @@ -{ config, lib, pkgs, ... }: +{ config, lib, options, pkgs, ... }: let domain = config.selfprivacy.domain; cfg = config.selfprivacy.modules.roundcube; @@ -48,74 +48,81 @@ in }; }; - config = lib.mkIf cfg.enable { - services.roundcube = { - enable = true; - # this is the url of the vhost, not necessarily the same as the fqdn of - # the mailserver - hostName = "${cfg.subdomain}.${config.selfprivacy.domain}"; - extraConfig = '' - # starttls needed for authentication, so the fqdn required to match - # the certificate - $config['smtp_host'] = "tls://${config.mailserver.fqdn}"; - $config['smtp_user'] = "%u"; - $config['smtp_pass'] = "%p"; - '' + lib.strings.optionalString is-auth-enabled '' - $config['oauth_provider'] = 'generic'; - $config['oauth_provider_name'] = '${auth-passthru.oauth2-provider-name}'; - $config['oauth_client_id'] = '${oauth-client-id}'; - $config['oauth_client_secret'] = "$(<${kanidm-oauth-client-secret-fp})"; - $config['oauth_auth_uri'] = 'https://${auth-fqdn}/ui/oauth2'; - $config['oauth_token_uri'] = 'https://${auth-fqdn}/oauth2/token'; - $config['oauth_identity_uri'] = 'https://${auth-fqdn}/oauth2/openid/${oauth-client-id}/userinfo'; - $config['oauth_scope'] = 'email profile openid'; # FIXME - $config['oauth_auth_parameters'] = []; - $config['oauth_identity_fields'] = ['email']; - $config['oauth_login_redirect'] = true; - $config['auto_create_user'] = true; - $config['oauth_verify_peer'] = false; # FIXME - # $config['oauth_pkce'] = 'S256'; # FIXME - ''; - }; - - services.nginx.virtualHosts."${cfg.subdomain}.${domain}" = { - forceSSL = true; - useACMEHost = domain; - enableACME = false; - }; - - systemd.slices.roundcube.description = "Roundcube service slice"; - - systemd.services.kanidm = lib.mkIf is-auth-enabled { - serviceConfig.ExecStartPre = lib.mkAfter [ - ("-+" + kanidmExecStartPreScriptRoot) - ("-" + kanidmExecStartPreScript) - ]; - requires = [ auth-passthru.oauth2-systemd-service ]; - }; - - services.kanidm.provision = lib.mkIf is-auth-enabled { - groups = { - "sp.roundcube.admins".members = [ "sp.admins" ]; - "sp.roundcube.users".members = [ "sp.roundcube.admins" ]; + config = lib.mkIf cfg.enable (lib.mkMerge [ + { + services.roundcube = { + enable = true; + # this is the url of the vhost, not necessarily the same as the fqdn of + # the mailserver + hostName = "${cfg.subdomain}.${config.selfprivacy.domain}"; + extraConfig = '' + # starttls needed for authentication, so the fqdn required to match + # the certificate + $config['smtp_host'] = "tls://${config.mailserver.fqdn}"; + $config['smtp_user'] = "%u"; + $config['smtp_pass'] = "%p"; + ''; }; - systems.oauth2.roundcube = { - displayName = "Roundcube"; - originUrl = "https://${cfg.subdomain}.${domain}/index.php/login/oauth"; - originLanding = "https://${cfg.subdomain}.${domain}/"; - basicSecretFile = kanidm-oauth-client-secret-fp; - # when true, name is passed to a service instead of name@domain - preferShortUsername = false; - allowInsecureClientDisablePkce = true; # FIXME is it needed? - scopeMaps = { - "sp.roundcube.users" = [ - "email" - "openid" - "profile" + + services.nginx.virtualHosts."${cfg.subdomain}.${domain}" = { + forceSSL = true; + useACMEHost = domain; + enableACME = false; + }; + + systemd.slices.roundcube.description = "Roundcube service slice"; + } + # the following part is active only when "auth" module is enabled + (lib.attrsets.optionalAttrs + (options.selfprivacy.modules ? "auth") + (lib.mkIf is-auth-enabled { + services.roundcube.extraConfig = lib.mkAfter '' + $config['oauth_provider'] = 'generic'; + $config['oauth_provider_name'] = '${auth-passthru.oauth2-provider-name}'; + $config['oauth_client_id'] = '${oauth-client-id}'; + $config['oauth_client_secret'] = "$(<${kanidm-oauth-client-secret-fp})"; + $config['oauth_auth_uri'] = 'https://${auth-fqdn}/ui/oauth2'; + $config['oauth_token_uri'] = 'https://${auth-fqdn}/oauth2/token'; + $config['oauth_identity_uri'] = 'https://${auth-fqdn}/oauth2/openid/${oauth-client-id}/userinfo'; + $config['oauth_scope'] = 'email profile openid'; # FIXME + $config['oauth_auth_parameters'] = []; + $config['oauth_identity_fields'] = ['email']; + $config['oauth_login_redirect'] = true; + $config['auto_create_user'] = true; + $config['oauth_verify_peer'] = false; # FIXME + # $config['oauth_pkce'] = 'S256'; # FIXME + ''; + systemd.services.kanidm = { + serviceConfig.ExecStartPre = lib.mkAfter [ + ("-+" + kanidmExecStartPreScriptRoot) + ("-" + kanidmExecStartPreScript) ]; + requires = [ auth-passthru.oauth2-systemd-service ]; }; - removeOrphanedClaimMaps = true; - }; - }; - }; + services.kanidm.provision = { + groups = { + "sp.roundcube.admins".members = [ "sp.admins" ]; + "sp.roundcube.users".members = [ "sp.roundcube.admins" ]; + }; + systems.oauth2.roundcube = { + displayName = "Roundcube"; + originUrl = "https://${cfg.subdomain}.${domain}/index.php/login/oauth"; + originLanding = "https://${cfg.subdomain}.${domain}/"; + basicSecretFile = kanidm-oauth-client-secret-fp; + # when true, name is passed to a service instead of name@domain + preferShortUsername = false; + allowInsecureClientDisablePkce = true; # FIXME is it needed? + scopeMaps = { + "sp.roundcube.users" = [ + "email" + "openid" + "profile" + ]; + }; + removeOrphanedClaimMaps = true; + }; + }; + }) + ) + ]); }