diff --git a/sp-modules/auth/common.nix b/sp-modules/auth/common.nix new file mode 100644 index 0000000..91f0f0c --- /dev/null +++ b/sp-modules/auth/common.nix @@ -0,0 +1,35 @@ +{ config, lib, pkgs, ... }: +rec { + domain = config.selfprivacy.domain; + cfg = config.selfprivacy.modules.auth; + passthru = config.passthru.selfprivacy.auth; + auth-fqdn = cfg.subdomain + "." + domain; + + kanidm_ldap_port = 3636; + + # e.g. "dc=mydomain,dc=com" + ldap_base_dn = + lib.strings.concatMapStringsSep + "," + (x: "dc=" + x) + (lib.strings.splitString "." domain); + + appendLdapBindPwd = + { name, file, prefix, suffix ? "", passwordFile, destination }: + pkgs.writeScript "append-ldap-bind-pwd-in-${name}" '' + #!${pkgs.stdenv.shell} + set -euo pipefail + + baseDir=$(dirname ${destination}) + if (! test -d "$baseDir"); then + mkdir -p $baseDir + chmod 755 $baseDir + fi + + cat ${file} > ${destination} + echo -n '${prefix}' >> ${destination} + cat ${passwordFile} >> ${destination} + echo -n '${suffix}' >> ${destination} + chmod 600 ${destination} + ''; +} diff --git a/sp-modules/auth/config-paths-needed.json b/sp-modules/auth/config-paths-needed.json index 7b922da..2253194 100644 --- a/sp-modules/auth/config-paths-needed.json +++ b/sp-modules/auth/config-paths-needed.json @@ -2,6 +2,7 @@ ["mailserver", "fqdn"], ["mailserver", "ldap"], ["mailserver", "vmailUID"], + ["passthru", "selfprivacy", "auth"], ["security", "acme", "certs"], ["selfprivacy", "domain"], ["selfprivacy", "modules"], diff --git a/sp-modules/auth/flake.nix b/sp-modules/auth/flake.nix index 15cfc9c..29d43bf 100644 --- a/sp-modules/auth/flake.nix +++ b/sp-modules/auth/flake.nix @@ -36,11 +36,12 @@ + /nixos/modules/services/security/oauth2-proxy-nginx.nix) ./module.nix ./ldap-postfix.nix + ./ldap-dovecot.nix ]; nixpkgs.overlays = [ self.overlays.default ]; selfprivacy.modules.auth.enable = true; - selfprivacy.modules.auth.debug = true; + selfprivacy.modules.auth.debug = false; }; configPathsNeeded = diff --git a/sp-modules/auth/ldap-dovecot.nix b/sp-modules/auth/ldap-dovecot.nix new file mode 100644 index 0000000..ee6ae92 --- /dev/null +++ b/sp-modules/auth/ldap-dovecot.nix @@ -0,0 +1,129 @@ +{ config, lib, pkgs, ... }@nixos-args: +let + inherit (import ./common.nix nixos-args) + appendLdapBindPwd + cfg + domain + passthru + ; + + ldapConfFile = "/run/dovecot2/dovecot-ldap.conf.ext"; # FIXME get "dovecot2" from `config` + mkLdapSearchScope = scope: ( + if scope == "sub" then "subtree" + else if scope == "one" then "onelevel" + else scope + ); + dovecot-ldap-config = pkgs.writeTextFile { + name = "dovecot-ldap.conf.ext.template"; + text = '' + ldap_version = 3 + uris = ${lib.concatStringsSep " " config.mailserver.ldap.uris} + ${lib.optionalString config.mailserver.ldap.startTls '' + tls = yes + ''} + # tls_require_cert = hard + # tls_ca_cert_file = ${config.mailserver.ldap.tlsCAFile} + dn = ${config.mailserver.ldap.bind.dn} + sasl_bind = no + auth_bind = no + base = ${config.mailserver.ldap.searchBase} + scope = ${mkLdapSearchScope config.mailserver.ldap.searchScope} + ${lib.optionalString (config.mailserver.ldap.dovecot.userAttrs != null) '' + user_attrs = ${config.mailserver.ldap.dovecot.userAttrs} + ''} + user_filter = ${config.mailserver.ldap.dovecot.userFilter} + ''; + }; + setPwdInLdapConfFile = appendLdapBindPwd { + name = "ldap-conf-file"; + file = dovecot-ldap-config; + prefix = ''dnpass = "''; + suffix = ''"''; + passwordFile = config.mailserver.ldap.bind.passwordFile; + destination = ldapConfFile; + }; + dovecot-oauth2-conf-file = pkgs.writeTextFile { + name = "dovecot-oauth2.conf.ext"; + text = '' + introspection_mode = post + introspection_url = ${passthru.oauth2-introspection-url "roundcube" "VERYSTRONGSECRETFORROUNDCUBE"} + client_id = roundcube + client_secret = VERYSTRONGSECRETFORROUNDCUBE # FIXME + username_attribute = username + # scope = email groups profile openid dovecotprofile + scope = email profile openid + tls_ca_cert_file = /etc/ssl/certs/ca-certificates.crt + active_attribute = active + active_value = true + openid_configuration_url = ${passthru.oauth2-discovery-url "roundcube"} + debug = ${if cfg.debug then "yes" else "no"} + ''; + }; +in +{ + mailserver.ldap = { + # note: in `ldapsearch` first comes filter, then attributes + dovecot.userAttrs = "+"; # all operational attributes + # TODO: investigate whether "mail=%u" is better than: + # dovecot.userFilter = "(&(class=person)(uid=%n))"; + }; + + services.dovecot2.extraConfig = '' + auth_mechanisms = xoauth2 oauthbearer + + passdb { + driver = oauth2 + mechanisms = xoauth2 oauthbearer + args = ${dovecot-oauth2-conf-file} + } + + userdb { + driver = static + args = uid=virtualMail gid=virtualMail home=/var/vmail/${domain}/%u + } + + # provide SASL via unix socket to postfix + service auth { + unix_listener /var/lib/postfix/private-auth { + mode = 0660 + user = postfix + group = postfix + } + } + service auth { + unix_listener auth-userdb { + mode = 0660 + user = dovecot2 + } + unix_listener dovecot-auth { + mode = 0660 + # Assuming the default Postfix user and group + user = postfix + group = postfix + } + } + + userdb { + driver = ldap + args = ${ldapConfFile} + default_fields = home=/var/vmail/${domain}/%u uid=${toString config.mailserver.vmailUID} gid=${toString config.mailserver.vmailUID} + } + + #auth_username_format = %Ln + + # FIXME + auth_debug = yes + auth_debug_passwords = yes # Be cautious with this in production as it logs passwords + auth_verbose = yes + mail_debug = yes + ''; + services.dovecot2.enablePAM = false; + systemd.services.dovecot2 = { + # TODO does it merge with existing preStart? + preStart = setPwdInLdapConfFile + "\n"; + }; + + # does it merge with existing restartTriggers? + systemd.services.postfix.restartTriggers = [ setPwdInLdapConfFile ]; + +} diff --git a/sp-modules/auth/ldap-postfix.nix b/sp-modules/auth/ldap-postfix.nix index 3a0e905..0739f44 100644 --- a/sp-modules/auth/ldap-postfix.nix +++ b/sp-modules/auth/ldap-postfix.nix @@ -1,26 +1,11 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, ... }@nixos-args: let + inherit (import ./common.nix nixos-args) + appendLdapBindPwd + ; + cfg = config.mailserver; - appendLdapBindPwd = - { name, file, prefix, suffix ? "", passwordFile, destination }: - pkgs.writeScript "append-ldap-bind-pwd-in-${name}" '' - #!${pkgs.stdenv.shell} - set -euo pipefail - - baseDir=$(dirname ${destination}) - if (! test -d "$baseDir"); then - mkdir -p $baseDir - chmod 755 $baseDir - fi - - cat ${file} > ${destination} - echo -n '${prefix}' >> ${destination} - cat ${passwordFile} >> ${destination} - echo -n '${suffix}' >> ${destination} - chmod 600 ${destination} - ''; - ldapSenderLoginMapFile = "/run/postfix/ldap-sender-login-map.cf"; submissionOptions.smtpd_sender_login_maps = lib.mkForce "hash:/etc/postfix/vaccounts,ldap:${ldapSenderLoginMapFile}"; @@ -65,6 +50,10 @@ let }; in { + mailserver.ldap = { + postfix.mailAttribute = "mail"; + postfix.uidAttribute = "uid"; + }; systemd.services.postfix-setup = { preStart = '' ${appendPwdInVirtualMailboxMap} @@ -75,7 +64,12 @@ in services.postfix = { # the list should be merged with other options from nixos-mailserver config.virtual_mailbox_maps = [ "ldap:${ldapVirtualMailboxMapFile}" ]; - submissionOptions = submissionOptions; + inherit submissionOptions; submissionsOptions = submissionOptions; + # extraConfig = '' + # debug_peer_list = + # debug_peer_level = 3 + # smtp_tls_security_level = encrypt + # ''; }; } diff --git a/sp-modules/auth/module.nix b/sp-modules/auth/module.nix index d51335f..063dbfd 100644 --- a/sp-modules/auth/module.nix +++ b/sp-modules/auth/module.nix @@ -1,96 +1,17 @@ -{ config, lib, pkgs, ... }: +{ config, lib, pkgs, ... }@nixos-args: let - domain = config.selfprivacy.domain; - cfg = config.selfprivacy.modules.auth; - auth-fqdn = cfg.subdomain + "." + domain; - oauth2-introspection-url = client_id: client_secret: - "https://${client_id}:${client_secret}@${auth-fqdn}/oauth2/token/introspect"; - oauth2-discovery-url = client_id: "https://${auth-fqdn}/oauth2/openid/${client_id}/.well-known/openid-configuration"; - - kanidm-bind-address = "127.0.0.1:3013"; - ldap_host = "127.0.0.1"; - ldap_port = 3636; - # e.g. "dc=mydomain,dc=com" - ldap_base_dn = - lib.strings.concatMapStringsSep - "," - (x: "dc=" + x) - (lib.strings.splitString "." domain); - - dovecot-oauth2-conf-file = pkgs.writeTextFile { - name = "dovecot-oauth2.conf.ext"; - text = '' - introspection_mode = post - introspection_url = ${oauth2-introspection-url "roundcube" "VERYSTRONGSECRETFORROUNDCUBE"} - client_id = roundcube - client_secret = VERYSTRONGSECRETFORROUNDCUBE # FIXME - username_attribute = username - # scope = email groups profile openid dovecotprofile - scope = email profile openid - tls_ca_cert_file = /etc/ssl/certs/ca-certificates.crt - active_attribute = active - active_value = true - openid_configuration_url = ${oauth2-discovery-url "roundcube"} - debug = ${if cfg.debug then "yes" else "no"} - ''; - }; + inherit (import ./common.nix nixos-args) + auth-fqdn + cfg + domain + kanidm_ldap_port + ldap_base_dn + passthru + ; lua_core_path = "${pkgs.luajitPackages.lua-resty-core}/lib/lua/5.1/?.lua"; lua_lrucache_path = "${pkgs.luajitPackages.lua-resty-lrucache}/lib/lua/5.1/?.lua"; lua_path = "${lua_core_path};${lua_lrucache_path};"; - ldapConfFile = "/run/dovecot2/dovecot-ldap.conf.ext"; # FIXME get "dovecot2" from `config` - mkLdapSearchScope = scope: ( - if scope == "sub" then "subtree" - else if scope == "one" then "onelevel" - else scope - ); - appendLdapBindPwd = - { name, file, prefix, suffix ? "", passwordFile, destination }: - pkgs.writeScript "append-ldap-bind-pwd-in-${name}" '' - #!${pkgs.stdenv.shell} - set -euo pipefail - - baseDir=$(dirname ${destination}) - if (! test -d "$baseDir"); then - mkdir -p $baseDir - chmod 755 $baseDir - fi - - cat ${file} > ${destination} - echo -n '${prefix}' >> ${destination} - cat ${passwordFile} >> ${destination} - echo -n '${suffix}' >> ${destination} - chmod 600 ${destination} - ''; - dovecot-ldap-config = pkgs.writeTextFile { - name = "dovecot-ldap.conf.ext.template"; - text = '' - ldap_version = 3 - uris = ${lib.concatStringsSep " " config.mailserver.ldap.uris} - ${lib.optionalString config.mailserver.ldap.startTls '' - tls = yes - ''} - # tls_require_cert = hard - # tls_ca_cert_file = ${config.mailserver.ldap.tlsCAFile} - dn = ${config.mailserver.ldap.bind.dn} - sasl_bind = no - auth_bind = no - base = ${config.mailserver.ldap.searchBase} - scope = ${mkLdapSearchScope config.mailserver.ldap.searchScope} - ${lib.optionalString (config.mailserver.ldap.dovecot.userAttrs != null) '' - user_attrs = ${config.mailserver.ldap.dovecot.userAttrs} - ''} - user_filter = ${config.mailserver.ldap.dovecot.userFilter} - ''; - }; - setPwdInLdapConfFile = appendLdapBindPwd { - name = "ldap-conf-file"; - file = dovecot-ldap-config; - prefix = ''dnpass = "''; - suffix = ''"''; - passwordFile = config.mailserver.ldap.bind.passwordFile; - destination = ldapConfFile; - }; in { options.selfprivacy.modules.auth = { @@ -118,16 +39,6 @@ in # kanidm with Rust code patches for OAuth and admin passwords provisioning package = pkgs.kanidm.withSecretProvisioning; - # package = pkgs.kanidm.withSecretProvisioning.overrideAttrs (_: { - # version = "git"; - # src = pkgs.fetchFromGitHub { - # owner = "AleXoundOS"; - # repo = "kanidm"; - # rev = "a1a55f2e53facbfa504c7d64c44c3b5d0eb796c2"; - # hash = "sha256-ADh4Zwn6EMt4CiOrvgG0RbmNMeR5i0ilVTxF46t/wm8="; - # }; - # doCheck = false; - # }); serverSettings = { inherit domain; @@ -143,13 +54,15 @@ in tls_key = "${config.security.acme.certs.${domain}.directory}/key.pem"; - bindaddress = kanidm-bind-address; # nginx should connect to it - ldapbindaddress = "${ldap_host}:${toString ldap_port}"; + # nginx should proxy requests to it + bindaddress = passthru.kanidm-bind-address; + + ldapbindaddress = "127.0.0.1:${toString kanidm_ldap_port}"; # kanidm is behind a proxy trust_x_forward_for = true; - log_level = "trace"; # FIXME + log_level = if cfg.debug then "trace" else "info"; }; provision = { enable = true; @@ -162,24 +75,6 @@ in verify_hostnames = false; # FIXME }; }; - # systemd.services.kanidm.serviceConfig.ExecStartPost = lib.mkBefore '' - # # check kanidm online here with curl again? - # # use API key for group creation? - # ''; - # services.phpfpm.pools.roundcube.settings = { - # catch_workers_output = true; - # "php_admin_value[error_log]" = "stdout"; - # "php_admin_flag[log_errors]" = true; - # "php_admin_value[log_level]" = "debug"; - # }; - services.phpfpm.phpOptions = '' - error_reporting = E_ALL - display_errors = on; - ''; - systemd.services.phpfpm-roundcube.serviceConfig = { - StandardError = "journal"; - StandardOutput = "journal"; - }; services.nginx = { enable = true; @@ -196,11 +91,6 @@ in useACMEHost = domain; forceSSL = true; locations."/" = { - # extraConfig = '' - # if ($args != $new_args) { - # rewrite ^ /ui/oauth2?$new_args? last; - # } - # ''; extraConfig = lib.strings.optionalString cfg.debug '' access_log /var/log/nginx/kanidm.log kanidm; @@ -230,16 +120,9 @@ in end '; ''; - proxyPass = "https://${kanidm-bind-address}"; + proxyPass = "https://${passthru.kanidm-bind-address}"; }; }; - # appendHttpConfig = '' - # # Define a map to modify redirect_uri and append %2F if missing - # map $args $new_args { - # ~^((.*)(redirect_uri=[^&]+)(?!%2F)(.*))$ $2$3%2F$4; - # default $args; - # } - # ''; }; # TODO move to mailserver module everything below @@ -248,7 +131,7 @@ in mailserver.loginAccounts = lib.mkForce { }; mailserver.extraVirtualAliases = lib.mkForce { }; - # LDAP is needed for Postfix to query Kanidm about email address ownership + # LDAP is needed for Postfix to query Kanidm about email address ownership. # LDAP is needed for Dovecot also. mailserver.ldap = { enable = false; @@ -256,94 +139,24 @@ in bind.dn = "dn=token"; # TODO change in this file should trigger system restart dovecot bind.passwordFile = "/run/keys/dovecot/kanidm-service-account-token"; # FIXME + # searchBase = "ou=persons," + ldap_base_dn; - searchBase = ldap_base_dn; - # searchScope = "sub"; - uris = [ "ldaps://localhost:${toString ldap_port}" ]; + searchBase = ldap_base_dn; # TODO refine this - # note: in `ldapsearch` first comes filter, then attributes - dovecot.userAttrs = "+"; # all operational attributes - # TODO: investigate whether "mail=%u" is better than: - # dovecot.userFilter = "(&(class=person)(uid=%n))"; - postfix.mailAttribute = "mail"; - postfix.uidAttribute = "uid"; + # NOTE: 127.0.0.1 instead of localhost does not work for unknown reason + uris = [ "ldaps://localhost:${toString kanidm_ldap_port}" ]; }; - services.dovecot2.extraConfig = '' - auth_mechanisms = xoauth2 oauthbearer - - passdb { - driver = oauth2 - mechanisms = xoauth2 oauthbearer - args = ${dovecot-oauth2-conf-file} - } - - userdb { - driver = static - args = uid=virtualMail gid=virtualMail home=/var/vmail/${domain}/%u - } - - # provide SASL via unix socket to postfix - service auth { - unix_listener /var/lib/postfix/private-auth { - mode = 0660 - user = postfix - group = postfix - } - } - service auth { - unix_listener auth-userdb { - mode = 0660 - user = dovecot2 - } - unix_listener dovecot-auth { - mode = 0660 - # Assuming the default Postfix user and group - user = postfix - group = postfix - } - } - - userdb { - driver = ldap - args = ${ldapConfFile} - default_fields = home=/var/vmail/${domain}/%u uid=${toString config.mailserver.vmailUID} gid=${toString config.mailserver.vmailUID} - } - - #auth_username_format = %Ln - - # FIXME - auth_debug = yes - auth_debug_passwords = yes # Be cautious with this in production as it logs passwords - auth_verbose = yes - mail_debug = yes - ''; - services.dovecot2.enablePAM = false; - services.postfix.extraConfig = '' - debug_peer_list = 94.43.135.210, 134.209.202.195 - debug_peer_level = 3 - smtp_use_tls = yes - # these below are already set in nixos-mailserver/mail-server/postfix.nix - # smtpd_sasl_local_domain = ${domain} - # smtpd_relay_restrictions = permit_sasl_authenticated, reject - # smtpd_sender_restrictions = - # smtpd_sender_login_maps = - # smtpd_sasl_type = dovecot - # smtpd_sasl_path = private-auth - # smtpd_sasl_auth_enable = yes - ''; - - systemd.services.dovecot2 = { - # TODO does it merge with existing preStart? - preStart = setPwdInLdapConfFile + "\n"; - }; - - # does it merge with existing restartTriggers? - systemd.services.postfix.restartTriggers = [ setPwdInLdapConfFile ]; - environment.systemPackages = lib.lists.optionals cfg.debug [ pkgs.shelldap pkgs.openldap ]; + + passthru.selfprivacy.auth = { + kanidm-bind-address = "127.0.0.1:3013"; + oauth2-introspection-url = client_id: client_secret: + "https://${client_id}:${client_secret}@${auth-fqdn}/oauth2/token/introspect"; + oauth2-discovery-url = client_id: "https://${auth-fqdn}/oauth2/openid/${client_id}/.well-known/openid-configuration"; + }; }; }