Merge pull request #77532 from symphorien/roundcube

nixos/roundcube: security improvements
This commit is contained in:
Maximilian Bosch 2020-01-22 21:58:25 +01:00 committed by GitHub
commit 2d9e51adc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 59 additions and 20 deletions

View File

@ -5,6 +5,8 @@ with lib;
let let
cfg = config.services.roundcube; cfg = config.services.roundcube;
fpm = config.services.phpfpm.pools.roundcube; fpm = config.services.phpfpm.pools.roundcube;
localDB = cfg.database.host == "localhost";
user = cfg.database.username;
in in
{ {
options.services.roundcube = { options.services.roundcube = {
@ -44,7 +46,10 @@ in
username = mkOption { username = mkOption {
type = types.str; type = types.str;
default = "roundcube"; default = "roundcube";
description = "Username for the postgresql connection"; description = ''
Username for the postgresql connection.
If <literal>database.host</literal> is set to <literal>localhost</literal>, a unix user and group of the same name will be created as well.
'';
}; };
host = mkOption { host = mkOption {
type = types.str; type = types.str;
@ -58,7 +63,12 @@ in
}; };
password = mkOption { password = mkOption {
type = types.str; type = types.str;
description = "Password for the postgresql connection"; description = "Password for the postgresql connection. Do not use: the password will be stored world readable in the store; use <literal>passwordFile</literal> instead.";
default = "";
};
passwordFile = mkOption {
type = types.str;
description = "Password file for the postgresql connection. Must be readable by user <literal>nginx</literal>. Ignored if <literal>database.host</literal> is set to <literal>localhost</literal>, as peer authentication will be used.";
}; };
dbname = mkOption { dbname = mkOption {
type = types.str; type = types.str;
@ -83,14 +93,22 @@ in
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
# backward compatibility: if password is set but not passwordFile, make one.
services.roundcube.database.passwordFile = mkIf (!localDB && cfg.database.password != "") (mkDefault ("${pkgs.writeText "roundcube-password" cfg.database.password}"));
warnings = lib.optional (!localDB && cfg.database.password != "") "services.roundcube.database.password is deprecated and insecure; use services.roundcube.database.passwordFile instead";
environment.etc."roundcube/config.inc.php".text = '' environment.etc."roundcube/config.inc.php".text = ''
<?php <?php
${lib.optionalString (!localDB) "$password = file_get_contents('${cfg.database.passwordFile}');"}
$config = array(); $config = array();
$config['db_dsnw'] = 'pgsql://${cfg.database.username}:${cfg.database.password}@${cfg.database.host}/${cfg.database.dbname}'; $config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}';
$config['log_driver'] = 'syslog'; $config['log_driver'] = 'syslog';
$config['max_message_size'] = '25M'; $config['max_message_size'] = '25M';
$config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}]; $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}];
$config['des_key'] = file_get_contents('/var/lib/roundcube/des_key');
$config['mime_types'] = '${pkgs.nginx}/conf/mime.types';
${cfg.extraConfig} ${cfg.extraConfig}
''; '';
@ -116,12 +134,26 @@ in
}; };
}; };
services.postgresql = mkIf (cfg.database.host == "localhost") { services.postgresql = mkIf localDB {
enable = true; enable = true;
ensureDatabases = [ cfg.database.dbname ];
ensureUsers = [ {
name = cfg.database.username;
ensurePermissions = {
"DATABASE ${cfg.database.username}" = "ALL PRIVILEGES";
};
} ];
}; };
users.users.${user} = mkIf localDB {
group = user;
isSystemUser = true;
createHome = false;
};
users.groups.${user} = mkIf localDB {};
services.phpfpm.pools.roundcube = { services.phpfpm.pools.roundcube = {
user = "nginx"; user = if localDB then user else "nginx";
phpOptions = '' phpOptions = ''
error_log = 'stderr' error_log = 'stderr'
log_errors = on log_errors = on
@ -143,9 +175,7 @@ in
}; };
systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ]; systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ];
systemd.services.roundcube-setup = let systemd.services.roundcube-setup = mkMerge [
pgSuperUser = config.services.postgresql.superUser;
in mkMerge [
(mkIf (cfg.database.host == "localhost") { (mkIf (cfg.database.host == "localhost") {
requires = [ "postgresql.service" ]; requires = [ "postgresql.service" ];
after = [ "postgresql.service" ]; after = [ "postgresql.service" ];
@ -153,22 +183,31 @@ in
}) })
{ {
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
script = '' script = let
mkdir -p /var/lib/roundcube psql = "${lib.optionalString (!localDB) "PGPASSFILE=${cfg.database.passwordFile}"} ${pkgs.postgresql}/bin/psql ${lib.optionalString (!localDB) "-h ${cfg.database.host} -U ${cfg.database.username} "} ${cfg.database.dbname}";
if [ ! -f /var/lib/roundcube/db-created ]; then in
if [ "${cfg.database.host}" = "localhost" ]; then ''
${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create role ${cfg.database.username} with login password '${cfg.database.password}'"; version="$(${psql} -t <<< "select value from system where name = 'roundcube-version';" || true)"
${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create database ${cfg.database.dbname} with owner ${cfg.database.username}"; if ! (grep -E '[a-zA-Z0-9]' <<< "$version"); then
fi ${psql} -f ${cfg.package}/SQL/postgres.initial.sql
PGPASSWORD="${cfg.database.password}" ${pkgs.postgresql}/bin/psql -U ${cfg.database.username} \ fi
-f ${cfg.package}/SQL/postgres.initial.sql \
-h ${cfg.database.host} ${cfg.database.dbname} if [ ! -f /var/lib/roundcube/des_key ]; then
touch /var/lib/roundcube/db-created base64 /dev/urandom | head -c 24 > /var/lib/roundcube/des_key;
# we need to log out everyone in case change the des_key
# from the default when upgrading from nixos 19.09
${psql} <<< 'TRUNCATE TABLE session;'
fi fi
${pkgs.php}/bin/php ${cfg.package}/bin/update.sh ${pkgs.php}/bin/php ${cfg.package}/bin/update.sh
''; '';
serviceConfig.Type = "oneshot"; serviceConfig = {
Type = "oneshot";
StateDirectory = "roundcube";
User = if localDB then user else "nginx";
# so that the des_key is not world readable
StateDirectoryMode = "0700";
};
} }
]; ];
}; };