PHP FPM Apparmor
I haven’t been using apache for a few years now … oh wow maybe like 15 years now.
There was one feature I really liked with apache and apparmor though … mod_changehat
.
The module allows me to assign different apparmor scopes to apache scopes. So
you could limit that your wordpress vhost can not access the files of your
nextcloud vhost even though they are running in the same apache.
With php-fastcgi you could start multiple instances and assign apparmor profiles to those.
Though nowadays you will probably use php-fpm
. For a while that meant you could split up apps with different user/groups but not with different apparmor scopes, all were in one profile.
Well … then we got this nice proposal https://wiki.php.net/rfc/fpm_change_hat, which is implemented as well!
So lets make use of this.
PHP FPM config
On openSUSE the config goes into /etc/php7/fpm/php-fpm.d/nextcloud.conf
The format is explained in the php documentation. Also check /etc/php7/fpm/php-fpm.d/www.conf.default
for a base configuration with a lot of inline comments.
[nextcloud]
user = nextcloud
group = nextcloud
# our hook for apparmor
apparmor_hat = nextcloud
listen = /run/php-fpm/fpm-nextcloud.socket
listen.owner = nginx
listen.group = nginx
listen.mode = 0660
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /srv/www/vhosts/nextcloud
env[PATH]="/usr/bin:/bin"
php_admin_value[upload_tmp_dir] = /srv/www/vhosts/nextcloud/tmp
php_admin_value[session.save_path] = "/srv/www/vhosts/nextcloud/sessions"
php_admin_value[upload_max_filesize]=10G
php_admin_value[post_max_size]=10G
The apparmor side
The easy approach is we stuff it all into one profile for /usr/sbin/php-fpm
.
But this becomes a maintenance nightmare. Especially if you want to ship subprofiles/hats in packages.
But when poking around in the apparmor profile for apache, I noticed a neat little trick
#include <apache2.d>
The main profile
For the main profile we use /etc/apparmor.d/usr.sbin.php-fpm
to follow the
naming convention.
The format is explained in the man apparmor.d
#
# LICENSED UNDER AGPL 3.0
#
#include <tunables/global>
profile php-fpm /usr/sbin/php-fpm flags=(attach_disconnected) {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/php7>
#include <abstractions/php-fixes>
#include <abstractions/openssl>
#include <abstractions/ssl_certs>
capability net_admin,
capability setuid,
capability setgid,
capability chown,
capability kill,
/etc/php7/fpm/* r,
/etc/php7/fpm/php-fpm.d/ r,
/etc/php7/fpm/php-fpm.d/* r,
/proc/@{pid}/attr/current rw,
/var/log/php-fpm.log rw,
/run/php-fpm/fpm-*.socket rwlk,
/run/php-fpm/php-fpm.pid rwlk,
deny / rw,
signal (send) peer=php-fpm//*,
change_profile -> php-fpm//*,
#include <php-fpm.d>
#include <local/usr.sbin.php-fpm>
}
A subprofile for a pool
To follow the example we create a subprofile for our nextcloud pool.
For this we create a /etc/apparmor.d/php-fpm.d/nextcloud
file:
#
# LICENSED UNDER AGPL 3.0
#
profile nextcloud flags=(attach_disconnected) {
#include <abstractions/php-fpm>
/var/log/nextcloud/nextcloud.log rwlk,
/srv/www/vhosts/nextcloud/public/ r,
/srv/www/vhosts/nextcloud/public/** r,
/srv/www/vhosts/nextcloud/public/config/config.php k,
owner /srv/www/vhosts/nextcloud/data/** rwlk,
owner /srv/www/vhosts/nextcloud/tmp/** rwlk,
owner /srv/www/vhosts/nextcloud/sessions/ r,
owner /srv/www/vhosts/nextcloud/sessions/** rwlk,
###
# include snippets provides e.g. by plugin directories
#
# We use the directory layout for abstraction includes in AppArmor 3
#
#include if exists <php-fpm.d/nextcloud.d>
#
# allow admins to add snippets as well.
#
#include if exists <local/php-fpm.d/nextcloud.d>
#
###
}
The last missing piece: abstractions/php-fpm
#
# LICENSED UNDER AGPL 3.0
#
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/php7>
#include <abstractions/php-fixes>
#include <abstractions/openssl>
#include <abstractions/ssl_certs>
signal (receive) peer=php-fpm,
/usr/share/icu/*/*.dat r,
All in action
If you want to see if a program runs under apparmor/selinux you can use the Z
parameter for ps
.
$ ps afxZ | grep '^/usr/sbin/php-fpm'
php-fpm (enforce) 4380 ? Ss 0:00 php-fpm: master process (/etc/php7/fpm/php-fpm.conf)
php-fpm//nextcloud (enforce) 5874 ? S 0:02 \_ php-fpm: pool nextcloud
If you are running with openSUSE, you can use my php-fpm-apparmor package.
$ zypper ar obs://home:darix:apps home_darix_apps
$ zypper ref
$ zypper in php-fpm-apparmor
The package comes with a /etc/apparmor.d/php-fpm.d/default
, which you can use as a starting point.
Dropping things in
So in the beginning I mentioned we want do all the work to allow easy integration with other packages. If we wanted to do something like our example for roundcube, our package only needs to install 2 files
# define a new pool for roundcube
/etc/php7/fpm/php-fpm.d/roundcube.conf
# define apparmor scope for the pool
/etc/apparmor.d/php-fpm.d/roundcube
Reload apparmor and restart php-fpm to activate the profile and you are done.