After nginx proxy, get the real IP and do concurrent access restriction (current limiting)

When the site is running, in order to prevent DDoS attacks or data bursts caused by internal interface calls, nginx provides a limit current limiting module:

HttpLimitZoneModule limits the number of concurrent accesses at the same time. HttpLimitReqModule limits access to data, up to a few requests per second.

1. Normal configuration:

What is a normal configuration?

The normal configuration is the nginx configuration for the regular mode of [User Browser] → [Web Server]. So, if I want to restrict access to a single IP, most tutorials are written like this:

## The user's IP address $binary_remote_addr is used as the Key. Each IP address has a maximum of 50 concurrent connections.
## Do you want to open thousands of connections and kill me? If there are more than 50 connections, a 503 error will be returned directly to you and your request will not be processed at all.
limit_conn_zone $binary_remote_addr zone=TotalConnLimitZone:10m;
limit_conn TotalConnLimitZone 50;
limit_conn_log_level notice;
## The user's IP address $binary_remote_addr is used as the Key. Each IP address handles 10 requests per second.
## If you want to use the program to brush me hundreds of times per second, there is no way. No matter how fast it is, it will not be processed and a 503 error will be returned to you directly.
limit_req_zone $binary_remote_addr zone=ConnLimitZone:10m rate=10r/s;
limit_req_log_level notice;
## Specific server configuration
server {<!-- -->
    listen 80;
    location ~ .php$ {<!-- -->
                ## Up to 5 queues. Since 10 requests + 5 queues are processed per second, you can send up to 15 requests per second. If more requests are sent, a 503 error will be returned to you.
        limit_req zone=ConnLimitZone burst=5 nodelay;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

This completes the simplest server security access restriction. Basically, 90% of the websites you can search on Google are this example, and it is also emphasized that you can use "$binary_remote_addr" Save memory and so on.

2. After CDN or SLB proxy

In order to increase security and performance, many sites use CDN acceleration or other secondary proxies, such as Alibaba’s SLB load balancing and so on.

Therefore, the access mode of the website becomes: user browser → CDN node/SLB node → website source server

Even more complex modes: User browser → CDN/SLB node (CDN entrance, CCDDoS attack traffic cleaning, etc.) → Alibaba Cloud Shield → Source server

As you can see, our website has gone through several layers of transparent acceleration and security filtering. In this case, we cannot use the “normal configuration” above. Because the result based on [source IP restriction] in ordinary configuration is that we restrict [CDN/SLB node] or [Alibaba Cloud Shield], because the “source IP” address here is no longer the real user’s IP, but The IP address of the intermediate CDN/SLB node.

What we need to restrict is the real users at the front, not the acceleration server in the middle that accelerates for us.

In fact, when a CDN/SLB or transparent proxy server transfers the user’s request to a later server, the CDN/SLB server will add a record to the HTTP header.

X-Forwarded-For: User IP, proxy server IP

If more than one proxy server is experienced in the middle, the record will be like this

X-Forwarded-For : User IP, Proxy 1-IP, Proxy 2-IP, Proxy 3-IP, ….

It can be seen that after many layers of proxies, the user’s real IP is in the first position, followed by a series of IP addresses of intermediate proxy servers. From here, the user’s real IP address is obtained, and restrictions can be placed on this IP address. .

Then the access restriction configuration in CDN/SLB mode should be written like this:

http{<!-- -->
    ## Get the IP address of the original user here. If you don’t use CDN/SLB, give it to $remote_addr.
    map $http_x_forwarded_for $clientRealIp {<!-- -->
        default $remote_addr;
        ~^(?P<firstAddr>[0-9.] + ),?.*$ $firstAddr;
    }
    #Set IP whitelist, no restrictions on internal IPs
    map $clientRealIp $limit{<!-- -->
        default $clientRealIp;
        xx.xx.xx.xx "";
    }
    #Use real IP as the unit, limit the number of requests, and return 429 status;
    limit_req_status 429;
    limit_req_zone $limit zone=ConnLimitZone:20m rate=5r/s;
    limit_req_log_level notice;
    #Use the real IP as the unit, limit the number of concurrent connections of the IP, and return the 429 status;
    limit_conn_status 429;
    limit_conn_zone $limit zone=TotalConnLimitZone:20m;
    limit_conn TotalConnLimitZone 100;
    limit_conn_log_level notice;
    #Limit the total number of concurrent links based on the access domain name;
    limit_conn_zone $server_name zone=SumConnLimitZone:20m;
}
## Specific Server: Just add restriction rules in the monitoring part of php/go/java as follows, or place them directly under the domain name to restrict all access.
server {<!-- -->
    listen 80;
    location ~ .php$ {<!-- -->
        #Limit the total number of concurrent connections
        limit_conn SumConnLimitZone 10000;
        #Up to 5 queues. Since 10 requests + 5 queues are processed per second, you can send up to 15 requests per second. If there are more, a 429 error will be returned to you.
        limit_req zone=ConnLimitZone burst=5 nodelay;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

3. How to verify

Based on the above configuration, we know that nginx allows up to 10 + 5 requests per second. During the stress test, there will be two situations:

In the whitelist (test the server in the whitelist), stress test the site and it should all pass.
Not in the whitelist, only 10 + 5 requests are allowed at most, and 429 should be returned for the remaining ones.
Prerequisite: The total number of stress tests exceeds 10 + 5, otherwise no effect will be seen.

Centos generally comes with the siege stress testing tool, which is relatively easy to use: yum -y install siege

Usage: siege -c 3 -r 10 -b
https://xxxx.xx.com/api/xxxx-c 3 means 3 users -r 10 means access more than 10 times means: 3 users, each user accesses 10 requests, total 30 times

After testing, for each additional nginx, the same configuration should be x 2. For example: the configuration of nginx1 is 10 + 5, and the configuration of nginx2 is also 10 + 5. The domain name is deployed on these two nginx. The maximum number of requests allowed is: 20+10

If the above test fails, it should be a configuration problem, then we use the echo module to debug:

4. echo module

The author mentioned an echo module of nginx in the original article. I played with it and found it quite interesting. Here are the simple integration steps.
①Integrate the echo module into nginx

cd /usr/local/src
#Download the echo module and unzip it:
wget https://github.com/openresty/echo-nginx-module/archive/v0.61.tar.gz
tar zxvf v0.61.tar.gz
#Download nginx and unzip it
wget http://nginx.org/download/nginx-1.12.2.tar.gz
tar -xzvf nginx-1.12.2.tar.gz
cd nginx-1.12.2/
#View the compilation parameters of nginx in use (omit if it is a new installation)
/usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.12.2
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC) #The following line is the old compilation parameter:
configure arguments: --user=www --group=www --prefix=/usr/local/nginx --with-http_gzip_static_module
#Add the [--add-module=/echo module decompression path] parameter based on the old compilation parameters and start compilation
./configure --prefix=/usr/local/nginx/nginx --add-module=/usr/local/src/echo-nginx-module-0.61
#makecompile
make -j2
#Smoothly upgrade nginx (if it is a new installation, please execute: make install)
mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old
cp -f objs/nginx /usr/local/nginx/sbin/
 
make upgrade

Friends who are not familiar with the above upgrade, compilation and addition of third-party modules can refer to my other blog:
https://mp.csdn.net/mdeditor/81136273

②. Echo usage example: In fact, it is similar to the shell’s echo. It can output customized information.

For example, the configuration in nginx is as follows:

location /hello {<!-- -->
  echo "hello, world!";
}

When you visit http://xxx.com/hello, hello, world! will be output in the browser (if the domain name is enabled with CDN, it may report 404).

For another example, to test the real user’s IP mentioned in this article, just add the following rules and reload based on the configuration in the second step of this article:

server {<!-- -->
    listen 80;
        server_name yourdomain.com;
        ## The following are the new rules:
        ## When the user accesses /ip, we output the $clientRealIp and $limit variables and take a look at this variable
        ## Is the value true? User source IP address
        location /ip{<!-- -->
                echo $clientRealIp;
                echo $limit;
        }
}

Friends who read carefully will ask what is the difference between clientRealIp and limit: clientRealIp If you use SLB/CDN, you will get the real IP. Otherwise, you will get remote_addr

limit is based on clientRealIp and excludes the “IP whitelist”. That is to say, when your source IP is in the whitelist, your limit should be “empty”. This way there will be no current restriction

Therefore, we can use this to judge whether the IP whitelist is valid: curl http://xxx.xxx.cn/ip