21

I am trying to restrict access to resources behind Nginx based on client IP passed in X-forwarded-for headers. Nginx is running in a container on a Kubernetes Cluster on Google Cloud Platform and real client ips are passed in x-forwarded-for header only

So far I've managed to do it for a single IP with the following code:

set $allow false;
if ($http_x_forwarded_for ~* 123.233.233.123) {
    set $allow true;
}
if ($http_x_forward_for ~* 10.20.30.40) {
    set $allow false;
}
if ($allow = false) {
    return 403;
}

But how can i do that for whole ranges of IPs? Specifying hundreds of IPs by hand doesn't make much sense.

All help is appreciated

3 Answers 3

20

Use the RealIP module to honour the value of the X-Forwarded-For header. Set set_real_ip_from to the IP address of the reverse proxy (the current value of $remote_addr).

For example:

server {
    ...
    real_ip_header X-Forwarded-For;
    set_real_ip_from 10.1.2.3;
    ...
}

You should now be able to use $remote_addr and allow/deny directives using the true IP address of the client. See this document for more.

6
  • so I tried the following to no avail, am I confusing it? location / { real_ip_header X-Forwarded-For; set_real_ip_from 10.0.0.0/8; real_ip_recursive on; allow xxx.xxx.xxx.xxx;
    – ptrh
    Jul 31, 2017 at 17:58
  • 1
    After looking at Google Load Balancing docs I found the following: X-Forwarded-For: <unverified IP(s)>, <immediate client IP>, <global forwarding rule external IP>, <proxies running in GCP> (requests only) The <immediate client IP> entry is the client that connected directly to the load balancer .
    – ptrh
    Jul 31, 2017 at 19:58
  • 1
    For this to work, you need to identify the address ranges for <global forwarding rule external IP> and <proxies running in GCP> and add set_real_ip_from statements covering all of them. Jul 31, 2017 at 20:08
  • <global forwarding rule external IP> is the external ip of my service, there are no other proxies in GCP, on my nginx logs I see requests in the following format [31/Jul/2017:20:05:46 +0000] "GET / HTTP/1.1" 403 169 "-" "curl/7.54.0" "aaa.aaa.aaa.aaa, bbb.bbb.bbb.bbb, ccc.ccc.ccc.ccc" where ccc.ccc.ccc.ccc is global forwarding rule, bbb.bbb.bbb.bbb a immediate client ip - matches what I see in whatsmyip.org. Any chance you can advise how to extract that part?
    – ptrh
    Jul 31, 2017 at 20:19
  • 1
    Ok, now I'm getting confused. You need to set_real_ip_from for all of the addresses to the right of the one you want to allow/deny. As indicated in the real_ip_recursive section. Jul 31, 2017 at 20:27
6

Richard's answer already contained the information on how to best get the real IP address to nginx.

Meanwhile, what comes to the question of specifying IP ranges, you can use http://nginx.org/en/docs/http/ngx_http_geo_module.html.

The geo module works like the map module, that is, a variable gets assigned values depending on the value of IP address.

An example:

geo $allow {
    default 0;
    192.168.168.0/24 1;
}

server {
    real_ip_header X-Forwarded-For;
    set_real_ip_from 10.1.2.3;

    if ($allow = 0) {
        return 403;
    }
}

Here we assign the geo map, where the default value for $allow is 0. If the IP address is in subnet 192.168.168.0/24, then $allow will get value 1, and the request is allowed.

You can have as many lines in the geo block as you need to define your IP ranges.

4
  • Thank you! that seems to work really well, last one thing I'm facing is that client_ip from X-forwarded-for. At the moment, from 3 ip addresses that are passed the last one is used. I have added real_ip_recursive on; below set_real_ip_from but it hasn't made any difference
    – ptrh
    Jul 31, 2017 at 19:52
  • Do you mean your X-Forwarded-For header has three separate addresses, that is, the request comes via multiple proxies? Do you have any other header there you can use, which contains only the client IP? Aug 1, 2017 at 7:18
  • Every proxy in the chain will append it's IP address to the X-Forwarded-For header. In addition to adding real_ip_recursive on you also need to add set_real_ip_from directives for each trusted server IP address in your proxy chain. Nginx will then work through each of these directives and return the client IP as the first value it hits in the X-Forwarded-For header which does not match any of your specified set_real_ip_from values
    – miknik
    Jun 17, 2018 at 17:40
  • FWIW, this combination did not work for me with AWS ALB. What did work was using the proxy directive inside the geo block, with the same ip as set_real_ip - nginx.org/en/docs/http/ngx_http_geo_module.html
    – talonx
    Sep 12, 2018 at 8:43
4

Got these working for me.

geo $remote_addr $giveaccess {
      proxy 172.0.0.0/8; <-- Private IP range here
      default 0;
      11.22.33.44 1; <-- Allowed IP here
    }


server{
##
    location ^~ /secure_url_here {
        if ($giveaccess = 0){
          return 403; 
        }
        try_files $uri $uri/ /index.php?$args; <-- Add this line specific for your CMS, if required.
    }

Ref: http://nginx.org/en/docs/http/ngx_http_geo_module.html

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .