When the StackPath WAF fully proxies a site, it needs a specific setting for the origin resolution, at least an IP and often a custom hostname, like origin.example.com that is different from the intended visitor URL like www.example.com. This is because if the visitor URL would otherwise point to the WAF for proxying, it would result in a redirect loop.
Keep 'em out the Manager
As such, a different approach is needed to ensure a WAF bypass header gets through to StackPath, and set on the various (sub)domains as follows. All of the following URLs should pointed to StackPath's WAF:
Public visitors: www.example.com
Manager: mgr.example.com
Origin: origin.example.com < this is what to use for the Origin Resolution
You also need an NGINX Web Rule on the Origin Server to block the Manager on anything but mgr.example.com … you can throw an NGINX 404, or a MODX 301 masquerading as a 404 page:
set $mgrcheck $uri$host;
if ($mgrcheck ~ "^\/manager(?!.*mgr.example.com)") {
return 301 https://www.example.com/page-not-found;
}
Based on that rule, anything that doesn't come from mgr.example.com to the manager will get bounced to the MODX "404 page" (assuming an alias of "page-not-found").
But you need to sneak past the WAF, right?
Next, you need to deal with bypassing the WAF when working in the Manager. A variety of things can trigger the WAF, from VersionX connectors, to editing Templates, to Chunks with specific code in them attempting to be saved. Currently a Manager bypass is achieved by having the Manger plugin set a custom header (X-SP-Auth) when someone has a valid Manager session, and StackPath checking for the right value to allow all requests if that's present:
Being able to edit content is a good thing…
However … the custom header from the Manager URL won't exist on the Visitor or Origin URLs, because they weren't set from there and the session doesn't exist as far as the WAF is concerned, beacuase, required loop-preventing Origin setting.
A modest proposal
The solution can be to whitelist Manager logins using a domain-wide cookie and an additional NGINX web rule. The bonus is that this can eliminate the per-request run of a MODX plugin on the front-end, saving it for Manager-only use. Here's a way to accomplish that:
From stackpath/model/stackpath.class.php around line 117, change the custom header code to set a custom cookie, instead of directly setting a custom header. Note that the custom header is critical to this working, but won't work across subdomains when using the Manager. By using a cookie, you can have NGINX set the header based on the cookie value when logged into the Manager:
Note the null parameter in the third option of the setcookie call might be better served by something that sets the expiry explicity, vs as it is now with a "when the browser closes, kill the cookie" setting. Chrome tends to be a bit foofy on this front though:
# 60 seconds x 5 minutes from now
time()+60*5
By switching to a cookie that's accessible to all subdomains on the URL, you can then use an NGINX web rule to read the cookie if it's present in a location block, and then set the custom header. NGINX is much faster at tasks like this compared to PHP:
# part of WAF bypass with valid Manager Session
set $sph 'X-Answer';
set $spv '42';
if ($http_cookie ~* 'SPB') {
set $sph 'X-SP-Auth';
set $spv $cookie_SPB;
}
location / {
add_header Access-Control-Allow-Origin *;
add_header $sph $spv;
try_files $uri $uri/ @modx-rewrite;
}
But wait, there's likely more!
Things to check:
PHP location is not getting the headers, beacuase this doesn't affect the PHP block
Logging out of a Manager session kills/unsets/destroys the cookie
Cookie expiry actually works … Chrome tends to be very sticky/cache-y
Side note, this should allow us to revert to the onManagerInit event only, too, which means the plugin won't fire on front-end requests, saving a teeny tiny bit of resources.
When the StackPath WAF fully proxies a site, it needs a specific setting for the origin resolution, at least an IP and often a custom hostname, like
origin.example.com
that is different from the intended visitor URL likewww.example.com
. This is because if the visitor URL would otherwise point to the WAF for proxying, it would result in a redirect loop.Keep 'em out the Manager
As such, a different approach is needed to ensure a WAF bypass header gets through to StackPath, and set on the various (sub)domains as follows. All of the following URLs should pointed to StackPath's WAF:
Public visitors:
www.example.com
Manager:mgr.example.com
Origin:origin.example.com
< this is what to use for the Origin ResolutionYou also need an NGINX Web Rule on the Origin Server to block the Manager on anything but
mgr.example.com
… you can throw an NGINX 404, or a MODX 301 masquerading as a 404 page:Based on that rule, anything that doesn't come from
mgr.example.com
to the manager will get bounced to the MODX "404 page" (assuming an alias of "page-not-found").But you need to sneak past the WAF, right?
Next, you need to deal with bypassing the WAF when working in the Manager. A variety of things can trigger the WAF, from VersionX connectors, to editing Templates, to Chunks with specific code in them attempting to be saved. Currently a Manager bypass is achieved by having the Manger plugin set a custom header (
X-SP-Auth
) when someone has a valid Manager session, and StackPath checking for the right value toallow
all requests if that's present:Being able to edit content is a good thing…
However … the custom header from the Manager URL won't exist on the Visitor or Origin URLs, because they weren't set from there and the session doesn't exist as far as the WAF is concerned, beacuase, required loop-preventing Origin setting.
A modest proposal
The solution can be to whitelist Manager logins using a domain-wide cookie and an additional NGINX web rule. The bonus is that this can eliminate the per-request run of a MODX plugin on the front-end, saving it for Manager-only use. Here's a way to accomplish that:
From
stackpath/model/stackpath.class.php
around line 117, change the custom header code to set a custom cookie, instead of directly setting a custom header. Note that the custom header is critical to this working, but won't work across subdomains when using the Manager. By using a cookie, you can have NGINX set the header based on the cookie value when logged into the Manager:Note the
null
parameter in the third option of the setcookie call might be better served by something that sets the expiry explicity, vs as it is now with a "when the browser closes, kill the cookie" setting. Chrome tends to be a bit foofy on this front though:By switching to a cookie that's accessible to all subdomains on the URL, you can then use an NGINX web rule to read the cookie if it's present in a location block, and then set the custom header. NGINX is much faster at tasks like this compared to PHP:
But wait, there's likely more!
Things to check: