Files
sp-config/sp-modules/roundcube/module.nix
Alexander Tomokhov 4c6228d694 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.
2025-01-31 14:31:58 +04:00

130 lines
4.9 KiB
Nix

{ config, lib, options, pkgs, ... }:
let
domain = config.selfprivacy.domain;
cfg = config.selfprivacy.modules.roundcube;
is-auth-enabled = config.selfprivacy.modules.auth.enable or false;
auth-passthru = config.passthru.selfprivacy.auth;
auth-fqdn = auth-passthru.auth-fqdn;
sp-module-name = "roundcube";
user = "roundcube";
group = "roundcube";
oauth-donor = config.selfprivacy.passthru.mailserver;
kanidm-oauth-client-secret-fp =
"/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 = {
enable = (lib.mkOption {
default = false;
type = lib.types.bool;
description = "Enable";
}) // {
meta = {
type = "enable";
};
};
subdomain = (lib.mkOption {
default = "roundcube";
type = lib.types.strMatching "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
description = "Subdomain";
}) // {
meta = {
widget = "subdomain";
type = "string";
regex = "[A-Za-z0-9][A-Za-z0-9\-]{0,61}[A-Za-z0-9]";
weight = 0;
};
};
};
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";
'';
};
services.nginx.virtualHosts."${cfg.subdomain}.${domain}" = {
forceSSL = true;
useACMEHost = domain;
enableACME = false;
};
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 = [ user ];
services.roundcube.extraConfig = lib.mkAfter ''
$config['oauth_provider'] = 'generic';
$config['oauth_provider_name'] = '${auth-passthru.oauth2-provider-name}';
$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-donor.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.mkBefore [
("-+" + kanidmExecStartPreScriptRoot)
];
};
services.kanidm.provision = {
groups = {
"sp.roundcube.admins".members = [ auth-passthru.admins-group ];
"sp.roundcube.users".members =
[ "sp.roundcube.admins" auth-passthru.full-users-group ];
};
systems.oauth2.${oauth-donor.oauth-client-id} = {
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;
};
};
})
)
]);
}