Separate php-fpm pools for great victory
The Blog of Nathan D. Smith
Let's say you use a pretty standard Nginx/PHp-fpm/Linux/Mariadb ("nephilim"?) stack for hosting web applications. On most distributions you'll have a single php-fpm pool which spawns workers to execute tasks handed to it by the web server, either via a POSIX or TCP socket. That's great for simplicity's sake.
But what if you have some web-app you want to run but don't really trust. HINT: You shouldn't really trust any internet facing application. If there's a remote code execution flaw in the code for webapp foo, an attacker then assumes the security persona of the entire php-fpm pool, including access to other applications' memory, file-system space, and databases. Yikes!
Nothing in the below is particularly novel, but it may be useful nonetheless. There's also the container approach to solving this, which is probably more secure overall, but is not available to everyone. The context of the examples below is running GNU Social on Centos 7. (On Centos 7, nginx runs as the "nginx" user, and php-fpm runs by default as the "apache" user, the same as httpd normally runs as).
Separate Databases
Each application should have its own database with its own unique username and password. I think most people know this, but stating it here for good measure.
Separate User
Each web application should have its own local unprivileged user account. If that account never needs a shell environment, it is best to not give it a login shell either. GNU Social requires a shell to run its queue daemon scripts, so here is how I did it:
useradd -m -s /bin/bash social
Assuming you have "PermitEmptyPasswords no" in your sshd\config, you don't have to set a password. Otherwise set a very strong one. It'll never be used under normal operations.
A note specific to GNU Social: the queue daemons should run as this user as well. We're in a systemd world now on Linux, so see an example of a unit file for queue daemons. You'll want to set the user to set:
User=social Group=social
Separate File-system Path
Take note of the group your webserver (nginx in my example) runs as. In Centos it is "nginx", in Debian derivatives it appears to be "www-data".
You've extracted your web application's files into var/www/social.example.com . You'll want to lock this down so that only the application pool user and the webserver can have access:
chown -R social:nginx /var/www/social.example.com/ chmod -R o-rwx /var/www/social.example.com/ # Also follow GNU Social's install instructions for setting # write permissions on avatar/ file/, and the base directory so # config.php can be written by the installer
This way the web server can read the application's root contents (e.g. php scripts and static files), and the php-fpm pool for your application will have write access (for writing the configuration at install time and uploading files). Other users should have no access to this location (go ahead, test it).
When you create a separate php-fpm pool below, you'll need to provide a session and cache path which are writable by the social user:
mkdir -p /var/lib/social/{cache,session} chown root:social /var/lib/social/{cache,session} chmod 770 /var/lib/social/{cache,session}
Separate php-fpm pools
Pools for php-fpm.d are typically found in etc/php-fpm.d. Your mileage may vary based on distribution, etc. Take a look at the default pool to see how it is configured.
Depending on the resources of your system, you may want to reduce the value of pm.maxchildren (and relate settings) to make room for your new pool. This can be tuned depending on the relative resource demands of your pools.
Now copy the default pool to a new file in the same directory called social.conf and edit it. Below are the required edits:
- Give the pool a unique socket, either a different path for a POSIX socket, or a different port number for a TCP sockets. Assuming everything is on a single server, I recommend the POSIX socket, e.g. "listen = /var/run/php-fpm-social.sock"
- Set "user = social"
- Set "group = social"
- Set "php\value[session.save\path] = /var/lib/social/session"
- Set "php\value[wsdl\cache\dir] = /var/lib/social/cache"
Configure your nginx configuration file for the site to use the unique socket listed above:
fastcgi_pass unix:/var/run/php-fpm-social.sock;
Now you are ready to restart php-fpm and nginx and your queue daemons. If you run the following, you should see some php-fpm workers running as social:
ps aux | grep php-fpm
If there is trouble, there are a few places you'll want to look:
- nginx error log
- nginx access log
- php-fpm error.log
- php-fpm www-error.log
Assuming that worked, you've got a separate, more-secure install of GNU Social. I did the foolish thing and changed the configuration after installing the site. I don't recommend it, unless you want an exercise in rapid troubleshooting. ;-)