modxcms / stackpath

StackPath Extra for MODX Revolution
1 stars 3 forks source link

Support for alternate Manager URLs #5

Open rthrash opened 6 years ago

rthrash commented 6 years ago

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:

image 2017-10-11 20-45-42

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.

stackpath weir settings 2017-10-11 21-01-19

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:

setcookie('SPB',$this->modx->getOption('scdn.auth_header_value', null, ''),null,"/","example.com");

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:

rthrash commented 6 years ago

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.