0

Our server uses PHP as well as MivaScript (.php and .mvc), both of which are configured in nginx using fastcgi.

Meanwhile, there are some files / directories that we want locked down by IP address. I've learned that nginx will only process one matching location block per request, and that seems to force me to duplicate some statements, which I'd prefer to avoid.

For example I started with the following, telling the server what to do with .php and .mvc files:

location ~ \.php$ {
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include        fastcgi_params;
    fastcgi_pass   unix:/var/run/php-fpm.sock;
}

location ~ \.mvc$ {
    fastcgi_read_timeout    300;
    fastcgi_pass            www.example.com:1234;
    fastcgi_param           MvCONFIG_LIBRARY /var/www/sites/www.example.com/cgi-bin/libmivaconfig.so;
    include                 fastcgi_params;
}

OK great, now the server knows what to do with those files. Next I want to tell the server to deny access to some files:

 location ~ ^(\/mm5\/admin.mvc|\/MyProtected\/PHPScripts\/) {
    deny all;
}

This works just fine, I get 403 errors when trying to visit these pages.

But now if I try to allow my IP address - the server "forgets" what to do with the .mvc file (I haven't gotten to test yet, but I imagine .php files will also be forgotten). Instead of displaying the web page, it prompts me to download the file.

location ~ ^(\/mm5\/admin.mvc|\/MyProtected\/PHPScripts\/) {
    allow 1.2.3.4;
    deny all;
}

I was hoping I could do some nesting, telling the server what to do with .mvc files and then denying some of them, but it still prompts to download:

location ~ \.mvc$ {
    fastcgi_read_timeout    300;
    fastcgi_pass            www.example.com:1234;
    fastcgi_param           MvCONFIG_LIBRARY /var/www/sites/www.example.com/cgi-bin/libmivaconfig.so;
    include                 fastcgi_params;

    location ~ ^(\/mm5\/admin.mvc) {
        allow 1.2.3.4;
        deny all;
    }
}

Even if the above attempt worked, I would then have to duplicate the list of IP addresses both under the .mvc block as well as under the .php block.

So the only thing I can get working, which requires lots of duplication, is this ridiculous mess:

# restrict access to this .mvc file, and tell the server how to
# handle it for anyone who is permitted
# remember to copy your changes everywhere you need to!
location ~ ^(\/mm5\/admin.mvc) {
    allow 1.2.3.4;
    deny all;

    fastcgi_read_timeout    300;
    fastcgi_pass            www.example.com:1234;
    fastcgi_param           MvCONFIG_LIBRARY /var/www/sites/www.example.com/cgi-bin/libmivaconfig.so;
    include                 fastcgi_params;
}

# tell the server how to handle the rest of the world's .mvc pages
# remember to copy your changes everywhere you need to!
location ~ \.mvc$ {
    fastcgi_read_timeout    300;
    fastcgi_pass            www.example.com:1234;
    fastcgi_param           MvCONFIG_LIBRARY /var/www/sites/www.example.com/cgi-bin/libmivaconfig.so;
    include                 fastcgi_params;
}

# restrict access to this directory of PHP scripts, and tell the server how to
# handle them for anyone who is permitted
# remember to copy your changes everywhere you need to!
location ~ ^(\/MyProtected\/PHPScripts\/) {
    allow 1.2.3.4;
    deny all;

    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include        fastcgi_params;
    fastcgi_pass   unix:/var/run/php-fpm.sock;
}

# tell the server how to handle the rest of the world's PHP scripts
# remember to copy your changes everywhere you need to!
location ~ \.php$ {
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include        fastcgi_params;
    fastcgi_pass   unix:/var/run/php-fpm.sock;
}

There must be a better way! At the very least, can I somehow set up a few include files, with the permitted IP list and the PHP/MVC configs, and just include them in the 999 places I seem to need to duplicate them?

Or better yet, hopefully I'm over-complicating this and there's an unbelievably simple, unbelievably obvious solution that I missed?

  • 2
    That's what includes are for. – Michael Hampton Jul 11 '18 at 16:46
  • Yea I just found some info about includes and cleaned up the file a bit. It looks a lot better – Mike Willis Jul 11 '18 at 18:09
0

There's another way you can do this using a couple of map directives. The map directive allows you to set the value of a custom variable based upon the value of another variable. These directives live in the http block but outside server blocks

So you start out with one to reference your restricted access files, adjust your regex to catch them all, no need to split the php and mvc into separate maps:

map $request_uri $fastcgi_restricted {
~ ^(\/regex\/pattern\/) 1;
}

Now if the request uri matches the regex then the custom variable $fastcgi_restricted will contain the value 1. Map directives support setting a default value in case none of your specified values match, but it's optional and for this you don't need it.

Now below that you add another map using the client IP as the variable to check against (if you had a long IP access list then a geo directive would be better than a map, but for a few then map is ok):

map $remote_addr $fastcgi_access {
default $fastcgi_restricted;
1.2.3.4 0;
}

Now your second custom variable $fastcgi_access will be empty if the client request uri does not match your regex above, it will be 0 if it does match the regex above but the client IP is 1.2.3.4 and it will be 1 if it matches the regex and client IP is anything else. So basically, if it's set to 1 we don't want to serve the request.

Nginx considers an empty variable or a variable with a value of 0 to be false. so within your server block, at the top and outside all of the location blocks you add:

if ($fastcgi_access) {
    return 403;
}

At this point you get the obligatory response from someone with a rudimentary understanding of Nginx that "if is evil" and the world will end if you dare to include it in your config anywhere, usually with a link to this page.

You can ignore those people as they obviously haven't read the page they are linking to. The important points to note being:

Directive if has problems when used in location context

and

In some cases, it’s also possible to move ifs to server level (where it’s safe as only other rewrite module directives are allowed within it).

Which is exactly what I have outlined here, this is roughly equivalent to how the geoip module is used to restrict access based upon geographic location. Now you should be able to eliminate the additional php and mvc blocks from your config as requests for the restricted files will be rejected before they make it that far.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.