Nginx配置白名单

来自Linux78|wiki

geo指令是通过ngx_http_geo_module模块提供的。默认情况下,nginx安装时是会自动加载这个模块,除非安装时人为的手动添加--without-http_geo_module。

ngx_http_geo_module模块可以用来创建变量,其值依赖于客户端IP地址。

geo指令
语法: geo [$address] $variable { ... }
默认值: —
配置段: http

定义从指定的变量获取客户端的IP地址。默认情况下,nginx从$remote_addr变量取得客户端IP地址,但也可以从其他变量获得。

例如:

geo $remote_addr $geo {
default 0;
127.0.0.1 1;
}
geo $arg_cc_com $geo {
default 0;
127.0.0.1 1;
}

如果该变量的值不能代表一个合法的IP地址,那么nginx将使用地址"255.255.255.255"。

nginx通过CIDR或者地址段来描述地址,支持下面几个参数:

1)delete:删除指定的网络

2)default:如果客户端地址不能匹配任意一个定义的地址,nginx将使用此值。 如果使用CIDR,可以用"0.0.0.0/0"代替default。

3)include: 包含一个定义地址和值的文件,可以包含多个。

4)proxy:定义可信地址。 如果请求来自可信地址,nginx将使用其“X-Forwarded-For”头来获得地址。 相对于普通地址,可信地址是顺序检测的。

5)proxy_recursive:开启递归查找地址。 如果关闭递归查找,在客户端地址与某个可信地址匹配时,nginx将使用"X-Forwarded-For"中的最后一个地址来代替原始客户端地址。如果开启递归查找,在客户端地址与某个可信地址匹配时,nginx将使用"X-Forwarded-For"中最后一个与所有可信地址都不匹配的地址来代替原始客户端地址。

6)ranges:使用以地址段的形式定义地址,这个参数必须放在首位。为了加速装载地址库,地址应按升序定义。

geo $country {
default ZZ;
include conf/geo.conf;
delete 127.0.0.0/16;
proxy 192.168.100.0/24;
proxy 2001:0db8::/32;

127.0.0.0/24 US;
127.0.0.1/32 RU;
10.1.0.0/16 RU;
192.168.1.0/24 UK;
}
# vim conf/geo.conf  //编辑conf/geo.cong文件
10.2.0.0/16 RU;
192.168.2.0/24 RU;

地址段例子:

geo $country {
ranges;
default ZZ;
127.0.0.0-127.0.0.0 US;
127.0.0.1-127.0.0.1 RU;
127.0.0.1-127.0.0.255 US;
10.1.0.0-10.1.255.255 RU;
192.168.1.0-192.168.1.255 UK;
}

geo指令主要是根据IP来对变量进行赋值的。因此geo块下只能定义IP或网络段,否则会报错。 安装完成之后,GeoIP数据库会被安装在/usr/share/GeoIP/GeoIP.dat.

yum -y install geoip-devel
ls /usr/share/GeoIP/GeoIP.dat
/usr/share/GeoIP/GeoIP.dat

配置openresty支持Geoip

 vim /usr/local/nginx/conf/nginx.conf
