From 4c6228d6941633b23d4445dc9704eba190977441 Mon Sep 17 00:00:00 2001 From: Alexander Tomokhov Date: Fri, 31 Jan 2025 14:31:09 +0400 Subject: [PATCH] roundcube & mailserver: fix oauth: mailserver is an OAuth secret donor Both of them use the same client ID and client secret, but Roundcube depends on mailserver generally, so mailserver is the one to share OAuth client id and secret. --- sp-modules/roundcube/config-paths-needed.json | 4 +- sp-modules/roundcube/module.nix | 45 ++++++++-------- .../simple-nixos-mailserver/auth-dovecot.nix | 51 ++++++++++++++----- .../simple-nixos-mailserver/auth-postfix.nix | 6 +-- sp-modules/simple-nixos-mailserver/common.nix | 3 +- .../config-paths-needed.json | 7 ++- sp-modules/simple-nixos-mailserver/config.nix | 22 ++++---- 7 files changed, 86 insertions(+), 52 deletions(-) diff --git a/sp-modules/roundcube/config-paths-needed.json b/sp-modules/roundcube/config-paths-needed.json index aff2a53..a759840 100644 --- a/sp-modules/roundcube/config-paths-needed.json +++ b/sp-modules/roundcube/config-paths-needed.json @@ -7,5 +7,7 @@ [ "passthru", "selfprivacy", "auth", "oauth2-systemd-service" ], [ "selfprivacy", "domain" ], [ "selfprivacy", "modules", "auth" ], - [ "selfprivacy", "modules", "roundcube" ] + [ "selfprivacy", "modules", "roundcube" ], + [ "selfprivacy", "passthru", "mailserver", "oauth-client-id" ], + [ "selfprivacy", "passthru", "mailserver", "oauth-client-secret-fp" ] ] diff --git a/sp-modules/roundcube/module.nix b/sp-modules/roundcube/module.nix index b5d6ddf..f01831d 100644 --- a/sp-modules/roundcube/module.nix +++ b/sp-modules/roundcube/module.nix @@ -5,24 +5,21 @@ let is-auth-enabled = config.selfprivacy.modules.auth.enable or false; auth-passthru = config.passthru.selfprivacy.auth; auth-fqdn = auth-passthru.auth-fqdn; - oauth-client-id = "roundcube"; - roundcube-user = "roundcube"; - roundcube-group = "roundcube"; - kanidmExecStartPreScriptRoot = pkgs.writeShellScript - "${oauth-client-id}-kanidm-ExecStartPre-root-script.sh" - '' - # set-group-ID bit allows for kanidm user to create files, - mkdir -p -v --mode=u+rwx,g+rs,g-w,o-rwx /run/keys/${oauth-client-id} - chown kanidm:${roundcube-group} /run/keys/${oauth-client-id} - ''; + sp-module-name = "roundcube"; + user = "roundcube"; + group = "roundcube"; + oauth-donor = config.selfprivacy.passthru.mailserver; kanidm-oauth-client-secret-fp = - "/run/keys/${oauth-client-id}/kanidm-oauth-client-secret"; - kanidmExecStartPreScript = pkgs.writeShellScript - "${oauth-client-id}-kanidm-ExecStartPre-script.sh" '' - set -o xtrace - [ -f "${kanidm-oauth-client-secret-fp}" ] || \ - "${lib.getExe pkgs.openssl}" rand -base64 -out "${kanidm-oauth-client-secret-fp}" 32 - ''; + "/run/keys/${group}/kanidm-oauth-client-secret"; + kanidmExecStartPreScriptRoot = pkgs.writeShellScript + "${sp-module-name}-kanidm-ExecStartPre-root-script.sh" + '' + # set-group-ID bit allows for kanidm user to create files inheriting group + mkdir -p -v --mode=u+rwx,g+rs,g-w,o-rwx /run/keys/${group} + chown kanidm:${group} /run/keys/${group} + + install -v -m640 -o kanidm -g ${group} ${oauth-donor.oauth-client-secret-fp} ${kanidm-oauth-client-secret-fp} + ''; in { options.selfprivacy.modules.roundcube = { @@ -72,21 +69,23 @@ in }; systemd.slices.roundcube.description = "Roundcube service slice"; + # Roundcube depends on Dovecot and its OAuth2 client secret. + systemd.services.roundcube.after = [ "dovecot2.service" ]; } # the following part is active only when "auth" module is enabled (lib.attrsets.optionalAttrs (options.selfprivacy.modules ? "auth") (lib.mkIf is-auth-enabled { # for phpfpm-roundcube to have access to get through /run/keys directory - users.groups.keys.members = [ roundcube-user ]; + users.groups.keys.members = [ user ]; 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_id'] = '${oauth-donor.oauth-client-id}'; $config['oauth_client_secret'] = file_get_contents('${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_identity_uri'] = 'https://${auth-fqdn}/oauth2/openid/${oauth-donor.oauth-client-id}/userinfo'; $config['oauth_scope'] = 'email profile openid'; # FIXME $config['oauth_auth_parameters'] = []; $config['oauth_identity_fields'] = ['email']; @@ -96,11 +95,9 @@ in # $config['oauth_pkce'] = 'S256'; # FIXME ''; systemd.services.kanidm = { - serviceConfig.ExecStartPre = lib.mkAfter [ + serviceConfig.ExecStartPre = lib.mkBefore [ ("-+" + kanidmExecStartPreScriptRoot) - ("-" + kanidmExecStartPreScript) ]; - requires = [ auth-passthru.oauth2-systemd-service ]; }; services.kanidm.provision = { groups = { @@ -108,7 +105,7 @@ in "sp.roundcube.users".members = [ "sp.roundcube.admins" auth-passthru.full-users-group ]; }; - systems.oauth2.roundcube = { + systems.oauth2.${oauth-donor.oauth-client-id} = { displayName = "Roundcube"; originUrl = "https://${cfg.subdomain}.${domain}/index.php/login/oauth"; originLanding = "https://${cfg.subdomain}.${domain}/"; diff --git a/sp-modules/simple-nixos-mailserver/auth-dovecot.nix b/sp-modules/simple-nixos-mailserver/auth-dovecot.nix index 10dbb3d..4a2615b 100644 --- a/sp-modules/simple-nixos-mailserver/auth-dovecot.nix +++ b/sp-modules/simple-nixos-mailserver/auth-dovecot.nix @@ -1,14 +1,15 @@ { config, lib, pkgs, ... }@nixos-args: let inherit (import ./common.nix nixos-args) - appendLdapBindPwd + appendSetting auth-passthru cfg domain + group is-auth-enabled ; - runtime-directory = "dovecot2"; + runtime-directory = group; ldapConfFile = "/run/${runtime-directory}/dovecot-ldap.conf.ext"; mkLdapSearchScope = scope: ( @@ -37,7 +38,7 @@ let user_filter = ${config.mailserver.ldap.dovecot.userFilter} ''; }; - setPwdInLdapConfFile = appendLdapBindPwd { + setPwdInLdapConfFile = appendSetting { name = "ldap-conf-file"; file = dovecot-ldap-config; prefix = ''dnpass = "''; @@ -45,24 +46,39 @@ let passwordFile = config.mailserver.ldap.bind.passwordFile; destination = ldapConfFile; }; - dovecot-oauth2-conf-file = pkgs.writeTextFile { - name = "dovecot-oauth2.conf.ext"; - text = '' + oauth-client-id = "mailserver"; + oauth-client-secret-fp = + "/run/keys/${group}/kanidm-oauth-client-secret"; + oauth-secret-ExecStartPreScript = pkgs.writeShellScript + "${oauth-client-id}-kanidm-ExecStartPre-script.sh" '' + set -o xtrace + [ -f "${oauth-client-secret-fp}" ] || \ + "${lib.getExe pkgs.openssl}" rand -base64 32 | tr -d "\n" > "${oauth-client-secret-fp}" + ''; + dovecot-oauth2-conf-fp = "/run/${runtime-directory}/dovecot-oauth2.conf.ext"; + write-dovecot-oauth2-conf = appendSetting { + name = "oauth2-conf-file"; + file = builtins.toFile "dovecot-oauth2.conf.ext.template" '' introspection_mode = post - introspection_url = ${auth-passthru.oauth2-introspection-url "roundcube" "VERYSTRONGSECRETFORROUNDCUBE"} - client_id = roundcube - client_secret = VERYSTRONGSECRETFORROUNDCUBE # FIXME username_attribute = username scope = email profile openid tls_ca_cert_file = /etc/ssl/certs/ca-certificates.crt active_attribute = active active_value = true - openid_configuration_url = ${auth-passthru.oauth2-discovery-url "roundcube"} + openid_configuration_url = ${auth-passthru.oauth2-discovery-url oauth-client-id} debug = "no" ''; + prefix = ''introspection_url = "'' + + (auth-passthru.oauth2-introspection-url-prefix oauth-client-id); + suffix = auth-passthru.oauth2-introspection-url-postfix + ''"''; + passwordFile = oauth-client-secret-fp; + destination = dovecot-oauth2-conf-fp; }; in { + # for dovecot2 to have access to get through /run/keys directory + users.groups.keys.members = [ group ]; + mailserver.ldap = { # note: in `ldapsearch` first comes filter, then attributes dovecot.userAttrs = "+"; # all operational attributes @@ -76,7 +92,7 @@ in passdb { driver = oauth2 mechanisms = xoauth2 oauthbearer - args = ${dovecot-oauth2-conf-file} + args = ${dovecot-oauth2-conf-fp} } userdb { @@ -114,13 +130,22 @@ in services.dovecot2.enablePAM = false; systemd.services.dovecot2 = { # TODO does it merge with existing preStart? - preStart = setPwdInLdapConfFile + "\n"; + preStart = setPwdInLdapConfFile + "\n" + write-dovecot-oauth2-conf + "\n"; # FIXME pass dependant services to auth module option instead? wants = [ auth-passthru.oauth2-systemd-service ]; after = [ auth-passthru.oauth2-systemd-service ]; serviceConfig.RuntimeDirectory = lib.mkForce [ runtime-directory ]; }; + systemd.services.kanidm.serviceConfig.ExecStartPre = lib.mkAfter [ + ("-" + oauth-secret-ExecStartPreScript) + ]; # does it merge with existing restartTriggers? - systemd.services.postfix.restartTriggers = [ setPwdInLdapConfFile ]; + systemd.services.postfix.restartTriggers = [ + setPwdInLdapConfFile + write-dovecot-oauth2-conf + ]; + selfprivacy.passthru.mailserver = { + inherit oauth-client-id oauth-client-secret-fp; + }; } diff --git a/sp-modules/simple-nixos-mailserver/auth-postfix.nix b/sp-modules/simple-nixos-mailserver/auth-postfix.nix index 38b141c..1d1029e 100644 --- a/sp-modules/simple-nixos-mailserver/auth-postfix.nix +++ b/sp-modules/simple-nixos-mailserver/auth-postfix.nix @@ -1,7 +1,7 @@ { config, lib, pkgs, ... }@nixos-args: let inherit (import ./common.nix nixos-args) - appendLdapBindPwd + appendSetting auth-passthru is-auth-enabled ; @@ -29,7 +29,7 @@ let query_filter = ${cfg.ldap.postfix.filter} result_attribute = ${cfg.ldap.postfix.mailAttribute} ''; - appendPwdInSenderLoginMap = appendLdapBindPwd { + appendPwdInSenderLoginMap = appendSetting { name = "ldap-sender-login-map"; file = ldapSenderLoginMap; prefix = "bind_pw = "; @@ -43,7 +43,7 @@ let result_attribute = ${cfg.ldap.postfix.uidAttribute} ''; ldapVirtualMailboxMapFile = "/run/postfix/ldap-virtual-mailbox-map.cf"; - appendPwdInVirtualMailboxMap = appendLdapBindPwd { + appendPwdInVirtualMailboxMap = appendSetting { name = "ldap-virtual-mailbox-map"; file = ldapVirtualMailboxMap; prefix = "bind_pw = "; diff --git a/sp-modules/simple-nixos-mailserver/common.nix b/sp-modules/simple-nixos-mailserver/common.nix index eba175b..17ce303 100644 --- a/sp-modules/simple-nixos-mailserver/common.nix +++ b/sp-modules/simple-nixos-mailserver/common.nix @@ -3,8 +3,9 @@ rec { auth-passthru = config.passthru.selfprivacy.auth; domain = config.selfprivacy.domain; is-auth-enabled = config.selfprivacy.modules.auth.enable or false; + group = "dovecot2"; - appendLdapBindPwd = + appendSetting = { name, file, prefix, suffix ? "", passwordFile, destination }: pkgs.writeScript "append-ldap-bind-pwd-in-${name}" '' #!${pkgs.stdenv.shell} diff --git a/sp-modules/simple-nixos-mailserver/config-paths-needed.json b/sp-modules/simple-nixos-mailserver/config-paths-needed.json index 3e0ce04..bb0c127 100644 --- a/sp-modules/simple-nixos-mailserver/config-paths-needed.json +++ b/sp-modules/simple-nixos-mailserver/config-paths-needed.json @@ -1,10 +1,15 @@ [ [ "mailserver" ], + [ "passthru", "selfprivacy", "auth", "admins-group" ], + [ "passthru", "selfprivacy", "auth", "full-users-group" ], [ "passthru", "selfprivacy", "auth", "ldap-base-dn" ], [ "passthru", "selfprivacy", "auth", "ldap-port" ], [ "passthru", "selfprivacy", "auth", "oauth2-discovery-url" ], - [ "passthru", "selfprivacy", "auth", "oauth2-introspection-url" ], + [ "passthru", "selfprivacy", "auth", "oauth2-introspection-url-postfix" ], + [ "passthru", "selfprivacy", "auth", "oauth2-introspection-url-prefix" ], [ "passthru", "selfprivacy", "auth", "oauth2-systemd-service" ], + [ "passthru", "selfprivacy", "roundcube", "oauth-client-id" ], + [ "passthru", "selfprivacy", "roundcube", "oauth-client-secret-fp" ], [ "security", "acme", "certs" ], [ "selfprivacy", "domain" ], [ "selfprivacy", "hashedMasterPassword" ], diff --git a/sp-modules/simple-nixos-mailserver/config.nix b/sp-modules/simple-nixos-mailserver/config.nix index 6868666..68cd44c 100644 --- a/sp-modules/simple-nixos-mailserver/config.nix +++ b/sp-modules/simple-nixos-mailserver/config.nix @@ -5,20 +5,22 @@ let inherit (import ./common.nix { inherit config pkgs; }) auth-passthru domain + group is-auth-enabled ; mailserver-service-account-name = "sp.mailserver.service-account"; mailserver-service-account-token-name = "mailserver-service-account-token"; mailserver-service-account-token-fp = - "/run/keys/mailserver/kanidm-service-account-token"; # FIXME sync with auth module - kanidmExecStartPostScriptRoot = pkgs.writeShellScript - "mailserver-kanidm-ExecStartPost-root-script.sh" + "/run/keys/${group}/kanidm-service-account-token"; # FIXME sync with auth module + kanidmExecStartPreScriptRoot = pkgs.writeShellScript + "mailserver-kanidm-ExecStartPre-root-script.sh" '' - # set-group-ID bit allows for kanidm user to create files, - mkdir -p -v --mode=u+rwx,g+rs,g-w,o-rwx /run/keys/mailserver - chown kanidm:kanidm /run/keys/mailserver + # set-group-ID bit allows for kanidm user to create files inheriting group + mkdir -p -v --mode=u+rwx,g+rs,g-w,o-rwx /run/keys/${group} + chown kanidm:${group} /run/keys/${group} ''; + # create service account token, needed for LDAP kanidmExecStartPostScript = pkgs.writeShellScript "mailserver-kanidm-ExecStartPost-script.sh" '' @@ -173,7 +175,7 @@ lib.mkIf sp.modules.simple-nixos-mailserver.enable (lib.mkMerge [ }; }; } - # the following part is active only when "auth" module is enabled + # the following parts are active only when "auth" module is enabled (lib.attrsets.optionalAttrs (options.selfprivacy.modules ? "auth") (lib.mkIf is-auth-enabled { @@ -199,9 +201,11 @@ lib.mkIf sp.modules.simple-nixos-mailserver.enable (lib.mkMerge [ }; }; # FIXME set auth module option instead + systemd.services.kanidm.serviceConfig.ExecStartPre = lib.mkBefore [ + ("-+" + kanidmExecStartPreScriptRoot) + ]; systemd.services.kanidm.serviceConfig.ExecStartPost = lib.mkAfter [ - ("+" + kanidmExecStartPostScriptRoot) - kanidmExecStartPostScript + ("-" + kanidmExecStartPostScript) ]; })) (lib.attrsets.optionalAttrs