Nginx deny if location contains - nginx

How can I deny access to nginx if the path contains /local or /local-int to all networks except the local one?
For example https://example.com/api/local/settings. I tried this, but when accessed locally, the request goes to /etc/nginx/html/api/local/settings,and not to the desired backend
location = (local|local-int) {
allow 10.150.0.0/16;
allow 10.160.0.0/16;
allow 10.170.0.0/16;
deny all;
}
I have about 20 such sites, and I'm trying to come up with a solution that would not be tied to a specific location
I summarize: if I access a site from allowed ip, then it should show the page to which I am accessing, and if from a deny list, then 403
Config example:
server {
listen ip:80;
listen ip:443 ssl;
server_name test.com;
if_modified_since off;
location /api {
proxy_pass https://api.example.com;
}
location ~ (\/local) {
allow 10.150.0.0/16;
allow 10.160.0.0/16;
allow 10.170.0.0/16;
deny all;
}
}

This will simply work with both of your locations, since both starts with /local
location ~ (\/local) {
allow 10.150.0.0/16;
allow 10.160.0.0/16;
allow 10.170.0.0/16;
deny all;
}

Nginx takes a = location modifier as an exact match (docs are here). If you want to make a location that will catch every URI containing /local substring (obviously including /local-int), you can use a regex one:
location ~ /local {
...
}
The ^~ modifier makes the location block in #user973254 answer (original answer version, already fixed) a prefix one with the greater priority than any regex locations, so it will overtake only the URIs starting with /local (obviously not including /api/local/settings from your example).
However if your web backend requires an additional URI processing (which is a most common case nowadays), you'll need at least to replicate your main location behavior with this new location. Fortunately, there is a way to avoid such a problems, and can be easily applied to an arbitrary number of sites as you ask for in your original question. You can check required conditions to make a decision for blocking the request or not using the (very powerful) map block feature. And since we want to match address against a list of subnets, we will use a chain of map and geo blocks. To use regexes (PRCE/PCRE2 syntax) for a map block match use a ~ string prefix (~* for case-insensitive match), strings containing some special characters (e.g. curly braces) should be single- or double-qouted. Here is a generic example (you'll need only the first line of the following map block to fulfill your question requirements):
map $uri $restricted {
~/local 1; # regex to match any URI containing '/local' substring
~^/private/ 1; # regex to match any URI starting with '/private'
~*\.jpe?g$ 1; # regex to match any URI ending with '.jpg' or '.jpeg' (case-insensitive)
/some/protected/page/ 1; # exact URI match (string isn't starting with '~')
... any number of additional rules here
default 0;
}
geo $deny {
10.150.0.0/16 0;
10.160.0.0/16 0;
10.170.0.0/16 0;
default $restricted;
}
server {
...
if ($deny) { return 403; }
...
}
You can swap the logic to check the URI first (it can be some performance impact since the regex matching will be performed for every request including requests from the non-restricted networks, however if the majority of requests come from public addresses, there will be no significant difference). That way you can have a common non-restricted subnes list and per-site URI lists:
geo $restricted {
10.150.0.0/16 0;
10.160.0.0/16 0;
10.170.0.0/16 0;
default 1;
}
map $uri $deny1 {
~/local $restricted;
default 0;
}
map $uri $deny2 {
~^/admin $restricted;
default 0;
}
server {
server_name site1.com;
if ($deny1) { return 403; }
...
}
server {
server_name site2.com;
if ($deny2) { return 403; }
...
}
Of course, you are not limited to use 403 return code using this solution (which is the case when you are using allow/deny directives). It also has nothing to do with the famous "If is evil" article since this if is used in server context.

Related

Dynamic location block variable to string

I am trying to generate a single nginx location block that dymically changes depending on a captured matching location
location ~*\/(\w+)/archive {
fancyindex_footer "/fancyindex/footer-{$1}.html";
}
So if the location is /something/archive I want the string to say /fancyindex/footer-something.html
It's a simple request but I'm struggling to get it done or even being able to debug
Well, I've made some testing. Indeed, the fancyindex_footer directive can't interpolate variables from its parameter. Most likely the reason is that it is based on add_after_body directive from the ngx_http_addition_module, and that one can't do it too. However using a named capture group the following workaround should work:
location ~* /(?<idx>\w+)/archive {
# by using a named capture group, we make our prefix to be captured with the
# '$idx' variable rather than '$1' one, to be used later in the other location
add_after_body /fancyindex/;
# '/fancyindex/` string treated as a subrequest URI
}
location /fancyindex/ {
# don't allow external access, use only as internal location
internal;
# use the full path to the 'fancyindex' folder as a root here
root /full/path/to/fancyindex;
# rewrite any URI to the '/footer-$idx.html' and serve it as a footer file
rewrite ^ /footer-$idx.html break;
}
If I misunderstood your question and you just want to append a /fancyindex/footer-something.html string to your output instead of /fancyindex/footer-something.html file contents, change the internal location to
location /fancyindex/ {
internal;
return 200 "/fancyindex/footer-$idx.html";
}

nginx - Proxy to a different backend based on query param