....
http {
geoip_country /usr/share/GeoIP/GeoIP.dat;
map $geoip_country_code $allowed_country {
default yes;
US no; # 国家 no就是不允许哪个国际访问
}
....
server{
....
       if ($allowed_country = no) { # 添加判断,如果访问国家=no,就返回404
               return 404;
       }
...
}
http {
geo $remote_addr $geo {
          default 0; #0表示禁止访问
          127.0.0.1 1; #1表示可以访问
       }
...
 }

server {
     ...
     location / {
       # 如果不是白名单则 显示403 禁止访问
       if ( $geo  = 0 ) {
            return 403;
        }
...
}


nginx利用geo模块做限速白名单

nginx的限速白名单需要结合geo和map指令来实现,map指令使用ngx_http_map_module模块提供的。默认情况下,nginx安装时是会自动加载这个模块,除非安装时人为手动添加--without-http_map_module。

ngx_http_map_module模块可以创建变量,这些变量的值与另外的变量值相关联。允许分类或者同时映射多个值到多个不同值并储存到一个变量中,map指令用来创建变量,但是仅在变量被接受的时候执行视图映射操作,对于处理没有引用变量的请求时,这个模块并没有性能上的缺失。

geo $limit {
       default 1;
       10.0.0.0/8 0;
       192.168.0.0/24 0;
}
 
map $limit $limit_key {
       0 "";
       1 $binary_remote_addr;
}
 
limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
 
server {
       location / {
               limit_req zone=req_zone burst=10 nodelay;
               
               # ...
       }
}

这个例子同时使用了geo和map指令。对于IP地址在白名单中的,geo块分配0值给$limit;其它所有不在白名单中的IP地址,分配1值。然后我们使用一个map去将这些值映射到某个key中,例如:

如果$limit是0,$limit_key被设置为空字符串

如果$limit是1,$limit_key被设置为客户端的IP地址的二进制格式

这个两个结合起来,对于白名单中的IP地址,$limit_key被设置为空字符串;否则,被设置为客户端的IP地址。当limit_req_zone指令的第一个参数是一个空字符串,限制不起作用,因此白名单的IP地址(在10.0.0.0/8和192.168.0.0/24子网中)没有被限制。其它所有的IP地址都被限制为5个请求每秒。

limit_req指令将限制作用在/定位中,并且允许在没有转发延迟的情况下,转发多达10个数据包。

在一个定位中包含多个limit_req指令

可以在单个定位(location)中包含多个limit_req指令。匹配给定的请求限制都会被使用,这意味着采用最严格的限制。例如,如果多于一个的指令使用了延迟,最终使用最长的延迟。类似地,如果某个指令使得请求被拒绝,即使其它的指令允许请求通过,最终还是被拒绝。

可以在白名单中的IP地址上应用某个限流来扩展之前的例子:

http {
     # ...

     limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
     limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s;

     server {
           # ...
           location / {
                 limit_req zone=req_zone burst=10 nodelay;
                 limit_req zone=req_zone_wl burst=20 nodelay;
                 # ...            
           }
     }
}

在白名单上的IP地址不匹配第一个限流(req_zone),但是能匹配第二个(req_zone_wl),因此这些IP地址被限制为15个请求每秒。不在白名单上的IP地址两个限流都能匹配上,因此最严格的那个限流起作用:5个请求每秒。

配置相关的特性

日志(Logging)

默认,NGNIX记录由于限流导致的延迟或丢弃的请求的日志,如下面的例子:

2019/06/13 04:20:00 [error] 120315#0: *32086 limiting requests, 
excess: 1.000 by zone "mylimit", 
client: 192.168.1.2, 
server: nginx.com, 
request: "GET / HTTP/1.0", 
host: "nginx.com"

该日志记录包含的字段:

limiting requests— 日志条目记录了某个限流的标志
excess— 超过这个请求代表的配置的速率的每毫秒请求数目
zone— 定义了启用了限流的区域
client— 产生请求的客户端IP地址
server— 服务器的IP地址或主机名
request— 客户端产生的实际的HTTP请求
host— HTTP头部主机名的值

默认,NGINX日志在error级别拒绝请求,如上面例子中的[error]所示。(它在低一个级别上记录延迟的请求,因此默认是info。)用limit_req_log_level指令来改变日志级别。下面我们设置在warn级别上记录被拒绝的请求的日志:

location /login/ {
     limit_req zone=mylimit burst=20 nodelay;
     limit_req_log_level warn;

     proxy_pass http://my_upstream;
}

发送给客户端的错误码

默认,当某个客户端超过它的限流,NGINX用503(Service Temporarily Unavailable)状态码来响应。使用limit_req_status指令设置一个不同的状态码(在下面的例子是444):

location /login/ {
     limit_req zone=mylimit burst=20 nodelay;
     limit_req_status 444;
}

拒绝对特定位置的所有请求

如果你想拒绝对于某个特定URL的所有请求,而不是仅仅的限制它们,可以为这个URL配置一个location块,并且在其中包含deny all指令:

location /foo.php {
     deny all;
}

Nginx利用geo模块做负载均衡

本次测试的机器ip信息如下:

server1: 113.110.86.28
server2: 113.110.86.25
server3: 188.84.155.239
 
客户端1:113.110.86.23 
客户端2:113.110.86.51
客户端3:113.110.86.19

三台server机器上都部署了nginx环境,为了测试效果,特意配置了server1和server2的9090端口的首页,如下:

[ root@localhost ~]# curl http://113.110.86.28:9090

this is server1:113.110.86.28

[root@localhost ~]# curl http://113.110.86.25:9090
this is server2:113.110.86.25

配置server3,在server3上实现利用geo模块做负载均衡的目的,server3的nginx配置如下:

[root@localhost vhosts]# cat test.conf
geo $geo {
   default default;
   113.110.86.19/32   uk;
   113.110.86.51/32   us;
   }

#这里我是单网测试,所以掩码是32位;如果是vlan,可以是24位掩码,比如:
# 113.110.86.0/24   tw;

   upstream  uk.server {
     server 113.110.86.28:9090;
   } 

   upstream  us.server {
     server 113.110.86.25:9090;
   }

   upstream  default.server {
     server 188.84.155.239:9090;
   }

   server {
     listen    80;
     server_name 188.84.155.239;
     index index.html index.htm;
     root /var/www/html/80;
   
     location / {
     proxy_redirect off;
     proxy_set_header Host $host;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_pass http://$geo.server$request_uri;
     }
   } 
  
   server {
     listen    9090;
     server_name 188.84.155.239;

     location / {
     root  /var/www/html/9090;
     index index.html index.htm;
     }
   }


访问server3的9090端口

[root@localhost vhosts]# curl http://188.84.155.239:9090
this is server3:188.84.155.239

开始测试

1)在客户端1上访问http://188.84.155.239,如下:

[root@localhost ~]# curl http://188.84.155.239
this is server3:188.84.155.239

因为客户端1的IP地址为113.110.86.23,按照上面server3中nginx的配置,它访问的很明显是server3的9090端口!

2)在客户端2上访问http://188.84.155.239,如下:

[root@localhost ~]# curl http://188.84.155.239
this is server2:113.110.86.25

按照server3的nginx配置,客户端2访问server3的80端口就会被负载到server2的9090端口上!

3)在客户端3上访问http://188.84.155.239,如下:

[root@jenkins-server ~]# curl http://188.84.155.239
this is server1:113.110.86.28

按照server3的nginx配置,客户端3访问server3的80端口就会被负载到server1的9090端口上!