Merge pull request #63156 from Izorkin/phpfpm-rootless

phpfpm: do not run anything as root
wip/yesman
Elis Hirwing 5 years ago committed by GitHub
commit b5478fd1a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      nixos/modules/rename.nix
  2. 42
      nixos/modules/services/mail/roundcube.nix
  3. 10
      nixos/modules/services/misc/zoneminder.nix
  4. 36
      nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
  5. 8
      nixos/modules/services/web-apps/limesurvey.nix
  6. 38
      nixos/modules/services/web-apps/matomo.nix
  7. 13
      nixos/modules/services/web-apps/nextcloud.nix
  8. 18
      nixos/modules/services/web-apps/restya-board.nix
  9. 45
      nixos/modules/services/web-apps/selfoss.nix
  10. 12
      nixos/modules/services/web-apps/tt-rss.nix
  11. 186
      nixos/modules/services/web-servers/phpfpm/default.nix
  12. 57
      nixos/modules/services/web-servers/phpfpm/pool-options.nix

@ -241,6 +241,12 @@ with lib;
# binfmt
(mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ])
# PHP-FPM
(mkRemovedOptionModule [ "services" "phpfpm" "poolConfigs" ] "Use services.phpfpm.pools instead.")
(mkRemovedOptionModule [ "services" "phpfpm" "phpPackage" ] "Use services.phpfpm.pools.<name>.phpPackage instead.")
(mkRemovedOptionModule [ "services" "phpfpm" "phpOptions" ] "Use services.phpfpm.pools.<name>.phpOptions instead.")
(mkRenamedOptionModule [ "services" "phpfpm" "extraConfig" ] [ "services" "phpfpm" "globalExtraConfig" ])
] ++ (flip map [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
"jsonExporter" "minioExporter" "nginxExporter" "nodeExporter"
"snmpExporter" "unifiExporter" "varnishExporter" ]

@ -105,7 +105,7 @@ in
extraConfig = ''
location ~* \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/phpfpm/roundcube;
fastcgi_pass unix:/run/phpfpm-roundcube/roundcube.sock;
include ${pkgs.nginx}/conf/fastcgi_params;
include ${pkgs.nginx}/conf/fastcgi.conf;
}
@ -119,24 +119,28 @@ in
enable = true;
};
services.phpfpm.poolConfigs.roundcube = ''
listen = /run/phpfpm/roundcube
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
user = nginx
pm = dynamic
pm.max_children = 75
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 20
pm.max_requests = 500
php_admin_value[error_log] = 'stderr'
php_admin_flag[log_errors] = on
php_admin_value[post_max_size] = 25M
php_admin_value[upload_max_filesize] = 25M
catch_workers_output = yes
'';
services.phpfpm.pools.roundcube = {
socketName = "roundcube";
phpPackage = pkgs.php;
user = "${config.services.nginx.user}";
group = "${config.services.nginx.group}";
extraConfig = ''
listen.owner = ${config.services.nginx.user}
listen.group = ${config.services.nginx.group}
listen.mode = 0600
pm = dynamic
pm.max_children = 75
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 20
pm.max_requests = 500
php_admin_value[error_log] = 'stderr'
php_admin_flag[log_errors] = on
php_admin_value[post_max_size] = 25M
php_admin_value[upload_max_filesize] = 25M
catch_workers_output = yes
'';
};
systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ];
systemd.services.roundcube-setup = let