I've got a case where I want to proxy a particular call down to a different backend based on the existence of a query param. The following is sort of what I start with
location ~ ^/abc/xyz/?$ {
proxy_pass $backend_url;
}
What I'd like to do is check for a query param foo (or even just the existence of that string anywhere). So I thought I could do this
location ~ ^/abc/xyz/?$ {
set $backend_url "somelocation"
if ($request_url ~ .*foo.*) {
set $backend_url "someotherlocation"
proxy_pass $backend_url
}
proxy_pass $backend_url;
}
But this doesn't seem to actually proxy to the new location. Am I doing something wrong with my code, or is the whole approach wrong?
I don't know why are you using two proxy_pass directives, this block should do it in a logic you described:
location ~ ^/abc/xyz/?$ {
set $backend_url "somelocation";
if ($request_url ~ \?(.*&)?foo(=|&|$)) {
set $backend_url "otherlocation";
}
proxy_pass $backend_url;
}
I slightly modified your regex to match only request URLs where foo is a query argument name and not a query argument value (or its part). However I'd rather use map directive for this purpose (map block should be placed outside the server block):
map $arg_foo $backend_url {
"" somelocation; # if the value is empty
default otherlocation; # otherwise
}
server {
...
location ~ ^/abc/xyz/?$ {
proxy_pass $backend_url;
}
}
Pay an attention you may need to define a resolver for this configuration to work (some additional info about this can be found here).

How to bypass nginx proxy a list of IPS

I have an Nginx config with the redirect to holding pages:
location / {
...
if ($setholdingpage = 'True') {
rewrite (^.*$) /holding-page last;
}
proxy_pass $backend;
}
Also, I have a list of IPs that should be whitelisted and not redirected to holding pages. How it's possible to do?
You can use the Nginx geo module to create a variable based upon client IP address, you can specify individual IP addresses or CIDR ranges:
geo $bypassip {
default 0;
64.233.160.0/19 1;
66.102.0.0/20 1;
}
Then override your variable if the IP matches one in your list:
if ($bypassip = 1){
set $setholdingpage False;
}
I use a similar setup to block certain geographic regions but still allow Google crawlers to access my site.
You can make use of the allow deny directives.
If I get you correct the whitelist will be your $setholdingpage variable in some sort?
try this
server {
error_page 403=#holding;
location / {
allow 127.0.0.1;
allow 192.168.1.0/24;
deny all;
proxy_pass http://backend;
}
location /#holding {
root /path/to/your/holding/html;
index holdingv1.html;
}
}
This will send the non-whitelisted IPs to the error-page you specified. The error_page can be in the location as well.
Not tested but this should do the trick.
References:
http://nginx.org/en/docs/http/ngx_http_access_module.html#allow

how to make nginx location match path both with slash and without slash?

currently i have this location in my nginx file, it does not work with http://mydomain/ab/cd. How can i make the browser to go to the same page when user type both http://mydomain/ab/cd and http://mydomain/ab/cd/?
location /ab/cd/ {
}
The fastest, in terms of performance, is simply two exact locations:
location = /ab/cd {
...
}
location = /ab/cd/ {
...
}
You can try
location ~* ^/ab/cd(|\/) {...}
It is a prefix matching regex that checks if it has trailing slash or not.

NGINX too long location (massive?)

NGINX
Have a locations like:
location ~ "^\/(place1|place2|...|place50)\/(service1|service2|...|service80)\-(else1|else2|...|else90)\/"
{...}
location ~ "^\/(word1|word2|...|word70)\/(place1|place2|...|place50)\-(else1|else2|...|else90)\/"
{...}
location ~ "^\/..."
The problem is there are a lot of places & services & words & else. So locations are awfully long strings. Is it possible to make them shorter? Maybe massive of places and massive of services and so on? Or something? Who has some experience?
examples of URI i want to match
/place23/service17-else87/
or
/world33/place42-else15/
and any combinations
For each location we are going to use set of rules. In order to use cache and unload our Apache
#proxy_cache start
set $do_not_cache 0;
# POST requests and urls with a query string should always go to PHP
if ($request_method = POST) {
set $do_not_cache 1;
}
if ($query_string != "") {
set $do_not_cache 1;
}
# Don't use the cache for logged in users or REBent commenters
if ($http_cookie ~* "wordpress_logged_in|bn_my_logged") {
set $do_not_cache 1;
}
if ($args ~* (show) ) {
set $do_not_cache 1;
}
ssi_types "*";
ssi on;
if ($do_not_cache = 0) {
set $memcached_key "SMREG|$request_uri";
memcached_pass memc_server;
ssi on;
}
You can use regular expressions in location blocks to make them match multiple things, rather than listing them all by hand.
e.g.
location ~* ^/world(\d{1,2})/place(\d{1,2})-else(\d{1,2})/ {
set $originalURI $uri;
fastcgi_param QUERY_STRING q=$originalURI&world=$1&place=$2&else=$3;
# or however you're passing it to your web server.
}
However as Mohammad AbuShady implied, what you're doing seems quite dumb.
You should be letting your application do the routing, and deciding whether things can be cached or not, and then using something that is actually designed to cache web pages e.g. Varnish, rather than trying to force application logic into Nginx.

Resources