PHP FPM Apparmor
Sep 8, 2018 01:56 · 819 words · 4 minute read
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-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>
/usr/sbin/php-fpm flags=(attach_disconnected) {
# load common libraries and their support files
#include <abstractions/base>
# resolve hostnames/usernames
#include <abstractions/nameservice>
# common php files and support files that php needs
#include <abstractions/php7>
# read openssl configuration
#include <abstractions/openssl>
# read the system certificates
#include <abstractions/ssl_certs>
capability net_admin,
# change user/group of a pool
capability setuid,
capability setgid,
# change ownership of the socket so that we can launch with a different user/group as the socket will be owned by
capability chown,
# we want to be able to kill our child processes
capability kill,
# read the php fpm configuration
/etc/php7/fpm/* r,
/etc/php7/fpm/php-fpm.d/ r,
/etc/php7/fpm/php-fpm.d/* r,
# we need write access here to move it into a different apparmor sub profile
/proc/@{pid}/attr/current rw,
# the main log file
/var/log/php-fpm.log rw,
# we need to be able to create all sockets
/run/php/fpm-*.socket rwlk,
# no idea why php tries to open / read/write
deny / rw,
# allow sending signals to our subprocesses
signal (send) peer=/usr/sbin/php-fpm//*,
# allow switching processes to those subprofiles
change_profile /usr/sbin/php-fpm//*,
# load all files from this directory
# store your configurations per pool in this dir
#include <php-fpm.d>
# special local configurations for the main process should go into the local/usr.sbin.php-fpm file
#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>
# move the log file out of the data dir
/var/log/nextcloud/nextcloud.log wlk,
# allow it to read our php files
/srv/www/vhosts/nextcloud/public/ r,
/srv/www/vhosts/nextcloud/public/** r,
# nextcloud wants to lock its configuration file
/srv/www/vhosts/nextcloud/public/config/config.php k,
# we allow writing to our session/data/tmp dir but only if the owner matches our runtime user
owner /srv/www/vhosts/nextcloud/data/** rwlk,
owner /srv/www/vhosts/nextcloud/tmp/** rwlk,
owner /srv/www/vhosts/nextcloud/sessions/** rwlk,
}
The last missing piece: abstractions/php-fpm
#
# LICENSED UNDER AGPL 3.0
#
# see main profile for the explanation for the the includes
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/php7>
#include <abstractions/openssl>
#include <abstractions/ssl_certs>
# allow the master process to send us signals
signal (receive) peer=/usr/sbin/php-fpm,
# This should be in the php abstraction. Bugfix pending upstream.
/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'
/usr/sbin/php-fpm (enforce) 4380 ? Ss 0:00 php-fpm: master process (/etc/php7/fpm/php-fpm.conf)
/usr/sbin/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.