nginx控制iptables芝麻开门
During maintenance of a production server, we use to encounter a problem like this – an internet-access-blocked port, say 3306 (mysql), is about to be open for a little while due to debugging needs, which is not uncommon, right? So how to make this easily operated, mostly secure, and automatically disabled after working – I guess this’s gonna help somehow.
A Linux server with whatever the distribution including iptables (e.g. CentOS, Rocky for now as example), Nginx, fcgiwrap, a simple shell script and a commonly used web browser, let’s say chrome, will be major ingredients on the recipe.
常做运维,总会遇到某些线上服务器,为了便于调试,在符合安全要求的基础上,需要相对便利的对公网短时定向开放某些端口。
昨天想了一招,用nginx操作shell脚本,对特定公网IP开口子,叫一声“芝麻开门”,便捷又安全。举例端口3306(mysql),系统是Rocky Linux 8(原CentOS 8)。
先查一下现有的防火墙规则,需要的话可以保存一下。换句话说,搞芝麻开门之前,先确定门是隐藏的;也即已经有相应(且持久化)的iptables rule确保到被保护端口的连接是DROP/REJECT状态。
1 2 3 4 5 6 7 8 9 10 11 |
[root@localhost ~]# iptables -nvL Chain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 48772 9041K ACCEPT tcp -- * * 10.0.0.0/8 0.0.0.0/0 tcp 6 376 DROP tcp -- eth0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination |
然后就是fcgiwrap的安装,此处就不写nginx安装过程了,默认安装即可。
安装后增加两个systemd服务文件,并测试fcgiwrap服务生效。
【注】那俩服务文件,在Redhat之外的发行版上,fcgiwrap安装包可能自带,请针对所用版本自行检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
[root@localhost ~]# dnf install fcgiwrap 上次元数据过期检查:2:45:15 前,执行于 2022年02月09日 星期三 20时00分42秒。 依赖关系解决。 (……部分省略) 已安装: fcgi-2.4.0-36.el8.x86_64 fcgiwrap-1.1.0-12.20181108git99c942c.el8.x86_64 完毕! [root@localhost ~]# [root@localhost ~]# cat /usr/lib/systemd/system/fcgiwrap.service [Unit] Description=Simple CGI Server After=nss-user-lookup.target Requires=fcgiwrap.socket [Service] EnvironmentFile=/etc/sysconfig/fcgiwrap ExecStart=/usr/sbin/fcgiwrap ${DAEMON_OPTS} -c ${DAEMON_PROCS} User=nginx Group=nginx [Install] Also=fcgiwrap.socket [root@localhost ~]# cat /usr/lib/systemd/system/fcgiwrap.socket [Unit] Description=fcgiwrap Socket [Socket] ListenStream=/run/fcgiwrap.socket [Install] WantedBy=sockets.target [root@localhost ~]# systemctl daemon-reload [root@localhost ~]# systemctl enable fcgiwrap.service —now Created symlink /etc/systemd/system/sockets.target.wants/fcgiwrap.socket → /usr/lib/systemd/system/fcgiwrap.socket. [root@localhost ~]# systemctl status fcgiwrap.service ● fcgiwrap.service - Simple CGI Server Loaded: loaded (/usr/lib/systemd/system/fcgiwrap.service; indirect; vendor preset: disable> Active: active (running) since Wed 2022-02-09 22:54:17 CST; 4s ago Main PID: 814486 (fcgiwrap) Tasks: 1 (limit: 23712) Memory: 492.0K CGroup: /system.slice/fcgiwrap.service └─814486 /usr/sbin/fcgiwrap -f -c 1 2月 09 22:54:17 localhost.localdomain systemd[1]: Started Simple CGI Server. [root@localhost ~]# |
接下来为一个新的nginx server准备个根目录,存放网页和之后控制iptables的脚本。
1 2 3 4 5 6 7 8 |
[root@localhost ~]# mkdir -p /var/www/secret_door [root@localhost ~]# cd /var/www/secret_door/ [root@localhost secret_door]# cp /usr/share/nginx/html/* ./ [root@localhost secret_door]# ll 总用量 8 -rw-r--r-- 1 root root 494 2月 9 23:03 50x.html -rw-r--r-- 1 root root 825 2月 9 23:03 index.html [root@localhost secret_door]# |
在nginx上单开一个server,指向fcgiwrap和之前设定的根目录。重启nginx服务前,检查新增conf文件无错误。
然后编写由nginx进行调用的shell脚本,注意脚本返回内容尽量按html的基本规范输出,且脚本需要+x执行权限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
[root@localhost secret_door]# cat /etc/nginx/conf.d/secret_door.conf server { listen 6080; listen [::]:6080; server_name secret_door; root /var/www/$server_name; location ~ (\.sh)$ { gzip off; root /var/www/$server_name; autoindex on; fastcgi_pass unix:/var/run/fcgiwrap.socket; include /etc/nginx/fastcgi_params; fastcgi_param DOCUMENT_ROOT /var/www/$server_name; fastcgi_param SCRIPT_FILENAME /var/www/$server_name$fastcgi_script_name; } location / { } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } [root@localhost secret_door]# nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful [root@localhost secret_door]# cat web-key.sh #!/bin/sh # -*- coding: utf-8 -*- NAME="secret door" echo -e "X-Test-Author: Fisherworks.cn\r\n" echo "<html><head>" echo "<title>$NAME</title>" echo '<meta name="description" content="'$NAME'">' echo '<meta name="keywords" content="'$NAME'">' echo '<meta http-equiv="Content-type" content="text/html; charset=UTF-8">' echo '<meta name="ROBOTS" content="noindex">' echo "</head><body><pre>" echo -e "`date -R`\n" echo -e "uname -a:" uname -a echo -e "\nUSER:" whoami # 找到自己的公网IP,并展示确认 echo -e "\nYOUR PUB ADDR:" echo $REMOTE_ADDR # 判断是否已经开门,如果没有则立即开门,如果已开,则不再重开 sudo /sbin/iptables -C INPUT -p tcp -s $REMOTE_ADDR --dport 3306 -j ACCEPT if [ $? != 0 ]; then sudo /sbin/iptables -I INPUT -p tcp -s $REMOTE_ADDR --dport 3306 -j ACCEPT echo -e "\nYOU SHALL PASS NOW" else echo -e "\nYOU ARE ALREADY IN" fi echo -e "\nIPTABLES RULES:" sudo /sbin/iptables -nvL echo "</pre></body></html>" [root@localhost secret_door]# chmod +x ./web-key.sh [root@localhost secret_door]# ll 总用量 12 -rw-r--r-- 1 root root 494 2月 9 23:03 50x.html -rw-r--r-- 1 root root 825 2月 9 23:03 index.html -rwxr-xr-x 1 root root 840 2月 9 23:14 web-key.sh [root@localhost secret_door]# |
最后一步,给nginx用户增加免密sudo iptables的能力。
此处建议尽量使用visudo而非手改sudo config文件,这样不容易杯具。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[root@localhost secret_door]# visudo ...部分省略 ## Next comes the main part: which users can run what software on ## which machines (the sudoers file can be shared between multiple ## systems). ## Syntax: ## ## user MACHINE=COMMANDS ## ## The COMMANDS section may have other options added to it. ## ## Allow root to run any commands anywhere root ALL=(ALL) ALL ...部分省略 nginx ALL=(root) NOPASSWD: /sbin/iptables |
搞定,打开浏览器输入URL,尝试开门,显示开门成功,待进门测试。
进门测试,使用nmap,开门前返回filtered(防火墙拦截),开门后显示open,验证成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
➤ ~ nmap -sT -p 3306 test.example.com Starting Nmap 7.92 ( https://nmap.org ) at 2022-02-09 23:23 CST Nmap scan report for test.example.com (1.2.3.4) Host is up (0.0074s latency). PORT STATE SERVICE 3306/tcp filtered mysql Nmap done: 1 IP address (1 host up) scanned in 0.34 seconds ➤ ~ nmap -sT -p 3306 test.example.com Starting Nmap 7.92 ( https://nmap.org ) at 2022-02-09 23:24 CST Nmap scan report for test.example.com (1.2.3.4) Host is up (0.0063s latency). PORT STATE SERVICE 3306/tcp open mysql Nmap done: 1 IP address (1 host up) scanned in 0.14 seconds ➤ ~ |
至此问题解决,针对单个公网IP开放的端口,安全风险基本已经很低了。
如果觉得这钥匙不够安全,怕开门URL被猜出/试出,可以优化nginx config文件,加上basic auth,或者走header等其他auth手段,保障钥匙有且仅有自己可用。那部分比较简单,本文不再详述。
如果还想做个定期自动关门
在未开门之前,先对iptables安装持久化所用的包,保存现有防火墙规则,并测试服务生效。
【注】Redhat系列是iptables-services,而Debian/Ubuntu系列,按版本新旧不同,可能是iptables-persistent或者netfilter-persistent,请自行查阅文档。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
[root@localhost ~]# dnf install iptables-services 上次元数据过期检查:2:39:47 前,执行于 2022年02月09日 星期三 20时00分42秒。 依赖关系解决。 (……部分省略) 已安装: iptables-services-1.8.4-20.el8.x86_64 完毕! [root@localhost ~]# service iptables save iptables: Saving firewall rules to /etc/sysconfig/iptables:[ OK ] [root@localhost ~]# cat /etc/sysconfig/iptables # Generated by iptables-save v1.8.4 on Wed Feb 9 22:41:55 2022 *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -s 10.0.0.0/8 -p tcp -m tcp -j ACCEPT -A INPUT -i eth0 -p tcp -m tcp --dport 3306 -j DROP COMMIT # Completed on Wed Feb 9 22:41:55 2022 [root@localhost ~]# systemctl enable iptables.service --now Created symlink /etc/systemd/system/multi-user.target.wants/iptables.service → /usr/lib/systemd/system/iptables.service. [root@localhost ~]# systemctl status iptables.service ● iptables.service - IPv4 firewall with iptables Loaded: loaded (/usr/lib/systemd/system/iptables.service; enabled; vendor preset: disabled) Active: active (exited) since Wed 2022-02-09 22:42:42 CST; 3s ago Process: 788891 ExecStart=/usr/libexec/iptables/iptables.init start (code=exited, status=0/SUCCESS) Main PID: 788891 (code=exited, status=0/SUCCESS) 2月 09 22:42:42 localhost.localdomain systemd[1]: Starting IPv4 firewall with iptables... 2月 09 22:42:42 localhost.localdomain iptables.init[788891]: iptables: Applying firewall rules: [ OK ] 2月 09 22:42:42 localhost.localdomain systemd[1]: Started IPv4 firewall with iptables. [root@localhost ~]# iptables -nvL Chain INPUT (policy ACCEPT 262 packets, 19223 bytes) pkts bytes target prot opt in out source destination 759 110K ACCEPT tcp -- * * 10.0.0.0/8 0.0.0.0/0 tcp 0 0 DROP tcp -- eth0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 725 packets, 5611K bytes) pkts bytes target prot opt in out source destination |
接下来就简单了,因为开门产生的rules并未持久化落盘,所以要恢复到原有条目,只要重启该服务即可。
可以用 crontab -e 写入计划任务,这里的例子是,每周三晚上23:46自动恢复iptables已保存条目。
1 2 3 4 |
[root@localhost secret_door]# crontab -e crontab: installing new crontab [root@localhost secret_door]# crontab -l 46 23 * * 3 /bin/systemctl restart iptables.service |
测试定时任务执行成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
[root@localhost secret_door]# iptables -nvL Chain INPUT (policy ACCEPT 86 packets, 5993 bytes) pkts bytes target prot opt in out source destination 0 0 ACCEPT tcp -- * * 1.2.3.4 0.0.0.0/0 tcp dpt:3306 985 193K ACCEPT tcp -- * * 10.0.0.0/8 0.0.0.0/0 tcp 0 0 DROP tcp -- eth0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 675 packets, 2539K bytes) pkts bytes target prot opt in out source destination [root@localhost secret_door]# date 2022年 02月 09日 星期三 23:45:37 CST [root@localhost secret_door]# tail -f /var/log/cron ... Feb 9 23:45:32 localhost crontab[815950]: (root) END EDIT (root) Feb 9 23:45:34 localhost crontab[815952]: (root) LIST (root) Feb 9 23:46:02 localhost crond[1232]: (root) RELOAD (/var/spool/cron/root) Feb 9 23:46:02 localhost CROND[815974]: (root) CMD (/bin/systemctl restart iptables.service) ^C [root@localhost secret_door]# iptables -nvL Chain INPUT (policy ACCEPT 82 packets, 5824 bytes) pkts bytes target prot opt in out source destination 1270 217K ACCEPT tcp -- * * 10.0.0.0/8 0.0.0.0/0 tcp 0 0 DROP tcp -- eth0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 882 packets, 2615K bytes) pkts bytes target prot opt in out source destination [root@localhost secret_door]# |
文章的脚注信息由WordPress的wp-posturl插件自动生成