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, 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.

user = nextcloud
group = nextcloud

# our hook for apparmor
apparmor_hat = nextcloud

listen = /run/php/fpm-nextcloud.socket

listen.owner = nginx = 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

php_admin_value[upload_tmp_dir] = /srv/www/vhosts/nextcloud/tmp
php_admin_value[session.save_path] = "/srv/www/vhosts/nextcloud/sessions"

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

#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:

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

# 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
# define apparmor scope for the pool

Reload apparmor and restart php-fpm to activate the profile and you are done.