写在前面的话
nginx 中主要的内容在前面的章节其实已经差不多了,接下都是一些小功能的实现以及关于 nginx 的优化问题。我们一起来探讨以下,如何把我们的 nginx 打造成为企业级应用。
安全优化:隐藏版本号和服务名称
我们在使用 curl 命令请求 nginx 的时候,甚至我们在访问出现 404 的时候,都会打印出我们的服务名称/版本号,如图:
WEB 访问:
这肯定是不好的,知道了版本号意味着黑客就能通过指定版本的漏洞对我们的服务器进行攻击,甚至知道了你是啥服务也能够针对性攻击,如果是小漏洞还好,如果是大漏洞就炸穿。
解决办法如下:
修改 /data/services/nginx/conf/fastcgi.conf:
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
该配置就是 curl 那里显示的 nginx/1.16.0,我们只需要把这个改成想要字符:
fastcgi_param SERVER_SOFTWARE apache;
并且在主配置文件中加入配置:
...
http {
server_tokens off;
...
}
...
比如我们把它改成 apache 后重载配置:
WEB 访问:
此时发现版本号确实被我们隐藏,但是服务器类型并没有因为我们改为 apache 而成功。
如果像修改服务器名称,则需要修改源码包中的:
1. 修改 /data/packages/nginx/nginx-1.16.0/src/core/nginx.h
2. 修改 /data/packages/nginx/nginx-1.16.0/src/http/ngx_http_header_filter_module.c
3.修改 /data/packages/nginx/nginx-1.16.0/src/http/ngx_http_special_response.c
由于我们并不是添加插件或者编译参数,所以回到 nginx 源码根目录直接编译:
# 编译 cd /data/packages/nginx/nginx-1.16.0 make # 备份 mv /data/services/nginx/sbin/nginx /data/backup/nginx/nginx_$(date +%F) # 更新 cp /data/packages/nginx/nginx-1.16.0/objs/nginx /data/services/nginx/sbin/ # 查看 /data/services/nginx/sbin/nginx -V
此时可以看到我们查了 nginx 的信息为:
重载配置,curl 测试:
访问错误页面:
可以看到我们第三步删除了默认网页中输出的那行代码,直接隐藏了服务器信息。这才是我们想要的。
安全优化:连接限制
大流量攻击一直就是互联网中比较常见的一种攻击,最为简单的就是黑客使用同一 IP 风控的对服务器发起请求或者传输文件导致服务器带宽被耗尽,进而影响正常用户的使用。
针对这样类似的攻击,nginx 是提供了 limit 模块用于对用户请求进行约束的。
也就是模块:http_limit_conn_module(默认编译)
参数 | 说明 |
---|---|
limit_conn_zone | 描述会话状态存储区域,只能在 http 段 配置段:http 语法:limit_conn_zone $variable zone=name:size; |
limit_zone | 和 limit_conn_zone 同等意思,已被弃用 |
limit_conn_log_level | 当达到最大限制连接数后,记录日志的等级 配置段:http, server, location 语法:limit_conn_log_level info | notice | warn | error |
limit_conn | 指定每个给定键值的最大同时连接数,当超过时返回 503 配置段:http, server, location 语法:limit_conn zone_name number |
limit_conn_status | 指定当超过限制时,返回的状态码,默认是 503 配置段:http, server, location 语法:limit_conn_status code; |
limit_rate | 对每个连接的速率限制。参数 rate 的单位是字节/秒,设置为 0 将关闭限速 配置段:http, server, location, if in location 语法:limit_rate rate 所谓的限制网速,其实质就是限制该 IP 每个线程的网速 |
另外需要补充几点:
1. 使用 limit_conn_zone 如:limit_conn_zone $binary_remote_addr zone=addr:10m;
说明:$binary_remote_addr 变量的固定长度是 4 个字节,我们定义了一个叫 addr 的空间,并给了这个空间 10M 的共享内存。如果是 32 位的平台上,1M 共享内存能够保存 3.2 万个 $binary_remote_addr 产生的存储状态,64 位则为 1.6 万。如果访问量过多导致超出了这个共享内存,新的访问则会被返回 503 错误。
2. 使用 limit_conn 来限制每个 IP 的并发,例如限制每个 IP 只能有一个活动连接:
limit_conn_zone $binary_remote_addr zone=client_addr:10m; server { location / { limit_conn client_addr 1; } }
3. 我们也可以限制限制单个 IP 的活动连接数的同时也限制单个虚拟主机的总连接数:
limit_conn_zone $binary_remote_addr zone=client_addr:10m; limit_conn_zone $server_name zone=server_addr:10m; server { location / { limit_conn client_addr 10; limit_conn server_addr 100; } }
比如这样允许每个 IP 开 10 个连接,总共支持 100 个连接。
说了这么多,我们来做做限制测试:limit-demo.conf
limit_conn_zone $binary_remote_addr zone=client_addr:10m; limit_conn_zone $server_name zone=server_addr:10m; server { listen 10003; server_name localhost; location ^~ /download { charset utf-8; alias /data/files/share; # 限制 limit_conn client_addr 2; limit_conn server_addr 2; limit_rate 200k; autoindex on; autoindex_exact_size on; autoindex_localtime on; if ($request_filename ~* ^.*?\.(txt|log|pdf|doc|docx|xls|xlsx|ppt|pptx|rar|zip|tar.gz|tar.xz|bz2|iso)$ ) { add_header Content-Disposition attachment; } } }
我们允许每个 IP 开两个连接,且服务器只支持 2 个连接,限速每个连接下载速率 200K。
访问测试:
点击两个下载:
可以看到限速到了 200K 每秒的下载速度!
此时已经占用了两个连接,我们刷新页面:
发现无法处理第三个请求,报错 503!
我们去其它服务器上面下载:
还是报错 503,因为我们只支持 2 个连接!你要是想使用只能等着别人处理完断开。
安全优化:请求限制
上文中的限制下载,请求数能够满足我们的一部分需求,但是某些时候我们还要一些特殊的需求,比如限制服务器每秒钟只能处理多少请求,请求的处理速率问题,这就需要另外一个限制模块。
一次性限制连接池终究还是太狠了点,我们换一种更加温柔的方式来进行限制,直接让请求者每秒钟只能请求处理这么多次。
模块:http_limit_req_module(默认编译)
参数 | 说明 |
---|---|
limit_req_zone | 设置一块共享内存限制域用来保存键值的状态参数。 配置段:http 语法:limit_req_zone $variable zone=name:size rate=rate; |
limit_req_log_level | 设置日志级别。 配置段:http, server, location 语法:limit_req_log_level info | notice | warn | error; |
limit_req_status | 设置拒绝请求的响应状态码,默认 503。 配置段:http, server, location 语法:limit_req_status code; |
limit_req | 设置对应的共享内存限制域和允许被处理的最大请求数阈值。 配置段:http, server, location 语法:limit_req zone=name [burst=number] [nodelay]; |
配置说明:
1. limit_req_zone 和前面的配置类似,定义共享内存空间,但是相对于 limit_conn_zone 多了处理速度,比如:
limit_req_zone $binary_remote_addr zone=req_client_addr:10m rate=1r/s;
在添加 rate,但是必须是整数,所以 2 秒处理一个只能写为 30r/m。
2. limit_req 的使用我们举个例子,限制每秒处理请求不超 1 个,且排队的不超过 5 个。
limit_req_zone $binary_remote_addr zone=req_client_addr:10m rate=1r/s; server { location / { limit_req zone=req_client_addr burst=5; } }
如果希望超过的请求不被延迟,可以在最后加 nodelay 参数:
limit_req zone=req_client_addr burst=5 nodelay;
我们做个测试验证一下:req-limit-demo.conf
limit_req_zone $binary_remote_addr zone=req_client_addr:10m rate=6r/m; server { listen 10004; server_name localhost; location ^~ /download { charset utf-8; alias /data/files/share; # 限制 limit_req zone=req_client_addr burst=2; fancyindex on; fancyindex_exact_size on; fancyindex_localtime on; if ($request_filename ~* ^.*?\.(txt|log|pdf|doc|docx|xls|xlsx|ppt|pptx|rar|zip|tar.gz|tar.xz|bz2|iso)$ ) { add_header Content-Disposition attachment; } } }
我们配置每个来源 IP 处理速率为 10 秒一个请求,访问测试:
连续刷新:
可以看到请求卡住了,需要等待几秒钟。准确其实是 10 秒。
放在 linux 上面测试:
while true; do curl -I http://192.168.100.111:10004/download/;done
请求结果:
可以看到两次请求成功的时间间隔 10 秒,这里就很准确了。
同时我们在这台机器上面再新开两个窗口允许同样的请求,再度运行这个命令会发现也会继续执行。
但是当我门总共开到第三个窗口运行这个命令的时候:
由于是死循环,所以疯狂的返回 503,这就是我们配置 burst 的作用。
我们加上 nodelay 参数,然后使用新命令访问:
while true; do curl -I http://192.168.100.111:10004/download/; sleep 1;done
我们每秒请求一次:
可以发现,请求立即返回,因为 burst 的原因,前 3 个请求都完全没问题,到第 4 个也就是第 4 秒的时候返回 503,后面几个 503 之后会重新 200,如此循环。
这就是 nodelay 的作用,直接返回,不去等待。
安全优化:访问控制
我们这里所说的访问控制其实就是来源的 IP,在 nginx 我们可以设置类似白名单这样的东西,只允许某些 IP 或者网段访问或者拒绝访问。
这其实是我们针对攻击者最狠的方式,直接禁止他的 IP 访问我们的服务。
同样,在内网中或者外网中,我们只希望某些人能够访问,也能进行 IP 限制来解决。
相对来说,这是一个非常非常实用的功能,依赖于:ngx_http_access_module 模块,默认安装
主要就两个参数:
参数 | 说明 |
---|---|
allow | 允许某个 ip 或者一个 ip 段访问,如果指定 unix: ,那将允许 socket 的访问。 配置段:http, server, location, limit_except 语法:allow address | CIDR | unix: | all; |
deny | 和 allow 相反。 配置段:http, server, location, limit_except 语法:deny address | CIDR | unix: | all; |
具体格式如下:
location / { deny 192.168.1.1; allow 192.168.1.0/24; allow 10.1.1.0/16; allow 2001:0db8::/32; deny all; }
测试新增配置:allow-deny-demo.conf
server { listen 10005; server_name localhost; default_type text/html; location / { allow 192.168.100.112; deny all; root /data/www/demo-80; index index.html index.htm; } }
使用 112 机器访问:
使用 113 访问:
安全优化:限制白名单
nginx 的白名单的实现依赖于:http_geo_module 和 http_map_module 模块,这两个模块都是默认安装的。
我们添加配置:whitelist-demo.conf
geo $remote_addr $grant { default 1; 127.0.0.1 0; 192.168.100.112 0; } server { listen 10007; server_name localhost; location ^~ /download { alias /data/files/share; charset utf-8; fancyindex on; fancyindex_exact_size off; fancyindex_localtime on; if ( $grant = 1 ){ return 403; } if ($request_filename ~* ^.*?\.(txt|log|pdf|doc|docx|xls|xlsx|ppt|pptx|rar|zip|tar.gz|tar.xz|bz2|iso)$ ) { add_header Content-Disposition attachment; } } }
说明:
1. 我们对来源的用户 IP 进行映射,如果为 1,则返回 403,则默认不在白名单的都为 1。
2. 在 location 中对来源的映射值进行判断,返回 403。
访问测试:
不在白名单的服务器:
访问测试配置完成!
修改配置:whitelist-demo.conf 实现限速白名单
geo $whiteiplist { default 1; 127.0.0.1 0; 192.168.100.112 0; } map $whiteiplist $limit { 1 $binary_remote_addr; 0 ""; } limit_conn_zone $limit zone=list_client_addr:10m; server { listen 10007; server_name localhost; location ^~ /download { alias /data/files/share; charset utf-8; fancyindex on; fancyindex_exact_size off; fancyindex_localtime on; limit_conn list_client_addr 1; if ( $limit != "" ) { limit_rate 200k; } if ($request_filename ~* ^.*?\.(txt|log|pdf|doc|docx|xls|xlsx|ppt|pptx|rar|zip|tar.gz|tar.xz|bz2|iso)$ ) { add_header Content-Disposition attachment; } } }
简单的说明:
1. 使用 geo 生成了一个 list,我们给 list 中的每项赋值为 0 或者 1,且默认设置为 1。
2. 通过 map 建立 0 和 1 的对应关系,0 代表 $binary_remote_addr,1 则为 ""。所以上面的 default 其实就是 $binary_remote_addr。
3. 我们做了 limit_conn 的限制,这次给的键的名称不是单独的某个变量,而是 map 定义的映射关系。
4. 整体配置的意思是:在 geo 定义的列表中,我们默认所有限速,如果不限速,将 IP 加入 geo 列表并赋值为 0。这就是白名单。
在很多资料中我们会看到:limit_conn_zone 和 limit_req_zone 指令对于键为空值的将会被忽略
但是在我这个版本中一直没有成功,所有我在外层多了个 if 判断。
访问测试白名单中的机器:
访问不在白名单的机器:
小结
这部分内容是进行 nginx 安全优化的重点,平时可能不起作用,但是生效都是大作用。