@ -19,7 +19,7 @@ let
useCustomDir = cfg.storageDir != null;
socket = "/run/phpfpm/${dirName}.sock";
socket = "/run/phpfpm-zoneminder/zoneminder.sock";
zms = "/cgi-bin/zms";
@ -284,7 +284,10 @@ in {
phpfpm = lib.mkIf useNginx {
pools.zoneminder = {
listen = socket;
socketName = "zoneminder";
phpPackage = pkgs.php;
user = "${user}";
group = "${group}";
phpOptions = ''
date.timezone = "${config.time.timeZone}"
@ -292,9 +295,6 @@ in {
"extension=${e.pkg}/lib/php/extensions/${e.name}.so") phpExtensions)}
'';
extraConfig = ''
user = ${user}
group = ${group}
listen.owner = ${user}
listen.group = ${group}
listen.mode = 0660

@ -1,7 +1,6 @@
{ config, lib, pkgs, ... }: with lib; let
cfg = config.services.icingaweb2;
poolName = "icingaweb2";
phpfpmSocketName = "/var/run/phpfpm/${poolName}.sock";
defaultConfig = {
global = {
@ -162,19 +161,23 @@ in {
};
config = mkIf cfg.enable {
services.phpfpm.poolConfigs = mkIf (cfg.pool == "${poolName}") {
"${poolName}" = ''
listen = "${phpfpmSocketName}"
listen.owner = nginx
listen.group = nginx
listen.mode = 0600
user = icingaweb2
pm = dynamic
pm.max_children = 75
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 10
'';
services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
"${poolName}" = {
socketName = "${poolName}";
phpPackage = pkgs.php;
user = "icingaweb2";
group = "icingaweb2";
extraConfig = ''
listen.owner = ${config.services.nginx.user}
listen.group = ${config.services.nginx.group}
listen.mode = 0600
pm = dynamic
pm.max_children = 75
pm.start_servers = 2
pm.min_spare_servers = 2
pm.max_spare_servers = 10
'';
};
};
services.phpfpm.phpOptions = mkIf (cfg.pool == "${poolName}")
@ -206,7 +209,7 @@ in {
include ${config.services.nginx.package}/conf/fastcgi.conf;
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:${phpfpmSocketName};
fastcgi_pass unix:/run/phpfpm-${poolName}/${poolName}.sock;
fastcgi_param SCRIPT_FILENAME ${pkgs.icingaweb2}/public/index.php;
'';
};
@ -239,5 +242,8 @@ in {
group = "icingaweb2";
isSystemUser = true;
};
users.users.nginx = {
extraGroups = [ "icingaweb2" ];
};
};
}

@ -202,13 +202,13 @@ in
};
services.phpfpm.pools.limesurvey = {
socketName = "limesurvey";
phpPackage = php;
listen = "/run/phpfpm/limesurvey.sock";
user = "${user}";
group = "${group}";
extraConfig = ''
listen.owner = ${config.services.httpd.user};
listen.group = ${config.services.httpd.group};
user = ${user};
group = ${group};
env[LIMESURVEY_CONFIG] = ${limesurveyConfig}
@ -241,7 +241,7 @@ in
<Directory "${pkg}/share/limesurvey">
<FilesMatch "\.php$">
<If "-f %{REQUEST_FILENAME}">
SetHandler "proxy:unix:/run/phpfpm/limesurvey.sock|fcgi://localhost/"
SetHandler "proxy:unix:/run/phpfpm-limesurvey/limesurvey.sock|fcgi://localhost/"
</If>
</FilesMatch>

@ -4,13 +4,14 @@ let
cfg = config.services.matomo;
user = "matomo";
group = "matomo";
dataDir = "/var/lib/${user}";
deprecatedDataDir = "/var/lib/piwik";
pool = user;
# it's not possible to use /run/phpfpm/${pool}.sock because /run/phpfpm/ is root:root 0770,
# it's not possible to use /run/phpfpm-${pool}/${pool}.sock because /run/phpfpm/ is root:root 0770,
# and therefore is not accessible by the web server.
phpSocket = "/run/phpfpm-${pool}.sock";
phpSocket = "/run/phpfpm-${pool}/${pool}.sock";
phpExecutionUnit = "phpfpm-${pool}";
databaseService = "mysql.service";
@ -137,9 +138,12 @@ in {
isSystemUser = true;
createHome = true;
home = dataDir;
group = user;
group = "${group}";
};
users.groups.${user} = {};
users.users.${config.services.nginx.user} = {
extraGroups = [ "${group}" ];
};
users.groups.${group} = {};
systemd.services.matomo-setup-update = {
# everything needs to set up and up to date before Matomo php files are executed
@ -169,7 +173,7 @@ in {
echo "Migrating from ${deprecatedDataDir} to ${dataDir}"
mv -T ${deprecatedDataDir} ${dataDir}
fi
chown -R ${user}:${user} ${dataDir}
chown -R ${user}:${group} ${dataDir}
chmod -R ug+rwX,o-rwx ${dataDir}
'';
script = ''
@ -225,22 +229,26 @@ in {
serviceConfig.UMask = "0007";
};
services.phpfpm.poolConfigs = let
services.phpfpm.pools = let
# workaround for when both are null and need to generate a string,
# which is illegal, but as assertions apparently are being triggered *after* config generation,
# we have to avoid already throwing errors at this previous stage.
socketOwner = if (cfg.nginx != null) then config.services.nginx.user
else if (cfg.webServerUser != null) then cfg.webServerUser else "";
in {
${pool} = ''
listen = "${phpSocket}"
listen.owner = ${socketOwner}
listen.group = root
listen.mode = 0600
user = ${user}
env[PIWIK_USER_PATH] = ${dataDir}
${cfg.phpfpmProcessManagerConfig}
'';
${pool} = {
socketName = "${pool}";
phpPackage = pkgs.php;
user = "${user}";
group = "${group}";
extraConfig = ''
listen.owner = ${socketOwner}
listen.group = ${group}
listen.mode = 0600
env[PIWIK_USER_PATH] = ${dataDir}
${cfg.phpfpmProcessManagerConfig}
'';
};
};

@ -394,13 +394,14 @@ in {
phpOptions)));
in {
phpOptions = phpOptionsExtensions;
socketName = "nextcloud";
phpPackage = phpPackage;
listen = "/run/phpfpm/nextcloud";
user = "nextcloud";
group = "${config.services.nginx.group}";
extraConfig = ''
listen.owner = nginx
listen.group = nginx
user = nextcloud
group = nginx
listen.owner = ${config.services.nginx.user}
listen.group = ${config.services.nginx.group}
listen.mode = 0600
${cfg.poolConfig}
env[NEXTCLOUD_CONFIG_DIR] = ${cfg.home}/config
env[PATH] = /run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin
@ -466,7 +467,7 @@ in {
fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
fastcgi_param modHeadersAvailable true;
fastcgi_param front_controller_active true;
fastcgi_pass unix:/run/phpfpm/nextcloud;
fastcgi_pass unix:/run/phpfpm-nextcloud/nextcloud.sock;
fastcgi_intercept_errors on;
fastcgi_request_buffering off;
fastcgi_read_timeout 120s;

@ -13,7 +13,7 @@ let
runDir = "/run/restya-board";
poolName = "restya-board";
phpfpmSocketName = "/run/phpfpm/${poolName}.sock";
phpfpmSocketName = "/run/phpfpm-${poolName}/${poolName}.sock";
in
@ -178,9 +178,12 @@ in
config = mkIf cfg.enable {
services.phpfpm.poolConfigs = {
services.phpfpm.pools = {
"${poolName}" = {
listen = phpfpmSocketName;
socketName = "${poolName}";
phpPackage = pkgs.php;
user = "${cfg.user}";
group = "${cfg.group}";
phpOptions = ''
date.timezone = "CET"
@ -192,11 +195,9 @@ in
''}
'';
extraConfig = ''
listen.owner = nginx
listen.group = nginx
listen.owner = ${config.services.nginx.user}
listen.group = ${config.services.nginx.group}
listen.mode = 0600
user = ${cfg.user}
group = ${cfg.group}
pm = dynamic
pm.max_children = 75
pm.start_servers = 10
@ -365,6 +366,9 @@ in
home = runDir;
group = "restya-board";
};
users.users.nginx = {
extraGroups = [ "restya-board" ];
};
users.groups.restya-board = {};
services.postgresql.enable = mkIf (cfg.database.host == null) true;

@ -3,9 +3,9 @@ with lib;
let
cfg = config.services.selfoss;
poolName = "selfoss_pool";
phpfpmSocketName = "/run/phpfpm/${poolName}.sock";
poolName = "selfoss";
phpfpmSocketName = "/run/phpfpm-${poolName}/${poolName}.sock";
group = "${cfg.user}";
dataDir = "/var/lib/selfoss";
selfoss-config =
@ -116,21 +116,25 @@ in
config = mkIf cfg.enable {
services.phpfpm.poolConfigs = mkIf (cfg.pool == "${poolName}") {
"${poolName}" = ''
listen = "${phpfpmSocketName}";
listen.owner = nginx
listen.group = nginx
listen.mode = 0600
user = nginx
pm = dynamic
pm.max_children = 75
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
catch_workers_output = 1
'';
services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
"${poolName}" = {
socketName = "${poolName}";
phpPackage = pkgs.php;
user = "${cfg.user}";
group = "${group}";
extraConfig = ''
listen.owner = ${config.services.nginx.user}
listen.group = ${config.services.nginx.group}
listen.mode = 0600
pm = dynamic
pm.max_children = 75
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
catch_workers_output = 1
'';
};
};
systemd.services.selfoss-config = {
@ -145,7 +149,7 @@ in
# Create the files
cp -r "${pkgs.selfoss}/"* "${dataDir}"
ln -sf "${selfoss-config}" "${dataDir}/config.ini"
chown -R "${cfg.user}" "${dataDir}"
chown -R "${cfg.user}":"${group}" "${dataDir}"
chmod -R 755 "${dataDir}"
'';
wantedBy = [ "multi-user.target" ];
@ -162,5 +166,8 @@ in
};
users.users.nginx = {
extraGroups = [ "${group}" ];
};
};
}

@ -512,12 +512,14 @@ let
services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
"${poolName}" = {
listen = "/var/run/phpfpm/${poolName}.sock";
socketName = "${poolName}";
phpPackage = pkgs.php;
user = "${config.services.nginx.user}";
group = "${config.services.nginx.group}";
extraConfig = ''
listen.owner = nginx
listen.group = nginx
listen.owner = ${config.services.nginx.user}
listen.group = ${config.services.nginx.group}
listen.mode = 0600
user = ${cfg.user}
pm = dynamic
pm.max_children = 75
pm.start_servers = 10
@ -543,7 +545,7 @@ let
locations."~ \.php$" = {
extraConfig = ''
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.listen};
fastcgi_pass unix:/run/phpfpm-${poolName}/${poolName}.sock;
fastcgi_index index.php;
'';
};

@ -4,37 +4,26 @@ with lib;
let
cfg = config.services.phpfpm;
enabled = cfg.poolConfigs != {} || cfg.pools != {};
enabled = cfg.pools != {};
stateDir = "/run/phpfpm";
poolConfigs =
(mapAttrs mapPoolConfig cfg.poolConfigs) //
(mapAttrs mapPool cfg.pools);
mapPoolConfig = n: p: {
phpPackage = cfg.phpPackage;
phpOptions = cfg.phpOptions;
config = p;
};
poolConfigs = (mapAttrs mapPool cfg.pools);
mapPool = n: p: {
phpPackage = p.phpPackage;
phpOptions = p.phpOptions;
config = ''
listen = ${p.listen}
${p.extraConfig}
'';
userPool = p.user;
groupPool = p.group;
};
fpmCfgFile = pool: conf: pkgs.writeText "phpfpm-${pool}.conf" ''
[global]
error_log = syslog
daemonize = no
${cfg.extraConfig}
${cfg.globalExtraConfig}
[${pool}]
${conf}
listen = /run/phpfpm-${pool}/${cfg.pools.${pool}.socketName}.sock
${cfg.pools.${pool}.extraConfig}
'';
phpIni = pool: pkgs.runCommand "php.ini" {
@ -49,86 +38,99 @@ let
'';
in {
options = {
services.phpfpm = {
extraConfig = mkOption {
globalExtraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Extra configuration that should be put in the global section of
Global extra configuration that should be put in the global section of
the PHP-FPM configuration file. Do not specify the options
<literal>error_log</literal> or
<literal>daemonize</literal> here, since they are generated by
NixOS.
'';
};
phpPackage = mkOption {
type = types.package;
default = pkgs.php;
defaultText = "pkgs.php";
description = ''
The PHP package to use for running the PHP-FPM service.
<literal>daemonize</literal> here, since they are generated by NixOS.
'';
};
phpOptions = mkOption {
type = types.lines;
default = "";
example =
''
date.timezone = "CET"
'';
description =
"Options appended to the PHP configuration file <filename>php.ini</filename>.";
};
poolConfigs = mkOption {
pools = mkOption {
default = {};
type = types.attrsOf types.lines;
type = types.attrsOf (types.submodule {
options = {
socketName = mkOption {
type = types.str;
example = "php-fpm";
description = ''
The address on which to accept FastCGI requests.
'';
};
phpPackage = mkOption {
type = types.package;
default = fpmCfg.phpPackage;
defaultText = "config.services.phpfpm.phpPackage";
description = ''
The PHP package to use for running this PHP-FPM pool.
'';
};
phpOptions = mkOption {
type = types.lines;
default = fpmCfg.phpOptions;
defaultText = "config.services.phpfpm.phpOptions";
description = ''
"Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
'';
};
user = mkOption {
type = types.string;
default = "phpfpm";
description = "User account under which phpfpm runs.";
};
group = mkOption {
type = types.string;
default = "phpfpm";
description = "Group account under which phpfpm runs.";
};
extraConfig = mkOption {
type = types.lines;
example = ''
pm = dynamic
pm.max_children = 75
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
'';
description = ''
Extra lines that go into the pool configuration.
See the documentation on <literal>php-fpm.conf</literal> for
details on configuration directives.
'';
};
};
});
example = literalExample ''
{ mypool = '''
listen = /run/phpfpm/mypool
user = nobody
pm = dynamic
pm.max_children = 75
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
''';
{
mypool = {
socketName = "example";
phpPackage = pkgs.php;
user = "phpfpm";
group = "phpfpm";
extraConfig = '''
pm = dynamic
pm.max_children = 75
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
''';
}
}
'';
description = ''
A mapping between PHP-FPM pool names and their configurations.
See the documentation on <literal>php-fpm.conf</literal> for
details on configuration directives. If no pools are defined,
the phpfpm service is disabled.
'';
};
pools = mkOption {
type = types.attrsOf (types.submodule (import ./pool-options.nix {
inherit lib config;
}));
default = {};
example = literalExample ''
{
mypool = {
listen = "/path/to/unix/socket";
phpPackage = pkgs.php;
extraConfig = '''
user = nobody
pm = dynamic
pm.max_children = 75
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
''';
}
}'';
description = ''
PHP-FPM pools. If no pools or poolConfigs are defined, the PHP-FPM
service is disabled.
@ -154,9 +156,6 @@ in {
after = [ "network.target" ];
wantedBy = [ "phpfpm.target" ];
partOf = [ "phpfpm.target" ];
preStart = ''
mkdir -p ${stateDir}
'';
serviceConfig = let
cfgFile = fpmCfgFile pool poolConfig.config;
iniFile = phpIni poolConfig;
@ -166,10 +165,19 @@ in {
ProtectSystem = "full";
ProtectHome = true;
# XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
Type = "notify";
ExecStart = "${poolConfig.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}";
ExecStart = "${poolConfig.phpPackage}/bin/php-fpm -y '${cfgFile}' -c '${iniFile}'";
ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
# User and group
User = "${poolConfig.userPool}";
Group = "${poolConfig.groupPool}";
# Runtime directory and mode
RuntimeDirectory = "phpfpm-${pool}";
RuntimeDirectoryMode = "0750";
# Capabilities
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SETGID" "CAP_SETUID" "CAP_CHOWN" "CAP_SYS_RESOURCE" ];
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SETGID" "CAP_SETUID" "CAP_CHOWN" "CAP_SYS_RESOURCE" ];
};
}
);

@ -1,57 +0,0 @@
{ lib, config }:
let
fpmCfg = config.services.phpfpm;
in
with lib; {
options = {
listen = mkOption {
type = types.str;
example = "/path/to/unix/socket";
description = ''
The address on which to accept FastCGI requests.
'';
};
phpPackage = mkOption {
type = types.package;
default = fpmCfg.phpPackage;
defaultText = "config.services.phpfpm.phpPackage";
description = ''
The PHP package to use for running this PHP-FPM pool.
'';
};
phpOptions = mkOption {
type = types.lines;
default = fpmCfg.phpOptions;
defaultText = "config.services.phpfpm.phpOptions";
description = ''
"Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
'';
};
extraConfig = mkOption {
type = types.lines;
example = ''
user = nobody
pm = dynamic
pm.max_children = 75
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
'';
description = ''
Extra lines that go into the pool configuration.
See the documentation on <literal>php-fpm.conf</literal> for
details on configuration directives.
'';
};
};
}
Loading…
Cancel
Save