hosted services
Recently needed one part of a site to run as one user, and another path to run as another. I needed WordPress to run as a non-content owner and yet allow wp-admin to be able to change bits of the site.
There's two main ways I see to do this, either use AssignUserID*
or
tell Apache to proxy the traffic to one of two PHP FPMs. This is
probably better, since Apache doesn't have to fork a new child and
setuid. The first part of this article talks about using
AssignUserIDExpr
, you'll find the proxy method at the end.
assignuserid
In this setup, dummy is the owner of the WP content directories, dummy-read doesn't have any write permissions within the WP structure.
Here's what I did:
RewriteEngine on
RewriteRule ^/wordpress.* - [E=ITK:dummy_read]
RewriteRule ^/wordpress/wp-admin.* - [E=ITK:dummy]
AssignUserIDExpr %{reqenv:ITK}
This needs mod_itk (AKA: libapache2-mpm-itk) and mod_rewrite of course.
Other ways could be to setup multiple FPM servers with different user
s
to run as, then rewrite to each as a proxy to the socket depending on
the conditions. Up to you!
RewriteRule ^ - [E=ITKUID:dummy_read,E=DB_USER:mysite_read,E=DB_PASSWORD:mysite,E=DB_NAME:mysite,E=DB_HOST:localhost]
RewriteCond %{QUERY_STRING} ^.*rest_route.*
RewriteCond %{REQUEST_METHOD} POST
RewriteRule ^/wordpress/index.php.* - [E=DB_USER:mysite,E=DB_PASSWORD:mysite,E=DB_NAME:mysite,E=DB_HOST:localhost]
RewriteRule ^/wordpress/(wp-admin|wp-login) - [E=ITKUID:dummy,E=DB_USER:mysite,E=DB_PASSWORD:mysite,E=DB_NAME:mysite,E=DB_HOST:localhost]
AssignUserIDExpr %{reqenv:ITKUID}
sql (optional)
For some sites a rewrite rule setup with the following pattern permits
only some requests through using restricted access. I created a reader
user with just select
and the other paths get read/write:
--- wp-config-sample.php 2024-03-11 14:08:10.000000000 +0000
+++ wp-config.php 2024-12-01 13:38:39.852873921 +0000
@@ -20,16 +20,16 @@
// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
-define( 'DB_NAME', 'database_name_here' );
+define( 'DB_NAME', getenv('DB_NAME') ? getenv('DB_NAME') : 'mysite');
/** Database username */
-define( 'DB_USER', 'username_here' );
+define( 'DB_USER', getenv('DB_USER') ? getenv('DB_USER') : 'mysite_read' );
/** Database password */
-define( 'DB_PASSWORD', 'password_here' );
+define( 'DB_PASSWORD', getenv('DB_PASSWORD') ? getenv('DB_PASSWORD') : 'mysite');
/** Database hostname */
-define( 'DB_HOST', 'localhost' );
+define( 'DB_HOST', getenv('DB_HOST') ? getenv('DB_HOST') : 'localhost');
/** Database charset to use in creating database tables. */
define( 'DB_CHARSET', 'utf8' );
In mysql:
MariaDB [(none)]> grant all on mysite.* to mysite@'%' identified by 'mysite';
MariaDB [(none)]> grant select on mysite.* to mysite_read@'%' identified by 'mysite';
What this does above is set the DB based on the environment variables. The most important is DB_NAME, if you don't want to put the password in the environment then you can set it in wp-config.php like normal, just get the DB_USER from environment.
locationmatch
This works using LocationMatch
with ProxyBalancer
too, if that fits
your setup, some advantages of proxy pass to FPM allows you to run
Apache in a non-preforking model (doesn't have to fork()
for all
requests and set the process owner ID).
It does have to IPC with the FPM though, but this is likely a very minor overhead compared to serving every single (non-PHP too) request in a forked model.
RewriteCond %{QUERY_STRING} ^.*rest_route.*
RewriteCond %{REQUEST_METHOD} POST
RewriteRule ^/wordpress/index.ph.* balancer://writer [P]
<LocationMatch "^/wordpress/.*php">
ProxyPass "balancer://reader"
ProxyPassReverse "balancer://reader"
</LocationMatch>
<LocationMatch "^/wordpress/(wp-admin|wp-login).*php">
ProxyPass "balancer://writer"
ProxyPassReverse "balancer://writer"
</LocationMatch>
<Proxy "balancer://reader">
SetEnv DB_USER mysite_read
SetEnv DB_PASSWORD mysite
SetEnv DB_NAME mysite
SetEnv DB_HOST localhost
ProxyFCGISetEnvIf "true" SCRIPT_FILENAME "%{DOCUMENT_ROOT}/%{REQUEST_URI}"
BalancerMember "unix:/var/run/php/php8.2-fpm-dummy_read.sock|fcgi://localhost"
</Proxy>
<Proxy "balancer://writer">
SetEnv DB_USER mysite
SetEnv DB_PASSWORD mysite
SetEnv DB_NAME mysite
SetEnv DB_HOST localhost
ProxyFCGISetEnvIf "true" SCRIPT_FILENAME "%{DOCUMENT_ROOT}/%{REQUEST_URI}"
BalancerMember "unix:/var/run/php/php8.2-fpm-dummy.sock|fcgi://localhost"
</Proxy>
This might be more convenient if you don't like using
AssignUserIDExpr
.